diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
index 546c9259f6..8742ab3e78 100644
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ b/build/NuSpecs/UmbracoCms.Core.nuspec
@@ -28,11 +28,11 @@
-
+
-
+
diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt
index 9199867efa..c3a575dc51 100644
--- a/build/UmbracoVersion.txt
+++ b/build/UmbracoVersion.txt
@@ -1,2 +1,2 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
-7.2.6
\ No newline at end of file
+7.2.7
\ No newline at end of file
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index 0e3d292c0f..3c73b4d055 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyFileVersion("7.2.6")]
-[assembly: AssemblyInformationalVersion("7.2.6")]
\ No newline at end of file
+[assembly: AssemblyFileVersion("7.2.7")]
+[assembly: AssemblyInformationalVersion("7.2.7")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs
index 382c3f29c4..93d502c8d3 100644
--- a/src/Umbraco.Core/ApplicationContext.cs
+++ b/src/Umbraco.Core/ApplicationContext.cs
@@ -1,15 +1,12 @@
using System;
using System.Configuration;
using System.Threading;
-using System.Web;
-using System.Web.Caching;
-using Umbraco.Core.Cache;
+using System.Threading.Tasks;
using Umbraco.Core.Configuration;
-using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Services;
-
+using Umbraco.Core.Sync;
namespace Umbraco.Core
{
@@ -36,6 +33,8 @@ namespace Umbraco.Core
_databaseContext = dbContext;
_services = serviceContext;
ApplicationCache = cache;
+
+ Init();
}
///
@@ -45,6 +44,8 @@ namespace Umbraco.Core
public ApplicationContext(CacheHelper cache)
{
ApplicationCache = cache;
+
+ Init();
}
///
@@ -60,13 +61,13 @@ namespace Umbraco.Core
///
public static ApplicationContext EnsureContext(ApplicationContext appContext, bool replaceContext)
{
- if (ApplicationContext.Current != null)
+ if (Current != null)
{
if (!replaceContext)
- return ApplicationContext.Current;
+ return Current;
}
- ApplicationContext.Current = appContext;
- return ApplicationContext.Current;
+ Current = appContext;
+ return Current;
}
///
@@ -85,14 +86,14 @@ namespace Umbraco.Core
///
public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, bool replaceContext)
{
- if (ApplicationContext.Current != null)
+ if (Current != null)
{
if (!replaceContext)
- return ApplicationContext.Current;
+ return Current;
}
var ctx = new ApplicationContext(dbContext, serviceContext, cache);
- ApplicationContext.Current = ctx;
- return ApplicationContext.Current;
+ Current = ctx;
+ return Current;
}
///
@@ -141,61 +142,75 @@ namespace Umbraco.Core
// GlobalSettings.CurrentVersion returns the hard-coded "current version"
// the system is configured if they match
// if they don't, install runs, updates web.config (presumably) and updates GlobalSettings.ConfiguredStatus
- //
- // then there is Application["umbracoNeedConfiguration"] which makes no sense... getting rid of it... SD: I have actually remove that now!
- //
+
public bool IsConfigured
{
- // todo - we should not do this - ok for now
- get
- {
- return Configured;
- }
+ get { return _configured.Value; }
}
///
- /// The original/first url that the web application executes
+ /// The application url.
///
///
- /// we need to set the initial url in our ApplicationContext, this is so our keep alive service works and this must
- /// exist on a global context because the keep alive service doesn't run in a web context.
- /// we are NOT going to put a lock on this because locking will slow down the application and we don't really care
- /// if two threads write to this at the exact same time during first page hit.
- /// see: http://issues.umbraco.org/issue/U4-2059
+ /// The application url is the url that should be used by services to talk to the application,
+ /// eg keep alive or scheduled publishing services. It must exist on a global context because
+ /// some of these services may not run within a web context.
+ /// The format of the application url is:
+ /// - has a scheme (http or https)
+ /// - has the SystemDirectories.Umbraco path
+ /// - does not end with a slash
+ /// It is initialized on the first request made to the server, by UmbracoModule.EnsureApplicationUrl:
+ /// - if umbracoSettings:settings/web.routing/@umbracoApplicationUrl is set, use the value (new setting)
+ /// - if umbracoSettings:settings/scheduledTasks/@baseUrl is set, use the value (backward compatibility)
+ /// - otherwise, use the url of the (first) request.
+ /// Not locking, does not matter if several threads write to this.
+ /// See also issues:
+ /// - http://issues.umbraco.org/issue/U4-2059
+ /// - http://issues.umbraco.org/issue/U4-6788
+ /// - http://issues.umbraco.org/issue/U4-5728
+ /// - http://issues.umbraco.org/issue/U4-5391
///
- internal string OriginalRequestUrl { get; set; }
+ internal string UmbracoApplicationUrl
+ {
+ get
+ {
+ // if initialized, return
+ if (_umbracoApplicationUrl != null) return _umbracoApplicationUrl;
- private bool _versionsDifferenceReported;
+ // try settings
+ ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(this, UmbracoConfig.For.UmbracoSettings());
- ///
- /// Checks if the version configured matches the assembly version
- ///
- private bool Configured
- {
- get
- {
- try
- {
- var configStatus = ConfigurationStatus;
- var currentVersion = UmbracoVersion.Current.ToString(3);
- var ok = configStatus == currentVersion;
+ // and return what we have, may be null
+ return _umbracoApplicationUrl;
+ }
+ set
+ {
+ _umbracoApplicationUrl = value;
+ }
+ }
- if (ok == false && _versionsDifferenceReported == false)
- {
- // remember it's been reported so we don't flood the log
- // no thread-safety so there may be a few log entries, doesn't matter
- _versionsDifferenceReported = true;
- LogHelper.Info("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'");
- }
-
- return ok;
- }
- catch
- {
- return false;
- }
- }
- }
+ internal string _umbracoApplicationUrl; // internal for tests
+
+ private Lazy _configured;
+ internal MainDom MainDom { get; private set; }
+
+ private void Init()
+ {
+ MainDom = new MainDom();
+ MainDom.Acquire();
+ //Create the lazy value to resolve whether or not the application is 'configured'
+ _configured = new Lazy(() =>
+ {
+ var configStatus = ConfigurationStatus;
+ var currentVersion = UmbracoVersion.Current.ToString(3);
+ var ok = configStatus == currentVersion;
+ if (ok == false)
+ {
+ LogHelper.Debug("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'");
+ }
+ return ok;
+ });
+ }
private string ConfigurationStatus
{
@@ -212,12 +227,6 @@ namespace Umbraco.Core
}
}
- private void AssertIsReady()
- {
- if (!this.IsReady)
- throw new Exception("ApplicationContext is not ready yet.");
- }
-
private void AssertIsNotReady()
{
if (this.IsReady)
diff --git a/src/Umbraco.Core/ApplicationEventHandler.cs b/src/Umbraco.Core/ApplicationEventHandler.cs
index a725a08a8e..8d97baac95 100644
--- a/src/Umbraco.Core/ApplicationEventHandler.cs
+++ b/src/Umbraco.Core/ApplicationEventHandler.cs
@@ -74,7 +74,7 @@ namespace Umbraco.Core
///
private bool ShouldExecute(ApplicationContext applicationContext)
{
- if (applicationContext.IsConfigured && applicationContext.DatabaseContext.CanConnect)
+ if (applicationContext.IsConfigured && applicationContext.DatabaseContext.IsDatabaseConfigured)
{
return true;
}
@@ -84,7 +84,7 @@ namespace Umbraco.Core
return true;
}
- if (!applicationContext.DatabaseContext.CanConnect && ExecuteWhenDatabaseNotConfigured)
+ if (!applicationContext.DatabaseContext.IsDatabaseConfigured && ExecuteWhenDatabaseNotConfigured)
{
return true;
}
diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs
index 0a9c79a80e..b4e8e64312 100644
--- a/src/Umbraco.Core/AsyncLock.cs
+++ b/src/Umbraco.Core/AsyncLock.cs
@@ -63,14 +63,14 @@ namespace Umbraco.Core
// for anonymous semaphore, use the unique releaser, else create a new one
return _semaphore != null
? _releaser // (IDisposable)new SemaphoreSlimReleaser(_semaphore)
- : (IDisposable)new NamedSemaphoreReleaser(_semaphore2);
+ : new NamedSemaphoreReleaser(_semaphore2);
}
public Task LockAsync()
{
var wait = _semaphore != null
- ? _semaphore.WaitAsync()
- : WaitOneAsync(_semaphore2);
+ ? _semaphore.WaitAsync()
+ : _semaphore2.WaitOneAsync();
return wait.IsCompleted
? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
@@ -79,6 +79,19 @@ namespace Umbraco.Core
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
+ public Task LockAsync(int millisecondsTimeout)
+ {
+ var wait = _semaphore != null
+ ? _semaphore.WaitAsync(millisecondsTimeout)
+ : _semaphore2.WaitOneAsync(millisecondsTimeout);
+
+ return wait.IsCompleted
+ ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
+ : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
+ this, CancellationToken.None,
+ TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
+ }
+
public IDisposable Lock()
{
if (_semaphore != null)
@@ -168,40 +181,5 @@ namespace Umbraco.Core
Dispose(false);
}
}
-
- // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100
- // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html
- // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex...
- // version below should be OK
-
- private static Task WaitOneAsync(WaitHandle handle)
- {
- var tcs = new TaskCompletionSource
public interface ICacheProvider
{
+ ///
+ /// Removes all items from the cache.
+ ///
void ClearAllCache();
+
+ ///
+ /// Removes an item from the cache, identified by its key.
+ ///
+ /// The key of the item.
void ClearCacheItem(string key);
+
+ ///
+ /// Removes items from the cache, of a specified type.
+ ///
+ /// The name of the type to remove.
+ ///
+ /// If the type is an interface, then all items of a type implementing that interface are
+ /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from
+ /// the specified type are not removed).
+ /// Performs a case-sensitive search.
+ ///
void ClearCacheObjectTypes(string typeName);
+
+ ///
+ /// Removes items from the cache, of a specified type.
+ ///
+ /// The type of the items to remove.
+ /// If the type is an interface, then all items of a type implementing that interface are
+ /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from
+ /// the specified type are not removed).
void ClearCacheObjectTypes();
+
+ ///
+ /// Removes items from the cache, of a specified type, satisfying a predicate.
+ ///
+ /// The type of the items to remove.
+ /// The predicate to satisfy.
+ /// If the type is an interface, then all items of a type implementing that interface are
+ /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from
+ /// the specified type are not removed).
void ClearCacheObjectTypes(Func predicate);
+
void ClearCacheByKeySearch(string keyStartsWith);
void ClearCacheByKeyExpression(string regexString);
+
IEnumerable GetCacheItemsByKeySearch(string keyStartsWith);
IEnumerable GetCacheItemsByKeyExpression(string regexString);
diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs
index d4f2c33ed1..045bc4ff4e 100644
--- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
+using System.Reflection;
using System.Runtime.Caching;
using System.Text.RegularExpressions;
using System.Threading;
@@ -16,12 +17,19 @@ namespace Umbraco.Core.Cache
///
internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
{
+
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
internal ObjectCache MemoryCache;
+ ///
+ /// Used for debugging
+ ///
+ internal Guid InstanceId { get; private set; }
+
public ObjectCacheRuntimeCacheProvider()
{
MemoryCache = new MemoryCache("in-memory");
+ InstanceId = Guid.NewGuid();
}
#region Clear
@@ -41,11 +49,14 @@ namespace Umbraco.Core.Cache
{
if (MemoryCache[key] == null) return;
MemoryCache.Remove(key);
- }
+ }
}
public virtual void ClearCacheObjectTypes(string typeName)
{
+ var type = TypeFinder.GetTypeByName(typeName);
+ if (type == null) return;
+ var isInterface = type.IsInterface;
using (new WriteLock(_locker))
{
foreach (var key in MemoryCache
@@ -55,7 +66,10 @@ namespace Umbraco.Core.Cache
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true);
- return value == null || value.GetType().ToString().InvariantEquals(typeName);
+
+ // if T is an interface remove anything that implements that interface
+ // otherwise remove exact types (not inherited types)
+ return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type));
})
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
@@ -67,7 +81,8 @@ namespace Umbraco.Core.Cache
{
using (new WriteLock(_locker))
{
- var typeOfT = typeof (T);
+ var typeOfT = typeof(T);
+ var isInterface = typeOfT.IsInterface;
foreach (var key in MemoryCache
.Where(x =>
{
@@ -75,7 +90,11 @@ namespace Umbraco.Core.Cache
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true);
- return value == null || value.GetType() == typeOfT;
+
+ // if T is an interface remove anything that implements that interface
+ // otherwise remove exact types (not inherited types)
+ return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT));
+
})
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
@@ -88,6 +107,7 @@ namespace Umbraco.Core.Cache
using (new WriteLock(_locker))
{
var typeOfT = typeof(T);
+ var isInterface = typeOfT.IsInterface;
foreach (var key in MemoryCache
.Where(x =>
{
@@ -96,8 +116,11 @@ namespace Umbraco.Core.Cache
// get non-created as NonCreatedValue & exceptions as null
var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true);
if (value == null) return true;
- return value.GetType() == typeOfT
- && predicate(x.Key, (T) value);
+
+ // if T is an interface remove anything that implements that interface
+ // otherwise remove exact types (not inherited types)
+ return (isInterface ? (value is T) : (value.GetType() == typeOfT))
+ && predicate(x.Key, (T)value);
})
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
@@ -114,7 +137,7 @@ namespace Umbraco.Core.Cache
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
MemoryCache.Remove(key);
- }
+ }
}
public virtual void ClearCacheByKeyExpression(string regexString)
@@ -126,7 +149,7 @@ namespace Umbraco.Core.Cache
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
MemoryCache.Remove(key);
- }
+ }
}
#endregion
@@ -178,7 +201,7 @@ namespace Umbraco.Core.Cache
return GetCacheItem(cacheKey, getCacheItem, null);
}
- public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal,CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
+ public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
// see notes in HttpRuntimeCacheProvider
@@ -193,6 +216,7 @@ namespace Umbraco.Core.Cache
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
lck.UpgradeToWriteLock();
+ //NOTE: This does an add or update
MemoryCache.Set(cacheKey, result, policy);
}
}
@@ -219,6 +243,7 @@ namespace Umbraco.Core.Cache
if (value == null) return; // do not store null values (backward compat)
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
+ //NOTE: This does an add or update
MemoryCache.Set(cacheKey, result, policy);
}
@@ -239,7 +264,7 @@ namespace Umbraco.Core.Cache
{
policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependentFiles.ToList()));
}
-
+
if (removedCallback != null)
{
policy.RemovedCallback = arguments =>
@@ -270,6 +295,6 @@ namespace Umbraco.Core.Cache
}
return policy;
}
-
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs
index f3d42b6904..2998fc2f78 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs
@@ -11,6 +11,8 @@
bool DisableFindContentByIdPath { get; }
string UrlProviderMode { get; }
+
+ string UmbracoApplicationUrl { get; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs
index f5b71eb2c7..1ed9bc034c 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs
@@ -30,8 +30,13 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")]
public string UrlProviderMode
{
- get { return (string)base["urlProviderMode"]; }
+ get { return (string) base["urlProviderMode"]; }
}
+ [ConfigurationProperty("umbracoApplicationUrl", DefaultValue = null)]
+ public string UmbracoApplicationUrl
+ {
+ get { return (string)base["umbracoApplicationUrl"]; }
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
index a548f759c7..3f8305589e 100644
--- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
@@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
- private static readonly Version Version = new Version("7.2.6");
+ private static readonly Version Version = new Version("7.2.7");
///
/// Gets the current version of Umbraco.
diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs
index 6e00d3d682..c5d3e2a4c7 100644
--- a/src/Umbraco.Core/CoreBootManager.cs
+++ b/src/Umbraco.Core/CoreBootManager.cs
@@ -2,10 +2,12 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Web;
using AutoMapper;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Mapping;
@@ -66,7 +68,9 @@ namespace Umbraco.Core
InitializeProfilerResolver();
- _timer = DisposableTimer.TraceDuration("Umbraco application starting", "Umbraco application startup complete");
+ _timer = DisposableTimer.TraceDuration(
+ string.Format("Umbraco {0} application starting on {1}", UmbracoVersion.Current, NetworkHelper.MachineName),
+ "Umbraco application startup complete");
CreateApplicationCache();
@@ -95,9 +99,25 @@ namespace Umbraco.Core
InitializeModelMappers();
- //now we need to call the initialize methods
- ApplicationEventsResolver.Current.ApplicationEventHandlers
- .ForEach(x => x.OnApplicationInitialized(UmbracoApplication, ApplicationContext));
+ using (DisposableTimer.DebugDuration(
+ () => string.Format("Executing {0} IApplicationEventHandler.OnApplicationInitialized", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()),
+ () => "Finished executing IApplicationEventHandler.OnApplicationInitialized"))
+ {
+ //now we need to call the initialize methods
+ ApplicationEventsResolver.Current.ApplicationEventHandlers
+ .ForEach(x =>
+ {
+ try
+ {
+ x.OnApplicationInitialized(UmbracoApplication, ApplicationContext);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex);
+ throw;
+ }
+ });
+ }
_isInitialized = true;
@@ -201,11 +221,27 @@ namespace Umbraco.Core
if (_isStarted)
throw new InvalidOperationException("The boot manager has already been initialized");
- //call OnApplicationStarting of each application events handler
- ApplicationEventsResolver.Current.ApplicationEventHandlers
- .ForEach(x => x.OnApplicationStarting(UmbracoApplication, ApplicationContext));
+ using (DisposableTimer.DebugDuration(
+ () => string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarting", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()),
+ () => "Finished executing IApplicationEventHandler.OnApplicationStarting"))
+ {
+ //call OnApplicationStarting of each application events handler
+ ApplicationEventsResolver.Current.ApplicationEventHandlers
+ .ForEach(x =>
+ {
+ try
+ {
+ x.OnApplicationStarting(UmbracoApplication, ApplicationContext);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex);
+ throw;
+ }
+ });
+ }
- if (afterStartup != null)
+ if (afterStartup != null)
{
afterStartup(ApplicationContext.Current);
}
@@ -226,10 +262,29 @@ namespace Umbraco.Core
throw new InvalidOperationException("The boot manager has already been completed");
FreezeResolution();
-
- //call OnApplicationStarting of each application events handler
- ApplicationEventsResolver.Current.ApplicationEventHandlers
- .ForEach(x => x.OnApplicationStarted(UmbracoApplication, ApplicationContext));
+
+ //Here we need to make sure the db can be connected to
+ EnsureDatabaseConnection();
+
+ using (DisposableTimer.DebugDuration(
+ () => string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarted", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()),
+ () => "Finished executing IApplicationEventHandler.OnApplicationStarted"))
+ {
+ //call OnApplicationStarting of each application events handler
+ ApplicationEventsResolver.Current.ApplicationEventHandlers
+ .ForEach(x =>
+ {
+ try
+ {
+ x.OnApplicationStarted(UmbracoApplication, ApplicationContext);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Error("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex);
+ throw;
+ }
+ });
+ }
//Now, startup all of our legacy startup handler
ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers();
@@ -249,6 +304,32 @@ namespace Umbraco.Core
return this;
}
+ ///
+ /// We cannot continue if the db cannot be connected to
+ ///
+ private void EnsureDatabaseConnection()
+ {
+ if (ApplicationContext.IsConfigured == false) return;
+ if (ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) return;
+
+ var currentTry = 0;
+ while (currentTry < 5)
+ {
+ if (ApplicationContext.DatabaseContext.CanConnect)
+ break;
+
+ //wait and retry
+ Thread.Sleep(1000);
+ currentTry++;
+ }
+
+ if (currentTry == 5)
+ {
+ throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database.");
+ }
+
+ }
+
///
/// Freeze resolution to not allow Resolvers to be modified
///
diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs
index fad96b849d..dfaebb0315 100644
--- a/src/Umbraco.Core/DatabaseContext.cs
+++ b/src/Umbraco.Core/DatabaseContext.cs
@@ -26,8 +26,6 @@ namespace Umbraco.Core
{
private readonly IDatabaseFactory _factory;
private bool _configured;
- private bool _canConnect;
- private volatile bool _connectCheck = false;
private readonly object _locker = new object();
private string _connectionString;
private string _providerName;
@@ -67,21 +65,9 @@ namespace Umbraco.Core
get
{
if (IsDatabaseConfigured == false) return false;
-
- //double check lock so that it is only checked once and is fast
- if (_connectCheck == false)
- {
- lock (_locker)
- {
- if (_canConnect == false)
- {
- _canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
- _connectCheck = true;
- }
- }
- }
-
- return _canConnect;
+ var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
+ LogHelper.Info("CanConnect = " + canConnect);
+ return canConnect;
}
}
diff --git a/src/Umbraco.Core/DisposableObject.cs b/src/Umbraco.Core/DisposableObject.cs
index f054b53c98..516a9712e5 100644
--- a/src/Umbraco.Core/DisposableObject.cs
+++ b/src/Umbraco.Core/DisposableObject.cs
@@ -4,69 +4,58 @@ using System.Threading;
namespace Umbraco.Core
{
///
- /// Abstract implementation of logic commonly required to safely handle disposable unmanaged resources.
+ /// Abstract implementation of IDisposable.
///
+ ///
+ /// Can also be used as a pattern for when inheriting is not possible.
+ ///
+ /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx
+ /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/
+ ///
+ /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor
+ /// has allocated disposable objects, it should take care of disposing them.
+ ///
public abstract class DisposableObject : IDisposable
{
private bool _disposed;
- private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim();
+ private readonly object _locko = new object();
- ///
- /// Gets a value indicating whether this instance is disposed.
- ///
- ///
- /// true if this instance is disposed; otherwise, false.
- ///
- public bool IsDisposed
- {
- get { return _disposed; }
- }
+ // gets a value indicating whether this instance is disposed.
+ // for internal tests only (not thread safe)
+ //TODO make this internal + rename "Disposed" when we can break compatibility
+ public bool IsDisposed { get { return _disposed; } }
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- /// 2
+ // implements IDisposable
public void Dispose()
{
Dispose(true);
-
- // Use SupressFinalize in case a subclass of this type implements a finalizer.
GC.SuppressFinalize(this);
}
+ // finalizer
~DisposableObject()
{
- // Run dispose but let the class know it was due to the finalizer running.
Dispose(false);
}
- protected virtual void Dispose(bool disposing)
+ //TODO make this private, non-virtual when we can break compatibility
+ protected virtual void Dispose(bool disposing)
{
- // Only operate if we haven't already disposed
- if (IsDisposed || !disposing) return;
+ lock (_locko)
+ {
+ if (_disposed) return;
+ _disposed = true;
+ }
- using (new WriteLock(_disposalLocker))
- {
- // Check again now we're inside the lock
- if (IsDisposed) return;
+ DisposeUnmanagedResources();
- // Call to actually release resources. This method is only
- // kept separate so that the entire disposal logic can be used as a VS snippet
- DisposeResources();
-
- // Indicate that the instance has been disposed.
- _disposed = true;
- }
+ if (disposing)
+ DisposeResources();
}
- ///
- /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic.
- ///
protected abstract void DisposeResources();
protected virtual void DisposeUnmanagedResources()
- {
-
- }
+ { }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs b/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs
new file mode 100644
index 0000000000..d27d38de9a
--- /dev/null
+++ b/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Umbraco.Core.Exceptions
+{
+ ///
+ /// An exception that is thrown if the umbraco application cannnot boot
+ ///
+ public class UmbracoStartupFailedException : Exception
+ {
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
+ public UmbracoStartupFailedException(string message) : base(message)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Logging/LogHelper.cs b/src/Umbraco.Core/Logging/LogHelper.cs
index 14aab56f0b..05b9e7ed65 100644
--- a/src/Umbraco.Core/Logging/LogHelper.cs
+++ b/src/Umbraco.Core/Logging/LogHelper.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Web;
@@ -40,8 +41,11 @@ namespace Umbraco.Core.Logging
///
private static string PrefixThreadId(string generateMessageFormat)
{
- return "[T" + Thread.CurrentThread.ManagedThreadId + "/D" + AppDomain.CurrentDomain.Id + "] " + generateMessageFormat;
- }
+ return (_prefixThreadId ?? (_prefixThreadId = "[P" + Process.GetCurrentProcess().Id + "/T" + Thread.CurrentThread.ManagedThreadId + "/D" + AppDomain.CurrentDomain.Id + "] "))
+ + generateMessageFormat;
+ }
+
+ private static string _prefixThreadId = null;
#region Error
///
diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs
new file mode 100644
index 0000000000..9eec3d4da6
--- /dev/null
+++ b/src/Umbraco.Core/MainDom.cs
@@ -0,0 +1,185 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO.MemoryMappedFiles;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Hosting;
+using Umbraco.Core.Logging;
+using Umbraco.Core.ObjectResolution;
+
+namespace Umbraco.Core
+{
+ // represents the main domain
+ class MainDom : IRegisteredObject
+ {
+ #region Vars
+
+ // our own lock for local consistency
+ private readonly object _locko = new object();
+
+ // async lock representing the main domain lock
+ private readonly AsyncLock _asyncLock;
+ private IDisposable _asyncLocker;
+
+ // event wait handle used to notify current main domain that it should
+ // release the lock because a new domain wants to be the main domain
+ private readonly EventWaitHandle _signal;
+
+ // indicates whether...
+ private volatile bool _isMainDom; // we are the main domain
+ private volatile bool _signaled; // we have been signaled
+
+ // actions to run before releasing the main domain
+ private readonly SortedList _callbacks = new SortedList();
+
+ private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds
+
+ #endregion
+
+ #region Ctor
+
+ // initializes a new instance of MainDom
+ public MainDom()
+ {
+ var appId = string.Empty;
+ // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail
+ if (HostingEnvironment.ApplicationID != null)
+ appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty);
+
+ var lockName = "UMBRACO-" + appId + "-MAINDOM-LCK";
+ _asyncLock = new AsyncLock(lockName);
+
+ var eventName = "UMBRACO-" + appId + "-MAINDOM-EVT";
+ _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
+ }
+
+ #endregion
+
+ // register a main domain consumer
+ public bool Register(Action release, int weight = 100)
+ {
+ return Register(null, release, weight);
+ }
+
+ // register a main domain consumer
+ public bool Register(Action install, Action release, int weight = 100)
+ {
+ lock (_locko)
+ {
+ if (_signaled) return false;
+ if (install != null)
+ install();
+ if (release != null)
+ _callbacks.Add(weight, release);
+ return true;
+ }
+ }
+
+ // handles the signal requesting that the main domain is released
+ private void OnSignal(string source)
+ {
+ // once signaled, we stop waiting, but then there is the hosting environment
+ // so we have to make sure that we only enter that method once
+
+ lock (_locko)
+ {
+ LogHelper.Debug("Signaled" + (_signaled ? " (again)" : "") + " (" + source + ").");
+ if (_signaled) return;
+ if (_isMainDom == false) return; // probably not needed
+ _signaled = true;
+ }
+
+ try
+ {
+ LogHelper.Debug("Stopping...");
+ foreach (var callback in _callbacks.Values)
+ {
+ try
+ {
+ callback(); // no timeout on callbacks
+ }
+ catch (Exception e)
+ {
+ LogHelper.Error("Error while running callback, remaining callbacks will not run.", e);
+ throw;
+ }
+
+ }
+ LogHelper.Debug("Stopped.");
+ }
+ finally
+ {
+ // in any case...
+ _isMainDom = false;
+ _asyncLocker.Dispose();
+ LogHelper.Debug("Released MainDom.");
+ }
+ }
+
+ // acquires the main domain
+ public bool Acquire()
+ {
+ lock (_locko) // we don't want the hosting environment to interfere by signaling
+ {
+ // if signaled, too late to acquire, give up
+ // the handler is not installed so that would be the hosting environment
+ if (_signaled)
+ {
+ LogHelper.Debug("Cannot acquire MainDom (signaled).");
+ return false;
+ }
+
+ LogHelper.Debug("Acquiring MainDom...");
+
+ // signal other instances that we want the lock, then wait one the lock,
+ // which may timeout, and this is accepted - see comments below
+
+ // signal, then wait for the lock, then make sure the event is
+ // resetted (maybe there was noone listening..)
+ _signal.Set();
+
+ // if more than 1 instance reach that point, one will get the lock
+ // and the other one will timeout, which is accepted
+
+ _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds);
+ _isMainDom = true;
+
+ // we need to reset the event, because otherwise we would end up
+ // signaling ourselves and commiting suicide immediately.
+ // only 1 instance can reach that point, but other instances may
+ // have started and be trying to get the lock - they will timeout,
+ // which is accepted
+
+ _signal.Reset();
+ _signal.WaitOneAsync()
+ .ContinueWith(_ => OnSignal("signal"));
+
+ HostingEnvironment.RegisterObject(this);
+
+ LogHelper.Debug("Acquired MainDom.");
+ return true;
+ }
+ }
+
+ // gets a value indicating whether we are the main domain
+ public bool IsMainDom
+ {
+ get { return _isMainDom; }
+ }
+
+ // IRegisteredObject
+ public void Stop(bool immediate)
+ {
+ try
+ {
+ OnSignal("environment"); // will run once
+ }
+ finally
+ {
+ HostingEnvironment.UnregisterObject(this);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs
index 8ba30665d0..1dc882674d 100644
--- a/src/Umbraco.Core/Models/Property.cs
+++ b/src/Umbraco.Core/Models/Property.cs
@@ -137,9 +137,23 @@ namespace Umbraco.Core.Models
new DelegateEqualityComparer(
(o, o1) =>
{
- //Custom comparer for enumerable if it is enumerable
if (o == null && o1 == null) return true;
+
+ //custom comparer for strings.
+ if (o is string || o1 is string)
+ {
+ //if one is null and another is empty then they are the same
+ if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace())
+ {
+ return true;
+ }
+ if (o == null || o1 == null) return false;
+ return o.Equals(o1);
+ }
+
if (o == null || o1 == null) return false;
+
+ //Custom comparer for enumerable if it is enumerable
var enum1 = o as IEnumerable;
var enum2 = o1 as IEnumerable;
if (enum1 != null && enum2 != null)
diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs
index 87eb06e295..0b478d54cf 100644
--- a/src/Umbraco.Core/ObjectResolution/Resolution.cs
+++ b/src/Umbraco.Core/ObjectResolution/Resolution.cs
@@ -112,7 +112,7 @@ namespace Umbraco.Core.ObjectResolution
/// resolution is already frozen.
public static void Freeze()
{
- LogHelper.Debug(typeof(Resolution), "Freezing resolution");
+ LogHelper.Debug(typeof (Resolution), "Freezing resolution");
using (new WriteLock(ConfigurationLock))
{
@@ -121,9 +121,20 @@ namespace Umbraco.Core.ObjectResolution
_isFrozen = true;
}
-
- if (Frozen != null)
- Frozen(null, null);
+
+ LogHelper.Debug(typeof(Resolution), "Resolution is frozen");
+
+ if (Frozen == null) return;
+
+ try
+ {
+ Frozen(null, null);
+ }
+ catch (Exception e)
+ {
+ LogHelper.Error(typeof (Resolution), "Exception in Frozen event handler.", e);
+ throw;
+ }
}
///
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs
index 31cfeb7997..0ad3d8e5b9 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs
@@ -34,15 +34,20 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven
if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql)
{
Delete.ForeignKey().FromTable("cmsTagRelationship").ForeignColumn("nodeId").ToTable("umbracoNode").PrimaryColumn("id");
+ //check for another strange really old one that might have existed
+ if (constraints.Any(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "tagId"))
+ {
+ Delete.ForeignKey().FromTable("cmsTagRelationship").ForeignColumn("tagId").ToTable("cmsTags").PrimaryColumn("id");
+ }
}
else
{
//Before we try to delete this constraint, we'll see if it exists first, some older schemas never had it and some older schema's had this named
// differently than the default.
- var constraint = constraints
- .SingleOrDefault(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "nodeId" && x.Item3.InvariantStartsWith("PK_") == false);
- if (constraint != null)
+ var constraintMatches = constraints.Where(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "nodeId" && x.Item3.InvariantStartsWith("PK_") == false);
+
+ foreach (var constraint in constraintMatches)
{
Delete.ForeignKey(constraint.Item3).OnTable("cmsTagRelationship");
}
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs
index 198e8307eb..dc775cb426 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs
@@ -42,11 +42,33 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero
Create.Index("IX_cmsDocument_newest").OnTable("cmsDocument").OnColumn("newest").Ascending().WithOptions().NonClustered();
}
- //TODO: We need to fix this for SQL Azure since it does not let you drop any clustered indexes
- // Issue: http://issues.umbraco.org/issue/U4-5673
- // Some work around notes:
- // http://stackoverflow.com/questions/15872347/alter-clustered-index-column
- // https://social.msdn.microsoft.com/Forums/azure/en-US/5cc4b302-fa42-4c62-956a-bbf79dbbd040/changing-clustered-index-in-azure?forum=ssdsgetstarted
+ //We need to do this for SQL Azure V2 since it does not let you drop any clustered indexes
+ // Issue: http://issues.umbraco.org/issue/U4-5673
+ if (Context.CurrentDatabaseProvider == DatabaseProviders.SqlServer || Context.CurrentDatabaseProvider == DatabaseProviders.SqlAzure)
+ {
+ var version = Context.Database.ExecuteScalar("SELECT @@@@VERSION");
+ if (version.Contains("Microsoft SQL Azure"))
+ {
+ var parts = version.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray();
+ if (parts.Length > 1)
+ {
+ if (parts[1].StartsWith("11."))
+ {
+
+ //we want to drop the umbracoUserLogins_Index index since it is named incorrectly and then re-create it so
+ // it follows the standard naming convention
+ if (dbIndexes.Any(x => x.IndexName.InvariantEquals("umbracoUserLogins_Index")))
+ {
+ //It's the old version that doesn't support dropping a clustered index on a table, so we need to do some manual work.
+ ExecuteSqlAzureSqlForChangingIndex();
+ }
+
+ return;
+ }
+ }
+ }
+ }
+
//we want to drop the umbracoUserLogins_Index index since it is named incorrectly and then re-create it so
// it follows the standard naming convention
@@ -67,5 +89,28 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero
Delete.Index("IX_cmsDocument_published").OnTable("cmsDocument");
Delete.Index("IX_cmsDocument_newest").OnTable("cmsDocument");
}
+
+ private void ExecuteSqlAzureSqlForChangingIndex()
+ {
+ Context.Database.Execute(@"CREATE TABLE ""umbracoUserLogins_temp""
+(
+ contextID uniqueidentifier NOT NULL,
+ userID int NOT NULL,
+ [timeout] bigint NOT NULL
+);
+CREATE CLUSTERED INDEX ""IX_umbracoUserLogins_Index"" ON ""umbracoUserLogins_temp"" (""contextID"");
+INSERT INTO ""umbracoUserLogins_temp"" SELECT * FROM ""umbracoUserLogins""
+DROP TABLE ""umbracoUserLogins""
+CREATE TABLE ""umbracoUserLogins""
+(
+ contextID uniqueidentifier NOT NULL,
+ userID int NOT NULL,
+ [timeout] bigint NOT NULL
+);
+CREATE CLUSTERED INDEX ""IX_umbracoUserLogins_Index"" ON ""umbracoUserLogins"" (""contextID"");
+INSERT INTO ""umbracoUserLogins"" SELECT * FROM ""umbracoUserLogins_temp""
+DROP TABLE ""umbracoUserLogins_temp""");
+
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
index 2a58879025..b606830c62 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
@@ -493,15 +493,10 @@ AND umbracoNode.id <> @id",
throw new InvalidOperationException("Cannot insert a pre value for a data type that has no identity");
}
- //Cannot add a duplicate alias
- var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataTypePreValues
-WHERE alias = @alias
-AND datatypeNodeId = @dtdid",
- new { alias = entity.Alias, dtdid = entity.DataType.Id });
- if (exists > 0)
- {
- throw new DuplicateNameException("A pre value with the alias " + entity.Alias + " already exists for this data type");
- }
+ //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT
+ // in reality there is no need to check the uniqueness of this alias because the only way that this code executes is
+ // based on an IDictionary dictionary being passed to this repository and a dictionary
+ // must have unique aliases by definition, so there is no need for this additional check
var dto = new DataTypePreValueDto
{
@@ -519,17 +514,12 @@ AND datatypeNodeId = @dtdid",
{
throw new InvalidOperationException("Cannot update a pre value for a data type that has no identity");
}
-
- //Cannot change to a duplicate alias
- var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataTypePreValues
-WHERE alias = @alias
-AND datatypeNodeId = @dtdid
-AND id <> @id",
- new { id = entity.Id, alias = entity.Alias, dtdid = entity.DataType.Id });
- if (exists > 0)
- {
- throw new DuplicateNameException("A pre value with the alias " + entity.Alias + " already exists for this data type");
- }
+
+ //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT
+ // this causes issues when sorting the pre-values (http://issues.umbraco.org/issue/U4-5670) but in reality
+ // there is no need to check the uniqueness of this alias because the only way that this code executes is
+ // based on an IDictionary dictionary being passed to this repository and a dictionary
+ // must have unique aliases by definition, so there is no need for this additional check
var dto = new DataTypePreValueDto
{
diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs
index d03edc0fc6..c63d8cdfde 100644
--- a/src/Umbraco.Core/PluginManager.cs
+++ b/src/Umbraco.Core/PluginManager.cs
@@ -606,14 +606,10 @@ namespace Umbraco.Core
///
internal IEnumerable CreateInstances(IEnumerable types, bool throwException = false)
{
- //Have removed logging because it doesn't really need to be logged since the time taken is generally 0ms.
- //we want to know if it fails ever, not how long it took if it is only 0.
-
var typesAsArray = types.ToArray();
- //using (DisposableTimer.DebugDuration(
- // String.Format("Starting instantiation of {0} objects of type {1}", typesAsArray.Length, typeof(T).FullName),
- // String.Format("Completed instantiation of {0} objects of type {1}", typesAsArray.Length, typeof(T).FullName)))
- //{
+
+ LogHelper.Debug(string.Format("Instantiating {0} objects of type {1}", typesAsArray.Length, typeof (T).FullName));
+
var instances = new List();
foreach (var t in typesAsArray)
{
@@ -634,7 +630,7 @@ namespace Umbraco.Core
}
}
return instances;
- //}
+
}
///
diff --git a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
index 1d5ee7855a..f3b241a5e0 100644
--- a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
+++ b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
@@ -1,10 +1,9 @@
-using System;
using System.Linq;
using System.Web;
-using System.Xml;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
+using Umbraco.Core.Logging;
namespace Umbraco.Core.Sync
{
@@ -13,58 +12,73 @@ namespace Umbraco.Core.Sync
///
internal static class ServerEnvironmentHelper
{
- ///
- /// Returns the current umbraco base url for the current server depending on it's environment
- /// 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.
- ///
- /// The full base url including schema (i.e. http://myserver:80/umbraco ) - or null if the url
- /// cannot be determined at the moment (usually because the first request has not properly completed yet).
- public static string GetCurrentServerUmbracoBaseUrl(ApplicationContext appContext, IUmbracoSettingsSection settings)
+ public static void TrySetApplicationUrlFromSettings(ApplicationContext appContext, IUmbracoSettingsSection settings)
{
+ // try umbracoSettings:settings/web.routing/@umbracoApplicationUrl
+ // which is assumed to:
+ // - end with SystemDirectories.Umbraco
+ // - contain a scheme
+ // - end or not with a slash, it will be taken care of
+ // eg "http://www.mysite.com/umbraco"
+ var url = settings.WebRouting.UmbracoApplicationUrl;
+ if (url.IsNullOrWhiteSpace() == false)
+ {
+ appContext.UmbracoApplicationUrl = url.TrimEnd('/');
+ LogHelper.Info(typeof(ServerEnvironmentHelper), "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using web.routing/@umbracoApplicationUrl)");
+ return;
+ }
+
+ // try umbracoSettings:settings/scheduledTasks/@baseUrl
+ // which is assumed to:
+ // - end with SystemDirectories.Umbraco
+ // - NOT contain any scheme (because, legacy)
+ // - end or not with a slash, it will be taken care of
+ // eg "mysite.com/umbraco"
+ url = settings.ScheduledTasks.BaseUrl;
+ if (url.IsNullOrWhiteSpace() == false)
+ {
+ var ssl = GlobalSettings.UseSSL ? "s" : "";
+ url = "http" + ssl + "://" + url;
+ appContext.UmbracoApplicationUrl = url.TrimEnd('/');
+ LogHelper.Info(typeof(ServerEnvironmentHelper), "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using scheduledTasks/@baseUrl)");
+ return;
+ }
+
+ // try servers
var status = GetStatus(settings);
-
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, settings);
- }
+ return;
+ // no server, nothing we can do
var servers = settings.DistributedCall.Servers.ToArray();
+ if (servers.Length == 0)
+ return;
- if (servers.Any() == false)
- {
- // 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, settings);
- }
-
+ // we have servers, look for this server
foreach (var server in servers)
{
var appId = server.AppId;
var serverName = server.ServerName;
+ // skip if no data
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
- {
continue;
- }
+ // if this server, build and return the url
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
{
- //match by appId or computer name! return the url configured
- return string.Format("{0}://{1}:{2}/{3}",
+ // match by appId or computer name, return the url configured
+ url = string.Format("{0}://{1}:{2}/{3}",
server.ForceProtocol.IsNullOrWhiteSpace() ? "http" : server.ForceProtocol,
server.ServerAddress,
server.ForcePortnumber.IsNullOrWhiteSpace() ? "80" : server.ForcePortnumber,
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
+
+ appContext.UmbracoApplicationUrl = url.TrimEnd('/');
+ LogHelper.Info(typeof(ServerEnvironmentHelper), "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using distributedCall/servers)");
}
}
-
- // 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, settings);
}
///
@@ -113,21 +127,5 @@ namespace Umbraco.Core.Sync
return CurrentServerEnvironmentStatus.Slave;
}
-
- private static string GetBaseUrl(ApplicationContext appContext, IUmbracoSettingsSection settings)
- {
- return (
- // is config empty?
- settings.ScheduledTasks.BaseUrl.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" : "", settings.ScheduledTasks.BaseUrl))
- .EnsureEndsWith('/');
- }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs
index 6b257fecfe..abaf5ae5e0 100644
--- a/src/Umbraco.Core/TypeFinder.cs
+++ b/src/Umbraco.Core/TypeFinder.cs
@@ -137,7 +137,7 @@ namespace Umbraco.Core
}
return _allAssemblies;
- }
+ }
}
///
@@ -226,7 +226,7 @@ namespace Umbraco.Core
}
return LocalFilteredAssemblyCache;
- }
+ }
}
///
@@ -451,7 +451,7 @@ namespace Umbraco.Core
var allTypes = GetTypesWithFormattedException(a)
.ToArray();
- var attributedTypes = new Type[] {};
+ var attributedTypes = new Type[] { };
try
{
//now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes but have
@@ -480,7 +480,8 @@ namespace Umbraco.Core
//now we need to include types that may be inheriting from sub classes of the attribute type being searched for
//so we will search in assemblies that reference those types too.
- foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly)){
+ foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly))
+ {
//So that we are not scanning too much, we need to group the sub types:
// * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type.
@@ -610,7 +611,7 @@ namespace Umbraco.Core
catch (TypeLoadException ex)
{
LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex);
- continue;
+ continue;
}
//add the types to our list to return
@@ -618,7 +619,7 @@ namespace Umbraco.Core
{
foundAssignableTypes.Add(t);
}
-
+
//now we need to include types that may be inheriting from sub classes of the type being searched for
//so we will search in assemblies that reference those types too.
foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly))
@@ -699,7 +700,18 @@ namespace Umbraco.Core
#endregion
-
+ //TODO: This isn't very elegant, and will have issues since the AppDomain.CurrentDomain
+ // doesn't actualy load in all assemblies, only the types that have been referenced so far.
+ // However, in a web context, the BuildManager will have executed which will force all assemblies
+ // to be loaded so it's fine for now.
+ internal static Type GetTypeByName(string typeName)
+ {
+ var type = Type.GetType(typeName);
+ if (type != null) return type;
+ return AppDomain.CurrentDomain.GetAssemblies()
+ .Select(x => x.GetType(typeName))
+ .FirstOrDefault(x => x != null);
+ }
}
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 67ce55b8f1..cf5b46776f 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -312,10 +312,12 @@
+
+
@@ -1214,6 +1216,7 @@
+
diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs
index c861eca6cd..550228e3a8 100644
--- a/src/Umbraco.Core/UmbracoApplicationBase.cs
+++ b/src/Umbraco.Core/UmbracoApplicationBase.cs
@@ -36,6 +36,19 @@ namespace Umbraco.Core
//don't output the MVC version header (security)
MvcHandler.DisableMvcResponseHeader = true;
+ //take care of unhandled exceptions - there is nothing we can do to
+ // prevent the entire w3wp process to go down but at least we can try
+ // and log the exception
+ AppDomain.CurrentDomain.UnhandledException += (_, args) =>
+ {
+ var exception = (Exception) args.ExceptionObject;
+ var isTerminating = args.IsTerminating; // always true?
+
+ var msg = "Unhandled exception in AppDomain";
+ if (isTerminating) msg += " (terminating)";
+ LogHelper.Error(msg, exception);
+ };
+
//boot up the application
GetBootManager()
.Initialize()
@@ -73,7 +86,18 @@ namespace Umbraco.Core
protected virtual void OnApplicationStarting(object sender, EventArgs e)
{
if (ApplicationStarting != null)
- ApplicationStarting(sender, e);
+ {
+ try
+ {
+ ApplicationStarting(sender, e);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Error("An error occurred in an ApplicationStarting event handler", ex);
+ throw;
+ }
+ }
+
}
///
@@ -84,7 +108,17 @@ namespace Umbraco.Core
protected virtual void OnApplicationStarted(object sender, EventArgs e)
{
if (ApplicationStarted != null)
- ApplicationStarted(sender, e);
+ {
+ try
+ {
+ ApplicationStarted(sender, e);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Error("An error occurred in an ApplicationStarted event handler", ex);
+ throw;
+ }
+ }
}
///
@@ -95,7 +129,17 @@ namespace Umbraco.Core
private void OnApplicationInit(object sender, EventArgs e)
{
if (ApplicationInit != null)
- ApplicationInit(sender, e);
+ {
+ try
+ {
+ ApplicationInit(sender, e);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Error("An error occurred in an ApplicationInit event handler", ex);
+ throw;
+ }
+ }
}
///
diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs
index 53441d7ea1..48e9b83b20 100644
--- a/src/Umbraco.Core/UriExtensions.cs
+++ b/src/Umbraco.Core/UriExtensions.cs
@@ -49,7 +49,7 @@ namespace Umbraco.Core
var urlPath = fullUrlPath.TrimStart(appPath).EnsureStartsWith('/');
//check if this is in the umbraco back office
- var isUmbracoPath = urlPath.InvariantStartsWith(GlobalSettings.Path.EnsureStartsWith('/'));
+ var isUmbracoPath = urlPath.InvariantStartsWith(GlobalSettings.Path.EnsureStartsWith('/').TrimStart(appPath.EnsureStartsWith('/')).EnsureStartsWith('/'));
//if not, then def not back office
if (isUmbracoPath == false) return false;
diff --git a/src/Umbraco.Core/WaitHandleExtensions.cs b/src/Umbraco.Core/WaitHandleExtensions.cs
new file mode 100644
index 0000000000..0d840a2496
--- /dev/null
+++ b/src/Umbraco.Core/WaitHandleExtensions.cs
@@ -0,0 +1,44 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Umbraco.Core
+{
+ internal static class WaitHandleExtensions
+ {
+
+ // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100
+ // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html
+ // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex...
+ // version below should be OK
+
+ public static Task WaitOneAsync(this WaitHandle handle, int millisecondsTimeout = Timeout.Infinite)
+ {
+ var tcs = new TaskCompletionSource();
+ var callbackHandleInitLock = new object();
+ lock (callbackHandleInitLock)
+ {
+ RegisteredWaitHandle callbackHandle = null;
+ // ReSharper disable once RedundantAssignment
+ callbackHandle = ThreadPool.RegisterWaitForSingleObject(
+ handle,
+ (state, timedOut) =>
+ {
+ tcs.SetResult(null);
+
+ // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle.
+ lock (callbackHandleInitLock)
+ {
+ // ReSharper disable once PossibleNullReferenceException
+ // ReSharper disable once AccessToModifiedClosure
+ callbackHandle.Unregister(null);
+ }
+ },
+ /*state:*/ null,
+ /*millisecondsTimeOutInterval:*/ millisecondsTimeout,
+ /*executeOnlyOnce:*/ true);
+ }
+
+ return tcs.Task;
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs
index 52962879d4..258c5adef9 100644
--- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs
+++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs
@@ -581,15 +581,11 @@ namespace Umbraco.Tests.Scheduling
var waitHandle = new ManualResetEvent(false);
using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions()))
{
- runner.TaskCompleted += (sender, args) => runCount++;
- runner.TaskStarting += async (sender, args) =>
+ runner.TaskCompleted += (sender, args) =>
{
- //wait for each task to finish once it's started
- await sender.CurrentThreadingTask;
+ runCount++;
if (runCount > 3)
- {
waitHandle.Set();
- }
};
var task = new MyRecurringTask(runner, 200, 500);
@@ -604,7 +600,10 @@ namespace Umbraco.Tests.Scheduling
Assert.GreaterOrEqual(runCount, 4);
// stops recurring
- runner.Shutdown(false, false);
+ runner.Shutdown(false, true);
+
+ // check that task has been disposed (timer has been killed, etc)
+ Assert.IsTrue(task.IsDisposed);
}
}
@@ -651,15 +650,12 @@ namespace Umbraco.Tests.Scheduling
var waitHandle = new ManualResetEvent(false);
using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions()))
{
- runner.TaskCompleted += (sender, args) => runCount++;
- runner.TaskStarting += async (sender, args) =>
+ runner.TaskCompleted += (sender, args) =>
{
- //wait for each task to finish once it's started
- await sender.CurrentThreadingTask;
+ runCount++;
if (runCount > 3)
- {
waitHandle.Set();
- }
+
};
var task = new MyDelayedRecurringTask(runner, 2000, 1000);
@@ -783,37 +779,31 @@ namespace Umbraco.Tests.Scheduling
}
}
- private class MyDelayedRecurringTask : DelayedRecurringTaskBase
+ private class MyDelayedRecurringTask : RecurringTaskBase
{
public bool HasRun { get; private set; }
- public MyDelayedRecurringTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds)
+ public MyDelayedRecurringTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds)
: base(runner, delayMilliseconds, periodMilliseconds)
{ }
- private MyDelayedRecurringTask(MyDelayedRecurringTask source)
- : base(source)
- { }
-
public override bool IsAsync
{
get { return false; }
}
- public override void PerformRun()
+ public override bool PerformRun()
{
HasRun = true;
+ return true; // repeat
}
- public override Task PerformRunAsync()
+ public override Task PerformRunAsync(CancellationToken token)
{
throw new NotImplementedException();
}
- protected override MyDelayedRecurringTask GetRecurring()
- {
- return new MyDelayedRecurringTask(this);
- }
+ public override bool RunsOnShutdown { get { return true; } }
}
private class MyDelayedTask : ILatchedBackgroundTask
@@ -867,29 +857,23 @@ namespace Umbraco.Tests.Scheduling
{ }
}
- private class MyRecurringTask : RecurringTaskBase
+ private class MyRecurringTask : RecurringTaskBase
{
private readonly int _runMilliseconds;
-
- public MyRecurringTask(IBackgroundTaskRunner runner, int runMilliseconds, int periodMilliseconds)
- : base(runner, periodMilliseconds)
+ public MyRecurringTask(IBackgroundTaskRunner runner, int runMilliseconds, int periodMilliseconds)
+ : base(runner, 0, periodMilliseconds)
{
_runMilliseconds = runMilliseconds;
}
- private MyRecurringTask(MyRecurringTask source, int runMilliseconds)
- : base(source)
- {
- _runMilliseconds = runMilliseconds;
- }
-
- public override void PerformRun()
+ public override bool PerformRun()
{
Thread.Sleep(_runMilliseconds);
+ return true; // repeat
}
- public override Task PerformRunAsync()
+ public override Task PerformRunAsync(CancellationToken token)
{
throw new NotImplementedException();
}
@@ -899,10 +883,7 @@ namespace Umbraco.Tests.Scheduling
get { return false; }
}
- protected override MyRecurringTask GetRecurring()
- {
- return new MyRecurringTask(this, _runMilliseconds);
- }
+ public override bool RunsOnShutdown { get { return false; } }
}
private class MyTask : BaseTask
diff --git a/src/Umbraco.Tests/ServerEnvironmentHelperTests.cs b/src/Umbraco.Tests/ServerEnvironmentHelperTests.cs
index 237c6cb17d..39f8b71eb3 100644
--- a/src/Umbraco.Tests/ServerEnvironmentHelperTests.cs
+++ b/src/Umbraco.Tests/ServerEnvironmentHelperTests.cs
@@ -1,92 +1,98 @@
using System.Configuration;
+using System.IO;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Profiling;
using Umbraco.Core.Sync;
+using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests
{
[TestFixture]
public class ServerEnvironmentHelperTests
{
+
+ // note: in tests, read appContext._umbracoApplicationUrl and not the property,
+ // because reading the property does run some code, as long as the field is null.
+
[Test]
- public void Get_Base_Url_Single_Server_Orig_Request_Url_No_SSL()
+ public void SetApplicationUrlWhenNoSettings()
{
var appContext = new ApplicationContext(null)
{
- OriginalRequestUrl = "test.com"
+ UmbracoApplicationUrl = null // NOT set
};
- ConfigurationManager.AppSettings.Set("umbracoUseSSL", "false");
+ ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); // does not make a diff here
- var result = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(
- appContext,
- Mock.Of(
- section =>
- section.DistributedCall == Mock.Of(callSection => callSection.Servers == Enumerable.Empty())
- && section.ScheduledTasks == Mock.Of()));
-
-
- Assert.AreEqual("http://test.com/", result);
- }
-
- [Test]
- public void Get_Base_Url_Single_Server_Orig_Request_Url_With_SSL()
- {
- var appContext = new ApplicationContext(null)
- {
- OriginalRequestUrl = "test.com"
- };
-
- ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true");
-
- var result = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(
- appContext,
+ ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appContext,
Mock.Of(
section =>
section.DistributedCall == Mock.Of(callSection => callSection.Servers == Enumerable.Empty())
+ && section.WebRouting == Mock.Of(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of()));
- Assert.AreEqual("https://test.com/", result);
+ // still NOT set
+ Assert.IsNull(appContext._umbracoApplicationUrl);
}
[Test]
- public void Get_Base_Url_Single_Server_Via_Config_Url_No_SSL()
+ public void SetApplicationUrlFromDcSettingsNoSsl()
{
var appContext = new ApplicationContext(null);
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "false");
- var result = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(
- appContext,
+ ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appContext,
Mock.Of(
section =>
section.DistributedCall == Mock.Of(callSection => callSection.Servers == Enumerable.Empty())
- && section.ScheduledTasks == Mock.Of(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world")));
+ && section.WebRouting == Mock.Of(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
+ && section.ScheduledTasks == Mock.Of(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world/")));
- Assert.AreEqual("http://mycoolhost.com/hello/world/", result);
+ Assert.AreEqual("http://mycoolhost.com/hello/world", appContext._umbracoApplicationUrl);
}
[Test]
- public void Get_Base_Url_Single_Server_Via_Config_Url_With_SSL()
+ public void SetApplicationUrlFromDcSettingsSsl()
{
var appContext = new ApplicationContext(null);
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true");
- var result = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(
- appContext,
+ ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appContext,
Mock.Of(
section =>
section.DistributedCall == Mock.Of(callSection => callSection.Servers == Enumerable.Empty())
+ && section.WebRouting == Mock.Of(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world")));
- Assert.AreEqual("https://mycoolhost.com/hello/world/", result);
+ Assert.AreEqual("https://mycoolhost.com/hello/world", appContext._umbracoApplicationUrl);
+ }
+
+ [Test]
+ public void SetApplicationUrlFromWrSettingsSsl()
+ {
+ var appContext = new ApplicationContext(null);
+
+ ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); // does not make a diff here
+
+ ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appContext,
+ Mock.Of(
+ section =>
+ section.DistributedCall == Mock.Of(callSection => callSection.Servers == Enumerable.Empty())
+ && section.WebRouting == Mock.Of(wrSection => wrSection.UmbracoApplicationUrl == "httpx://whatever.com/hello/world/")
+ && section.ScheduledTasks == Mock.Of(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world")));
+
+
+ Assert.AreEqual("httpx://whatever.com/hello/world", appContext._umbracoApplicationUrl);
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 18b9b8d006..10bbdaadc2 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -58,9 +58,9 @@
..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll
-
+ False
- ..\packages\Examine.0.1.63.0\lib\Examine.dll
+ ..\packages\Examine.0.1.65.0\lib\Examine.dllFalse
diff --git a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs
index 18343a96f5..d01b298bb4 100644
--- a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs
+++ b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs
@@ -14,7 +14,7 @@ namespace Umbraco.Tests.UmbracoExamine
public void Events_Ignoring_Node()
{
//change the parent id so that they are all ignored
- var existingCriteria = ((IndexCriteria)_indexer.IndexerData);
+ var existingCriteria = _indexer.IndexerData;
_indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
999); //change to 999
diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs
index 77a9fd2dba..a5418649fe 100644
--- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs
+++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs
@@ -53,7 +53,7 @@ namespace Umbraco.Tests.UmbracoExamine
public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID()
{
//change parent id to 1116
- var existingCriteria = ((IndexCriteria)_indexer.IndexerData);
+ var existingCriteria = _indexer.IndexerData;
_indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
1116);
@@ -110,7 +110,7 @@ namespace Umbraco.Tests.UmbracoExamine
_indexer.ReIndexNode(node, IndexTypes.Media);
//change the parent node id to be the one it used to exist under
- var existingCriteria = ((IndexCriteria)_indexer.IndexerData);
+ var existingCriteria = _indexer.IndexerData;
_indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
2222);
diff --git a/src/Umbraco.Tests/UriExtensionsTests.cs b/src/Umbraco.Tests/UriExtensionsTests.cs
index 1b0ae96621..4ea8e8875a 100644
--- a/src/Umbraco.Tests/UriExtensionsTests.cs
+++ b/src/Umbraco.Tests/UriExtensionsTests.cs
@@ -4,12 +4,19 @@ using System.Linq;
using System.Text;
using NUnit.Framework;
using Umbraco.Core;
+using Umbraco.Core.IO;
namespace Umbraco.Tests
{
[TestFixture]
public class UriExtensionsTests
{
+ [TearDown]
+ public void TearDown()
+ {
+ SystemDirectories.Root = "";
+ }
+
[TestCase("http://www.domain.com/umbraco", "", true)]
[TestCase("http://www.domain.com/Umbraco/", "", true)]
[TestCase("http://www.domain.com/umbraco/default.aspx", "", true)]
@@ -35,6 +42,8 @@ namespace Umbraco.Tests
[TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)]
public void Is_Back_Office_Request(string input, string virtualPath, bool expected)
{
+ SystemDirectories.Root = virtualPath;
+
var source = new Uri(input);
Assert.AreEqual(expected, source.IsBackOfficeRequest(virtualPath));
}
diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config
index 58d314c3b1..a919949f56 100644
--- a/src/Umbraco.Tests/packages.config
+++ b/src/Umbraco.Tests/packages.config
@@ -2,25 +2,25 @@
-
-
+
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json
index e8ad94653f..d6682b0435 100644
--- a/src/Umbraco.Web.UI.Client/bower.json
+++ b/src/Umbraco.Web.UI.Client/bower.json
@@ -22,9 +22,13 @@
"jquery": "2.0.3",
"jquery-file-upload": "~9.4.0",
"jquery-ui": "1.10.3",
- "angular-dynamic-locale": "~0.1.27"
+ "angular-dynamic-locale": "~0.1.27",
+ "bootstrap-tabdrop": "~1.0.0"
},
"exportsOverride": {
+ "bootstrap-tabdrop": {
+ "": "build/js/bootstrap-tabdrop.min.js"
+ },
"rgrove-lazyload": {
"": "lazyload.js"
},
@@ -38,7 +42,7 @@
"": "tmhDynamicLocale.min.{js,js.map}"
},
"jquery": {
- "": "jquery.min.{js,map}"
+ "": "jquery.min.{js,map}"
},
"jquery-file-upload": {
"": "**/jquery.{fileupload,fileupload-process,fileupload-angular,fileupload-image}.js"
@@ -53,9 +57,9 @@
"blueimp-tmpl": {
"ignore": "*.ignore"
},
-
+
"blueimp-canvas-to-blob": {
"ignore": "*.ignore"
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js
index ef636d2ada..a5961f126c 100644
--- a/src/Umbraco.Web.UI.Client/gruntFile.js
+++ b/src/Umbraco.Web.UI.Client/gruntFile.js
@@ -418,7 +418,7 @@ module.exports = function (grunt) {
//this is the same as 'byComponent', however we will not allow
// folders with '.' in them since the grunt copy task does not like that
var componentWithoutPeriod = component.replace(".", "-");
- return path.join(componentWithoutPeriod, type);
+ return path.join(componentWithoutPeriod, type);
}
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js
index b1cca01d44..15a1a09c05 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js
@@ -40,6 +40,8 @@ angular.module("umbraco.directives")
}
});
+
+ $('.nav-pills, .nav-tabs').tabdrop();
}
scope.showTabs = iAttrs.tabs ? true : false;
diff --git a/src/Umbraco.Web.UI.Client/src/less/grid.less b/src/Umbraco.Web.UI.Client/src/less/grid.less
index 22ea5d9836..8e1557fea1 100644
--- a/src/Umbraco.Web.UI.Client/src/less/grid.less
+++ b/src/Umbraco.Web.UI.Client/src/less/grid.less
@@ -176,3 +176,70 @@ body {
.emptySection #contentwrapper {left: 80px;}
.emptySection #speechbubble {left: 0;}
.emptySection #navigation {display: none}
+
+@media (max-width: 767px) {
+
+ // make leftcolumn smaller on tablets
+ #leftcolumn {
+ width: 60px;
+ }
+ #applications ul.sections {
+ width: 60px;
+ }
+ ul.sections.sections-tray {
+ width: 60px;
+ }
+ #applications-tray {
+ left: 60px;
+ }
+ #navigation {
+ left: 60px;
+ }
+ #contentwrapper, #contentcolumn {
+ left: 30px;
+ }
+ #umbracoMainPageBody .umb-modal-left.fade.in {
+ margin-left: 60px;
+ }
+}
+
+
+
+@media (max-width: 500px) {
+
+ // make leftcolumn smaller on mobiles
+ #leftcolumn {
+ width: 40px;
+ }
+ #applications ul.sections {
+ width: 40px;
+ }
+ ul.sections li [class^="icon-"]:before {
+ font-size: 25px!important;
+ }
+ #applications ul.sections li.avatar a img {
+ width: 25px;
+ }
+ ul.sections a span {
+ display:none !important;
+ }
+ #applications ul.sections-tray {
+ width: 40px;
+ }
+ ul.sections.sections-tray {
+ width: 40px;
+ }
+ #applications-tray {
+ left: 40px;
+ }
+ #navigation {
+ left: 40px;
+ }
+ #contentwrapper, #contentcolumn {
+ left: 20px;
+ }
+ #umbracoMainPageBody .umb-modal-left.fade.in {
+ margin-left: 40px;
+ width: 85%!important;
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less
index 2a6abef6d7..ca61d872d3 100644
--- a/src/Umbraco.Web.UI.Client/src/less/panel.less
+++ b/src/Umbraco.Web.UI.Client/src/less/panel.less
@@ -1,19 +1,27 @@
// Panel
// -------------------------
-.umb-panel{
- background: white;
- position: absolute;
- top: 0px; bottom: 0px; left: 0px; right: 0px;}
+.umb-panel {
+ background: white;
+ position: absolute;
+ top: 0px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+}
+
+.umb-panel-nobody {
+ padding-top: 100px;
+ overflow: auto;
+}
-.umb-panel-nobody{padding-top: 100px; overflow: auto;}
.umb-panel-header {
- background: @grayLighter;
- border-bottom: 1px solid @grayLight;
- position: absolute;
- height: 99px;
- top: 0px;
- right: 0px;
- left: 0px;
+ background: @grayLighter;
+ border-bottom: 1px solid @grayLight;
+ position: absolute;
+ height: 99px;
+ top: 0px;
+ right: 0px;
+ left: 0px;
}
.umb-panel-body {
@@ -25,6 +33,7 @@
clear: both;
overflow: auto;
}
+
.umb-panel-body.no-header {
top: 20px;
}
@@ -38,71 +47,83 @@
}
.umb-panel-header .umb-headline, .umb-panel-header h1 {
- font-size: 16px;
- border: none;
- background: none;
- margin: 15px 0 0 20px;
- padding: 3px 5px;
- line-height: 1.4;
- height: auto;
- width: 100%;
- border: 1px solid @grayLighter;
+ font-size: 16px;
+ border: none;
+ background: none;
+ margin: 15px 0 0 20px;
+ padding: 3px 5px;
+ line-height: 1.4;
+ height: auto;
+ width: 100%;
+ border: 1px solid @grayLighter;
}
-.umb-panel-header .umb-headline:focus,.umb-panel-header .umb-headline:active {
- border: 1px solid @grayLight;
- background-color: @white;
+.umb-panel-header .umb-headline:focus, .umb-panel-header .umb-headline:active {
+ border: 1px solid @grayLight;
+ background-color: @white;
}
-.umb-headline-editor-wrapper{
- position: relative;
+.umb-headline-editor-wrapper {
+ position: relative;
}
-.umb-headline-editor-wrapper .help-inline{
- right: 0px;
- top: 25px;
- position: absolute;
- font-size: 10px;
- color: @red;
- }
+.umb-headline-editor-wrapper .help-inline {
+ right: 0px;
+ top: 25px;
+ position: absolute;
+ font-size: 10px;
+ color: @red;
+}
-.umb-panel-header .umb-nav-tabs{
- bottom: -1px;
- position: absolute;
- padding: 0px 0px 0px 20px;
+.umb-panel-header .umb-nav-tabs {
+ bottom: -1px;
+ position: absolute;
+ padding: 0px 0px 0px 20px;
}
.umb-panel-header p {
- margin:0px 20px;
+ margin: 0px 20px;
}
-.umb-btn-toolbar .dimmed, .umb-dimmed{
- opacity: 0.6;
+.umb-btn-toolbar .dimmed, .umb-dimmed {
+ opacity: 0.6;
}
-.umb-headline-editor-wrapper input {
- background: none;
- border: none;
- margin: -6px 0 0 0;
- padding: 0 0 2px 0;
- border-radius: 0;
- line-height: normal;
- border: 1px solid transparent;
- color: @black;
- letter-spacing: -0.01em
-}
-.umb-headline-editor-wrapper input.ng-invalid {
- color: @red;
+.umb-headline-editor-wrapper input {
+ background: none;
+ border: none;
+ margin: -6px 0 0 0;
+ padding: 0 0 2px 0;
+ border-radius: 0;
+ line-height: normal;
+ border: 1px solid transparent;
+ color: @black;
+ letter-spacing: -0.01em;
}
-.umb-headline-editor-wrapper input.ng-invalid::-webkit-input-placeholder {color: @red; line-height: 22px;}
-.umb-headline-editor-wrapper input.ng-invalid::-moz-placeholder {color: @red; line-height: 22px;}
-.umb-headline-editor-wrapper input.ng-invalid:-ms-input-placeholder {color: @red; line-height: 22px;}
+.umb-headline-editor-wrapper input.ng-invalid {
+ color: @red;
+}
+
+.umb-headline-editor-wrapper input.ng-invalid::-webkit-input-placeholder {
+ color: @red;
+ line-height: 22px;
+}
+
+.umb-headline-editor-wrapper input.ng-invalid::-moz-placeholder {
+ color: @red;
+ line-height: 22px;
+}
+
+.umb-headline-editor-wrapper input.ng-invalid:-ms-input-placeholder {
+ color: @red;
+ line-height: 22px;
+}
.umb-panel-header i {
- font-size: 13px;
- vertical-align: middle;
+ font-size: 13px;
+ vertical-align: middle;
}
.umb-panel-header-meta {
@@ -115,65 +136,76 @@
}
.umb-panel-footer {
- margin: 0;
- padding: 20px;
- z-index: 999;
- position: absolute;
- bottom: 0px;
- left: 0px;
- right: 0px;
+ margin: 0;
+ padding: 20px;
+ z-index: 999;
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
}
/* Publish */
.umb-btn-toolbar .dropdown-menu {
- right: 0;
- left: auto;
- border-radius: @tabsBorderRadius;
- box-shadow: none;
- padding: 0
- }
+ right: 0;
+ left: auto;
+ border-radius: @tabsBorderRadius;
+ box-shadow: none;
+ padding: 0;
+}
- .umb-btn-toolbar .dropdown-menu small {
- background: #fef9db;
- display: block;
- padding: 10px 20px;
- }
+.umb-btn-toolbar .dropdown-menu small {
+ background: #fef9db;
+ display: block;
+ padding: 10px 20px;
+}
- .umb-btn-toolbar .dropdown-menu .btn {
+.umb-btn-toolbar .dropdown-menu .btn {
margin: 20px 29px;
- width: 80px
- }
+ width: 80px;
+}
/* tab buttons */
-.umb-bottom-bar{
+.umb-bottom-bar {
background: white;
-webkit-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1);
- -moz-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1);
- box-shadow: 0px -18px 20px rgba(255, 255, 255, 1);
+ -moz-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1);
+ box-shadow: 0px -18px 20px rgba(255, 255, 255, 1);
border-top: 1px solid @grayLighter;
padding: 10px 0 10px 0;
position: fixed;
- bottom: 0px;
+ bottom: 0;
left: 100px;
+ right: 40px;
z-index: 6010;
-}
-.umb-tab-buttons{
- padding-left: 0px;
-}
-
-@media (min-width: 1101px) {
- .umb-bottom-bar {
+ @media (min-width: 1101px) {
left: 460px;
- }
+ }
}
-.umb-tab-pane{padding-bottom: 90px}
+.umb-tab-buttons {
+ padding-left: 0;
-.tab-content{overflow: visible; }
+ > .btn-group:not([style*="display:none"]):not([style*="display: none"]) {
+ margin-left: 0;
+ }
-.umb-panel-footer-nav{
+ @media (min-width: 768px) {
+ padding-left: 180px;
+ }
+}
+
+.umb-tab-pane {
+ padding-bottom: 90px;
+}
+
+.tab-content {
+ overflow: visible;
+}
+
+.umb-panel-footer-nav {
position: absolute;
bottom: 0px;
height: 30px;
@@ -187,93 +219,117 @@
}
.umb-panel-footer-nav li a {
- border-radius: 0;
- display: block;
- float: left;
- height: 30px;
- background: @grayLighter;
- text-align: center;
- padding: 8px 0px 8px 30px;
- position: relative;
- margin: 0 1px 0 0;
- text-decoration: none;
- color: @gray;
- font-size: 11px;
+ border-radius: 0;
+ display: block;
+ float: left;
+ height: 30px;
+ background: @grayLighter;
+ text-align: center;
+ padding: 8px 0px 8px 30px;
+ position: relative;
+ margin: 0 1px 0 0;
+ text-decoration: none;
+ color: @gray;
+ font-size: 11px;
}
.umb-panel-footer-nav li a:after {
- content: "";
- border-top: 16px solid transparent;
- border-bottom: 16px solid transparent;
- border-left: 16px solid @grayLighter;
- position: absolute; right: -16px; top: 0;
- z-index: 1;
-}
-
-.umb-panel-footer-nav li a:before {
- content: "";
- border-top: 16px solid transparent;
- border-bottom: 16px solid transparent;
- border-left: 16px solid @grayLight;
- position: absolute; left: 0; top: 0;
+ content: "";
+ border-top: 16px solid transparent;
+ border-bottom: 16px solid transparent;
+ border-left: 16px solid @grayLighter;
+ position: absolute;
+ right: -16px;
+ top: 0;
+ z-index: 1;
}
-.umb-panel-footer-nav li:first-child a{
- padding-left: 20px;
+.umb-panel-footer-nav li a:before {
+ content: "";
+ border-top: 16px solid transparent;
+ border-bottom: 16px solid transparent;
+ border-left: 16px solid @grayLight;
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+
+.umb-panel-footer-nav li:first-child a {
+ padding-left: 20px;
}
.umb-panel-footer-nav li:first-child a:before {
- display: none;
+ display: none;
}
.umb-panel-footer-nav li:last-child a:after {
- display: none;
+ display: none;
}
- // Utility classes
+// Utility classes
- //SD: Had to add these because if we want to use the bootstrap text colors
- // in a panel/editor they'll all show up as white on white - so we need to use the
- // form styles
+//SD: Had to add these because if we want to use the bootstrap text colors
+// in a panel/editor they'll all show up as white on white - so we need to use the
+// form styles
.umb-dialog .muted,
-.umb-panel .muted { color: @grayLight; }
+.umb-panel .muted {
+ color: @grayLight;
+}
.umb-dialog a.muted:hover,
.umb-dialog a.muted:focus,
.umb-panel a.muted:hover,
-.umb-panel a.muted:focus { color: darken(@grayLight, 10%); }
+.umb-panel a.muted:focus {
+ color: darken(@grayLight, 10%);
+}
.umb-dialog .text-warning,
-.umb-panel .text-warning { color: @formWarningText; }
+.umb-panel .text-warning {
+ color: @formWarningText;
+}
.umb-dialog a.text-warning:hover,
.umb-dialog a.text-warning:focus,
.umb-panel a.text-warning:hover,
-.umb-panel a.text-warning:focus { color: darken(@formWarningText, 10%); }
+.umb-panel a.text-warning:focus {
+ color: darken(@formWarningText, 10%);
+}
.umb-dialog .text-error,
-.umb-panel .text-error { color: @formErrorText; }
+.umb-panel .text-error {
+ color: @formErrorText;
+}
.umb-dialog a.text-error:hover,
.umb-dialog a.text-error:focus,
.umb-panel a.text-error:hover,
-.umb-panel a.text-error:focus { color: darken(@formErrorText, 10%); }
+.umb-panel a.text-error:focus {
+ color: darken(@formErrorText, 10%);
+}
.umb-dialog .text-info,
-.umb-panel .text-info { color: @formInfoText; }
+.umb-panel .text-info {
+ color: @formInfoText;
+}
.umb-dialog a.text-info:hover,
.umb-dialog a.text-info:focus,
.umb-panel a.text-info:hover,
-.umb-panel a.text-info:focus { color: darken(@formInfoText, 10%); }
+.umb-panel a.text-info:focus {
+ color: darken(@formInfoText, 10%);
+}
.umb-dialog .text-success,
-.umb-panel .text-success { color: @formSuccessText; }
+.umb-panel .text-success {
+ color: @formSuccessText;
+}
.umb-dialog a.text-success:hover,
.umb-dialog a.text-success:focus,
.umb-panel a.text-success:hover,
-.umb-panel a.text-success:focus { color: darken(@formSuccessText, 10%); }
\ No newline at end of file
+.umb-panel a.text-success:focus {
+ color: darken(@formSuccessText, 10%);
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
index f34d45be63..e06a45771b 100644
--- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less
+++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
@@ -522,3 +522,5 @@ ul.color-picker li a {
.bootstrap-datetimepicker-widget .btn{padding: 0;}
.bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;}
.umb-datepicker .input-append .add-on{cursor: pointer;}
+.umb-datepicker p {margin-top:10px;}
+.umb-datepicker p a{color: @gray;}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/loader.js b/src/Umbraco.Web.UI.Client/src/loader.js
index 32def61618..179de16de8 100644
--- a/src/Umbraco.Web.UI.Client/src/loader.js
+++ b/src/Umbraco.Web.UI.Client/src/loader.js
@@ -1,28 +1,32 @@
LazyLoad.js(
[
'lib/jquery/jquery.min.js',
- 'lib/jquery-ui/jquery-ui.min.js',
-
- /* 1.1.5 */
'lib/angular/1.1.5/angular.min.js',
+ 'lib/underscore/underscore-min.js',
+
+ 'lib/jquery-ui/jquery-ui.min.js',
+
'lib/angular/1.1.5/angular-cookies.min.js',
- 'lib/angular/1.1.5/angular-mobile.min.js',
- 'lib/angular/1.1.5/angular-mocks.js',
+ 'lib/angular/1.1.5/angular-mobile.js',
'lib/angular/1.1.5/angular-sanitize.min.js',
'lib/angular/angular-ui-sortable.js',
'lib/angular-dynamic-locale/tmhDynamicLocale.min.js',
- /* App-wide file-upload helper */
+ 'lib/blueimp-load-image/load-image.all.min.js',
'lib/jquery-file-upload/jquery.fileupload.js',
'lib/jquery-file-upload/jquery.fileupload-process.js',
+ 'lib/jquery-file-upload/jquery.fileupload-image.js',
'lib/jquery-file-upload/jquery.fileupload-angular.js',
-
+
'lib/bootstrap/js/bootstrap.2.3.2.min.js',
- 'lib/underscore/underscore-min.js',
- 'lib/umbraco/Extensions.js',
+ 'lib/bootstrap-tabdrop/bootstrap-tabdrop.min.js',
+ 'lib/umbraco/Extensions.js',
+
'lib/umbraco/NamespaceManager.js',
+ 'lib/umbraco/LegacyUmbClientMgr.js',
+ 'lib/umbraco/LegacySpeechBubble.js',
'js/umbraco.servervariables.js',
'js/app.dev.js',
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js
index eb49bc3b39..64a22d4c54 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js
@@ -18,7 +18,8 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
//map the user config
$scope.model.config = angular.extend(config, $scope.model.config);
- $scope.datetimePickerValue = $scope.model.value;
+ $scope.hasDatetimePickerValue = $scope.model.value ? true : false;
+ $scope.datetimePickerValue = null;
//hide picker if clicking on the document
$scope.hidePicker = function () {
@@ -35,14 +36,20 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
function applyDate(e) {
angularHelper.safeApply($scope, function() {
// when a date is changed, update the model
- if (e.date) {
- if ($scope.model.config.pickTime) {
- $scope.model.value = e.date.format("YYYY-MM-DD HH:mm:ss");
+ if (e.date && e.date.isValid()) {
+ $scope.datePickerForm.datepicker.$setValidity("pickerError", true);
+ $scope.hasDatetimePickerValue = true;
+ if (!$scope.model.config.format) {
+ $scope.datetimePickerValue = e.date;
}
else {
- $scope.model.value = e.date.format("YYYY-MM-DD");
+ $scope.datetimePickerValue = e.date.format($scope.model.config.format);
}
}
+ else {
+ $scope.hasDatetimePickerValue = false;
+ $scope.datetimePickerValue = null;
+ }
if (!$scope.model.config.pickTime) {
$element.find("div:first").datetimepicker("hide", 0);
@@ -50,6 +57,15 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
});
}
+ var picker = null;
+
+ $scope.clearDate = function() {
+ $scope.hasDatetimePickerValue = false;
+ $scope.datetimePickerValue = null;
+ $scope.model.value = null;
+ $scope.datePickerForm.datepicker.$setValidity("pickerError", true);
+ }
+
//get the current user to see if we can localize this picker
userService.getCurrentUser().then(function (user) {
@@ -69,25 +85,39 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
// Get the id of the datepicker button that was clicked
var pickerId = $scope.model.alias;
- // Open the datepicker and add a changeDate eventlistener
- $element.find("div:first")
- .datetimepicker($scope.model.config)
- .on("dp.change", applyDate);
+ var element = $element.find("div:first");
- //manually assign the date to the plugin
- if (!$scope.model.config.format) {
- $element.find("div:first").datetimepicker("setValue", $scope.model.value ? $scope.model.value : null);
- }
- else {
- $element.find("div:first").datetimepicker("setValue", $scope.model.value ? new Date($scope.model.value) : null);
- if ($scope.model.value && $scope.model.config.format) {
- $scope.datetimePickerValue = moment($scope.model.value).format($scope.model.config.format);
- }
- }
+ // Open the datepicker and add a changeDate eventlistener
+ element
+ .datetimepicker(angular.extend({ useCurrent: true }, $scope.model.config))
+ .on("dp.change", applyDate)
+ .on("dp.error", function(a, b, c) {
+ $scope.hasDatetimePickerValue = false;
+ $scope.datePickerForm.datepicker.$setValidity("pickerError", false);
+ });
+
+ if ($scope.hasDatetimePickerValue) {
+
+ //assign value to plugin/picker
+ element.datetimepicker("setValue", $scope.model.value ? new Date($scope.model.value) : moment());
+
+ if (!$scope.model.config.format) {
+ $scope.datetimePickerValue = moment($scope.model.value);
+ }
+ else {
+ $scope.datetimePickerValue = moment($scope.model.value).format($scope.model.config.format);
+ }
+ }
+
+ element.find("input").bind("blur", function() {
+ //we need to force an apply here
+ $scope.$apply();
+ });
//Ensure to remove the event handler when this instance is destroyted
- $scope.$on('$destroy', function () {
- $element.find("div:first").datetimepicker("destroy");
+ $scope.$on('$destroy', function () {
+ element.find("input").unbind("blur");
+ element.datetimepicker("destroy");
});
});
});
@@ -95,9 +125,24 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
});
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ if ($scope.hasDatetimePickerValue) {
+ if ($scope.model.config.pickTime) {
+ $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD HH:mm:ss");
+ }
+ else {
+ $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD");
+ }
+ }
+ else {
+ $scope.model.value = null;
+ }
+ });
+
//unbind doc click event!
$scope.$on('$destroy', function () {
$(document).unbind("click", $scope.hidePicker);
+ unsubscribe();
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html
index b3e503eb4a..898afe88a3 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html
@@ -1,14 +1,26 @@
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index b84764cd0d..a24bca7b17 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -47,6 +47,7 @@
..\truetrue
+ bin\
@@ -114,9 +115,9 @@
False..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll
-
- False
- ..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll
+
+ ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll
+ TrueFalse
@@ -126,9 +127,9 @@
False..\packages\dotless.1.4.1.0\lib\dotless.Core.dll
-
- ..\packages\Examine.0.1.63.0\lib\Examine.dll
- True
+
+ False
+ ..\packages\Examine.0.1.65.0\lib\Examine.dllFalse
@@ -2540,9 +2541,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\"
TrueTrue
- 7260
+ 7270/
- http://localhost:7260
+ http://localhost:7270FalseFalse
diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config
index 1c16a6e029..0f6c04a6b7 100644
--- a/src/Umbraco.Web.UI/config/ClientDependency.config
+++ b/src/Umbraco.Web.UI/config/ClientDependency.config
@@ -10,7 +10,7 @@ NOTES:
* Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config
* A new version will invalidate both client and server cache and create new persisted files
-->
-
+
+ internalRedirectPreservesTemplate="false" disableAlternativeTemplates="false" disableFindContentByIdPath="false"
+ umbracoApplicationUrl="">
diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config
index 255dce6542..546ecf42fe 100644
--- a/src/Umbraco.Web.UI/config/umbracoSettings.config
+++ b/src/Umbraco.Web.UI/config/umbracoSettings.config
@@ -276,10 +276,15 @@
By default you can call any content Id in the url and show the content with that id, for example:
http://mysite.com/1092 or http://mysite.com/1092.aspx would render the content with id 1092. Setting
this setting to true stops that behavior
+ @umbracoApplicationUrl
+ The url of the Umbraco application. By default, Umbraco will figure it out from the first request.
+ Configure it here if you need anything specific. Needs to be a complete url with scheme and umbraco
+ path, eg http://mysite.com/umbraco. NOT just "mysite.com" or "mysite.com/umbraco" or "http://mysite.com".
-->
-
+
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config
index 4a754f6758..c6d0752e12 100644
--- a/src/Umbraco.Web.UI/packages.config
+++ b/src/Umbraco.Web.UI/packages.config
@@ -1,28 +1,28 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs
index 8655acd9a7..a3be346758 100644
--- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs
+++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs
@@ -14,6 +14,7 @@ using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.member;
using System.Linq;
using umbraco.cms.businesslogic.web;
+using Umbraco.Core.Logging;
using Umbraco.Core.Publishing;
using Content = Umbraco.Core.Models.Content;
using ApplicationTree = Umbraco.Core.Models.ApplicationTree;
@@ -30,7 +31,9 @@ namespace Umbraco.Web.Cache
public class CacheRefresherEventHandler : ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
- {
+ {
+ LogHelper.Info("Initializing Umbraco internal event handlers for cache refreshing");
+
//bind to application tree events
ApplicationTreeService.Deleted += ApplicationTreeDeleted;
ApplicationTreeService.Updated += ApplicationTreeUpdated;
diff --git a/src/Umbraco.Web/Install/FilePermissionHelper.cs b/src/Umbraco.Web/Install/FilePermissionHelper.cs
index 013f40fc0e..b5bc908fde 100644
--- a/src/Umbraco.Web/Install/FilePermissionHelper.cs
+++ b/src/Umbraco.Web/Install/FilePermissionHelper.cs
@@ -98,7 +98,7 @@ namespace Umbraco.Web.Install
// that and we might get lock issues.
try
{
- var xmlFile = content.Instance.UmbracoXmlDiskCacheFileName + ".tmp";
+ var xmlFile = content.GetUmbracoXmlDiskFileName() + ".tmp";
SaveAndDeleteFile(xmlFile);
return true;
}
diff --git a/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs
index cbd2e0e519..dc8aa15f6a 100644
--- a/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs
+++ b/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Net.Http.Headers;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
@@ -36,22 +37,33 @@ namespace Umbraco.Web.Mvc
return _applicationContext ?? ApplicationContext.Current;
}
+ public const string AuthorizationType = "AToken";
+
///
- /// Used to return the value that needs to go in the Authorization header
+ /// Used to return the full value that needs to go in the Authorization header
///
///
///
public static string GetAuthHeaderTokenVal(ApplicationContext appContext)
{
- var admin = appContext.Services.UserService.GetUserById(0);
+ return string.Format("{0} {1}", AuthorizationType, GetAuthHeaderVal(appContext));
+ }
+ public static AuthenticationHeaderValue GetAuthenticationHeaderValue(ApplicationContext appContext)
+ {
+ return new AuthenticationHeaderValue(AuthorizationType, GetAuthHeaderVal(appContext));
+ }
+
+ private static string GetAuthHeaderVal(ApplicationContext appContext)
+ {
+ var admin = appContext.Services.UserService.GetUserById(0);
var token = string.Format("{0}u____u{1}u____u{2}", admin.Email, admin.Username, admin.RawPasswordValue);
var encrypted = token.EncryptWithMachineKey();
var bytes = Encoding.UTF8.GetBytes(encrypted);
var base64 = Convert.ToBase64String(bytes);
- return "AToken val=\"" + base64 + "\"";
+ return string.Format("val=\"{0}\"", base64);
}
///
diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs
index accdc660d7..870d65c4e1 100644
--- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs
+++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs
@@ -17,11 +17,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
/// if multiple threads are performing publishing tasks that the file will be persisted in accordance with the final resulting
/// xml structure since the file writes are queued.
///
- internal class XmlCacheFilePersister : ILatchedBackgroundTask
+ internal class XmlCacheFilePersister : LatchedBackgroundTaskBase
{
private readonly IBackgroundTaskRunner _runner;
private readonly content _content;
- private readonly ManualResetEventSlim _latch = new ManualResetEventSlim(false);
private readonly object _locko = new object();
private bool _released;
private Timer _timer;
@@ -38,7 +37,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
private const int MaxWaitMilliseconds = 30000; // save the cache after some time (ie no more than 30s of changes)
// save the cache when the app goes down
- public bool RunsOnShutdown { get { return true; } }
+ public override bool RunsOnShutdown { get { return _timer != null; } }
// initialize the first instance, which is inactive (not touched yet)
public XmlCacheFilePersister(IBackgroundTaskRunner runner, content content)
@@ -141,28 +140,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
lock (_locko)
{
LogHelper.Debug("Timer: release.");
- if (_timer != null)
- _timer.Dispose();
- _timer = null;
_released = true;
- // if running (because of shutdown) this will have no effect
- // else it tells the runner it is time to run the task
- _latch.Set();
+ Release();
}
}
- public WaitHandle Latch
- {
- get { return _latch.WaitHandle; }
- }
-
- public bool IsLatched
- {
- get { return true; }
- }
-
- public async Task RunAsync(CancellationToken token)
+ public override async Task RunAsync(CancellationToken token)
{
lock (_locko)
{
@@ -181,15 +165,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
}
- public bool IsAsync
+ public override bool IsAsync
{
get { return true; }
}
- public void Dispose()
- { }
-
- public void Run()
+ public override void Run()
{
lock (_locko)
{
@@ -203,5 +184,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
_content.SaveXmlToFile();
}
}
+
+ protected override void DisposeResources()
+ {
+ base.DisposeResources();
+
+ // stop the timer
+ if (_timer == null) return;
+ _timer.Change(Timeout.Infinite, Timeout.Infinite);
+ _timer.Dispose();
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs
index 575845efba..9612ede0b9 100644
--- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs
+++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs
@@ -90,7 +90,8 @@ namespace Umbraco.Web.Scheduling
_options = options;
_logPrefix = "[" + name + "] ";
- HostingEnvironment.RegisterObject(this);
+ if (options.Hosted)
+ HostingEnvironment.RegisterObject(this);
if (options.AutoStart)
StartUp();
@@ -385,6 +386,7 @@ namespace Umbraco.Web.Scheduling
// still latched & not running on shutdown = stop here
if (dbgTask.IsLatched && dbgTask.RunsOnShutdown == false)
{
+ dbgTask.Dispose(); // will not run
TaskSourceCompleted(taskSource, token);
return;
}
@@ -442,7 +444,7 @@ namespace Umbraco.Web.Scheduling
try
{
- using (bgTask) // ensure it's disposed
+ try
{
if (bgTask.IsAsync)
//configure await = false since we don't care about the context, we're on a background thread.
@@ -450,6 +452,12 @@ namespace Umbraco.Web.Scheduling
else
bgTask.Run();
}
+ finally // ensure we disposed - unless latched (again)
+ {
+ var lbgTask = bgTask as ILatchedBackgroundTask;
+ if (lbgTask == null || lbgTask.IsLatched == false)
+ bgTask.Dispose();
+ }
}
catch (Exception e)
{
diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs
index 4688ff37d6..55df42d3b7 100644
--- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs
+++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs
@@ -15,6 +15,8 @@ namespace Umbraco.Web.Scheduling
LongRunning = false;
KeepAlive = false;
AutoStart = false;
+ PreserveRunningTask = false;
+ Hosted = true;
}
///
@@ -36,9 +38,16 @@ namespace Umbraco.Web.Scheduling
public bool AutoStart { get; set; }
///
- /// Gets or setes a value indicating whether the running task should be preserved
+ /// Gets or sets a value indicating whether the running task should be preserved
/// once completed, or reset to null. For unit tests.
///
public bool PreserveRunningTask { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the runner should register with (and be
+ /// stopped by) the hosting. Otherwise, something else should take care of stopping
+ /// the runner. True by default.
+ ///
+ public bool Hosted { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Scheduling/DelayedRecurringTaskBase.cs b/src/Umbraco.Web/Scheduling/DelayedRecurringTaskBase.cs
deleted file mode 100644
index af3dedbe70..0000000000
--- a/src/Umbraco.Web/Scheduling/DelayedRecurringTaskBase.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Umbraco.Web.Scheduling
-{
- ///
- /// Provides a base class for recurring background tasks.
- ///
- /// The type of the managed tasks.
- internal abstract class DelayedRecurringTaskBase : RecurringTaskBase, ILatchedBackgroundTask
- where T : class, IBackgroundTask
- {
- private readonly ManualResetEventSlim _latch;
- private Timer _timer;
-
- protected DelayedRecurringTaskBase(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds)
- : base(runner, periodMilliseconds)
- {
- if (delayMilliseconds > 0)
- {
- _latch = new ManualResetEventSlim(false);
- _timer = new Timer(_ =>
- {
- _timer.Dispose();
- _timer = null;
- _latch.Set();
- });
- _timer.Change(delayMilliseconds, 0);
- }
- }
-
- protected DelayedRecurringTaskBase(DelayedRecurringTaskBase source)
- : base(source)
- {
- // no latch on recurring instances
- _latch = null;
- }
-
- public override void Run()
- {
- if (_latch != null)
- _latch.Dispose();
- base.Run();
- }
-
- public override async Task RunAsync(CancellationToken token)
- {
- if (_latch != null)
- _latch.Dispose();
- await base.RunAsync(token);
- }
-
- public WaitHandle Latch
- {
- get
- {
- if (_latch == null)
- throw new InvalidOperationException("The task is not latched.");
- return _latch.WaitHandle;
- }
- }
-
- public bool IsLatched
- {
- get { return _latch != null && _latch.IsSet == false; }
- }
-
- public virtual bool RunsOnShutdown
- {
- get { return true; }
- }
- }
-}
diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs
index c1b43b57c6..d11bf2af35 100644
--- a/src/Umbraco.Web/Scheduling/KeepAlive.cs
+++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs
@@ -1,47 +1,83 @@
using System;
using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
using Umbraco.Core;
-using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
-using Umbraco.Core.Sync;
namespace Umbraco.Web.Scheduling
{
- internal class KeepAlive
+ internal class KeepAlive : RecurringTaskBase
{
- public static void Start(ApplicationContext appContext, IUmbracoSettingsSection settings)
+ private readonly ApplicationContext _appContext;
+
+ public KeepAlive(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds,
+ ApplicationContext appContext)
+ : base(runner, delayMilliseconds, periodMilliseconds)
{
+ _appContext = appContext;
+ }
+
+ public override bool PerformRun()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override async Task PerformRunAsync(CancellationToken token)
+ {
+ if (_appContext == null) return true; // repeat...
+
+ // ensure we do not run if not main domain, but do NOT lock it
+ if (_appContext.MainDom.IsMainDom == false)
+ {
+ LogHelper.Debug("Does not run if not MainDom.");
+ return false; // do NOT repeat, going down
+ }
+
using (DisposableTimer.DebugDuration(() => "Keep alive executing", () => "Keep alive complete"))
- {
- var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(
- appContext,
- settings);
+ {
+ string umbracoAppUrl = null;
- if (string.IsNullOrWhiteSpace(umbracoBaseUrl))
+ try
{
- LogHelper.Warn("No url for service (yet), skip.");
- }
- else
- {
- var url = string.Format("{0}ping.aspx", umbracoBaseUrl.EnsureEndsWith('/'));
-
- try
+ umbracoAppUrl = _appContext.UmbracoApplicationUrl;
+ if (umbracoAppUrl.IsNullOrWhiteSpace())
{
- using (var wc = new WebClient())
+ LogHelper.Warn("No url for service (yet), skip.");
+ return true; // repeat
+ }
+
+ var url = umbracoAppUrl + "/ping.aspx";
+ using (var wc = new HttpClient())
+ {
+ var request = new HttpRequestMessage()
{
- wc.DownloadString(url);
- }
- }
- catch (Exception ee)
- {
- LogHelper.Error(
- 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);
+ RequestUri = new Uri(url),
+ Method = HttpMethod.Get
+ };
+
+ var result = await wc.SendAsync(request, token);
}
}
+ catch (Exception e)
+ {
+ LogHelper.Error(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e);
+ }
}
-
+
+ return true; // repeat
+ }
+
+ public override bool IsAsync
+ {
+ get { return true; }
+ }
+
+ public override bool RunsOnShutdown
+ {
+ get { return false; }
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs b/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs
new file mode 100644
index 0000000000..3315fa7c34
--- /dev/null
+++ b/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Umbraco.Core;
+
+namespace Umbraco.Web.Scheduling
+{
+ internal abstract class LatchedBackgroundTaskBase : DisposableObject, ILatchedBackgroundTask
+ {
+ private readonly ManualResetEventSlim _latch;
+
+ protected LatchedBackgroundTaskBase()
+ {
+ _latch = new ManualResetEventSlim(false);
+ }
+
+ ///
+ /// Implements IBackgroundTask.Run().
+ ///
+ public abstract void Run();
+
+ ///
+ /// Implements IBackgroundTask.RunAsync().
+ ///
+ public abstract Task RunAsync(CancellationToken token);
+
+ ///
+ /// Indicates whether the background task can run asynchronously.
+ ///
+ public abstract bool IsAsync { get; }
+
+ public WaitHandle Latch
+ {
+ get { return _latch.WaitHandle; }
+ }
+
+ public bool IsLatched
+ {
+ get { return _latch.IsSet == false; }
+ }
+
+ protected void Release()
+ {
+ _latch.Set();
+ }
+
+ protected void Reset()
+ {
+ _latch.Reset();
+ }
+
+ public abstract bool RunsOnShutdown { get; }
+
+ // the task is going to be disposed after execution,
+ // unless it is latched again, thus indicating it wants to
+ // remain active
+
+ protected override void DisposeResources()
+ {
+ _latch.Dispose();
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs
index a9f70a612e..14a7ea3211 100644
--- a/src/Umbraco.Web/Scheduling/LogScrubber.cs
+++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Caching;
@@ -6,15 +7,16 @@ using umbraco.BusinessLogic;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
+using Umbraco.Core.Sync;
namespace Umbraco.Web.Scheduling
{
- internal class LogScrubber : DelayedRecurringTaskBase
+ internal class LogScrubber : RecurringTaskBase
{
private readonly ApplicationContext _appContext;
private readonly IUmbracoSettingsSection _settings;
- public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds,
+ public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds,
ApplicationContext appContext, IUmbracoSettingsSection settings)
: base(runner, delayMilliseconds, periodMilliseconds)
{
@@ -22,19 +24,7 @@ namespace Umbraco.Web.Scheduling
_settings = settings;
}
- public LogScrubber(LogScrubber source)
- : base(source)
- {
- _appContext = source._appContext;
- _settings = source._settings;
- }
-
- protected override LogScrubber GetRecurring()
- {
- return new LogScrubber(this);
- }
-
- private int GetLogScrubbingMaximumAge(IUmbracoSettingsSection settings)
+ private static int GetLogScrubbingMaximumAge(IUmbracoSettingsSection settings)
{
int maximumAge = 24 * 60 * 60;
try
@@ -44,7 +34,7 @@ namespace Umbraco.Web.Scheduling
}
catch (Exception e)
{
- LogHelper.Error("Unable to locate a log scrubbing maximum age. Defaulting to 24 horus", e);
+ LogHelper.Error("Unable to locate a log scrubbing maximum age. Defaulting to 24 hours.", e);
}
return maximumAge;
@@ -65,15 +55,32 @@ namespace Umbraco.Web.Scheduling
return interval;
}
- public override void PerformRun()
+ public override bool PerformRun()
{
- using (DisposableTimer.DebugDuration(() => "Log scrubbing executing", () => "Log scrubbing complete"))
+ if (_appContext == null) return true; // repeat...
+
+ if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
+ {
+ LogHelper.Debug("Does not run on slave servers.");
+ return false; // do NOT repeat, server status comes from config and will NOT change
+ }
+
+ // ensure we do not run if not main domain, but do NOT lock it
+ if (_appContext.MainDom.IsMainDom == false)
+ {
+ LogHelper.Debug("Does not run if not MainDom.");
+ return false; // do NOT repeat, going down
+ }
+
+ using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete"))
{
Log.CleanLogs(GetLogScrubbingMaximumAge(_settings));
- }
+ }
+
+ return true; // repeat
}
- public override Task PerformRunAsync()
+ public override Task PerformRunAsync(CancellationToken token)
{
throw new NotImplementedException();
}
diff --git a/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs
index d710a70e03..567f85f1f5 100644
--- a/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs
+++ b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs
@@ -6,57 +6,51 @@ namespace Umbraco.Web.Scheduling
///
/// Provides a base class for recurring background tasks.
///
- /// The type of the managed tasks.
- internal abstract class RecurringTaskBase : IBackgroundTask
- where T : class, IBackgroundTask
+ internal abstract class RecurringTaskBase : LatchedBackgroundTaskBase
{
- private readonly IBackgroundTaskRunner _runner;
+ private readonly IBackgroundTaskRunner _runner;
private readonly int _periodMilliseconds;
- private Timer _timer;
- private T _recurrent;
+ private readonly Timer _timer;
+ private bool _disposed;
///
- /// Initializes a new instance of the class with a tasks runner and a period.
+ /// Initializes a new instance of the class.
///
/// The task runner.
+ /// The delay.
/// The period.
/// The task will repeat itself periodically. Use this constructor to create a new task.
- protected RecurringTaskBase(IBackgroundTaskRunner runner, int periodMilliseconds)
+ protected RecurringTaskBase(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds)
{
_runner = runner;
_periodMilliseconds = periodMilliseconds;
- }
- ///
- /// Initializes a new instance of the class with a source task.
- ///
- /// The source task.
- /// Use this constructor to create a new task from a source task in GetRecurring.
- protected RecurringTaskBase(RecurringTaskBase source)
- {
- _runner = source._runner;
- _timer = source._timer;
- _periodMilliseconds = source._periodMilliseconds;
+ // note
+ // must use the single-parameter constructor on Timer to avoid it from being GC'd
+ // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
+
+ _timer = new Timer(_ => Release());
+ _timer.Change(delayMilliseconds, 0);
}
///
/// Implements IBackgroundTask.Run().
///
/// Classes inheriting from RecurringTaskBase must implement PerformRun.
- public virtual void Run()
+ public override void Run()
{
- PerformRun();
- Repeat();
+ var shouldRepeat = PerformRun();
+ if (shouldRepeat) Repeat();
}
///
/// Implements IBackgroundTask.RunAsync().
///
/// Classes inheriting from RecurringTaskBase must implement PerformRun.
- public virtual async Task RunAsync(CancellationToken token)
+ public override async Task RunAsync(CancellationToken token)
{
- await PerformRunAsync();
- Repeat();
+ var shouldRepeat = await PerformRunAsync(token);
+ if (shouldRepeat) Repeat();
}
private void Repeat()
@@ -64,52 +58,39 @@ namespace Umbraco.Web.Scheduling
// again?
if (_runner.IsCompleted) return; // fail fast
- if (_periodMilliseconds == 0) return;
+ if (_periodMilliseconds == 0) return; // safe
- _recurrent = GetRecurring();
- if (_recurrent == null)
- {
- _timer.Dispose();
- _timer = null;
- return; // done
- }
+ Reset(); // re-latch
- // note
- // must use the single-parameter constructor on Timer to avoid it from being GC'd
- // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
-
- _timer = _timer ?? new Timer(_ => _runner.TryAdd(_recurrent));
- _timer.Change(_periodMilliseconds, 0);
+ // try to add again (may fail if runner has completed)
+ // if added, re-start the timer, else kill it
+ if (_runner.TryAdd(this))
+ _timer.Change(_periodMilliseconds, 0);
+ else
+ Dispose(true);
}
- ///
- /// Indicates whether the background task can run asynchronously.
- ///
- public abstract bool IsAsync { get; }
-
///
/// Runs the background task.
///
- public abstract void PerformRun();
+ /// A value indicating whether to repeat the task.
+ public abstract bool PerformRun();
///
/// Runs the task asynchronously.
///
- /// A instance representing the execution of the background task.
- public abstract Task PerformRunAsync();
+ /// A cancellation token.
+ /// A instance representing the execution of the background task,
+ /// and returning a value indicating whether to repeat the task.
+ public abstract Task PerformRunAsync(CancellationToken token);
- ///
- /// Gets a new occurence of the recurring task.
- ///
- /// A new task instance to be queued, or null to terminate the recurring task.
- /// The new task instance must be created via the RecurringTaskBase(RecurringTaskBase{T} source) constructor,
- /// where source is the current task, eg: return new MyTask(this);
- protected abstract T GetRecurring();
+ protected override void DisposeResources()
+ {
+ base.DisposeResources();
- ///
- /// Dispose the task.
- ///
- public virtual void Dispose()
- { }
+ // stop the timer
+ _timer.Change(Timeout.Infinite, Timeout.Infinite);
+ _timer.Dispose();
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
index 9db21fba8a..11cffb3648 100644
--- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
+++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
@@ -1,25 +1,21 @@
using System;
-using System.Diagnostics;
-using System.Net;
-using System.Text;
+using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
-using Umbraco.Core.Publishing;
using Umbraco.Core.Sync;
using Umbraco.Web.Mvc;
namespace Umbraco.Web.Scheduling
{
- internal class ScheduledPublishing : DelayedRecurringTaskBase
+ internal class ScheduledPublishing : RecurringTaskBase
{
private readonly ApplicationContext _appContext;
private readonly IUmbracoSettingsSection _settings;
- private static bool _isPublishingRunning;
-
- public ScheduledPublishing(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds,
+ public ScheduledPublishing(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds,
ApplicationContext appContext, IUmbracoSettingsSection settings)
: base(runner, delayMilliseconds, periodMilliseconds)
{
@@ -27,75 +23,68 @@ namespace Umbraco.Web.Scheduling
_settings = settings;
}
- private ScheduledPublishing(ScheduledPublishing source)
- : base(source)
- {
- _appContext = source._appContext;
- _settings = source._settings;
- }
-
- protected override ScheduledPublishing GetRecurring()
- {
- return new ScheduledPublishing(this);
- }
-
- public override void PerformRun()
- {
- if (_appContext == null) return;
- if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
- {
- LogHelper.Debug("Does not run on slave servers.");
- return;
- }
-
- using (DisposableTimer.DebugDuration(() => "Scheduled publishing executing", () => "Scheduled publishing complete"))
- {
- if (_isPublishingRunning) return;
-
- _isPublishingRunning = true;
-
- var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(_appContext, _settings);
-
- try
- {
-
- if (string.IsNullOrWhiteSpace(umbracoBaseUrl))
- {
- LogHelper.Warn("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));
-
- var result = wc.UploadString(url, "");
- }
- }
- }
- catch (Exception ee)
- {
- LogHelper.Error(
- 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);
- }
- finally
- {
- _isPublishingRunning = false;
- }
- }
- }
-
- public override Task PerformRunAsync()
+ public override bool PerformRun()
{
throw new NotImplementedException();
}
+ public override async Task PerformRunAsync(CancellationToken token)
+ {
+ if (_appContext == null) return true; // repeat...
+
+ if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
+ {
+ LogHelper.Debug("Does not run on slave servers.");
+ return false; // do NOT repeat, server status comes from config and will NOT change
+ }
+
+ // ensure we do not run if not main domain, but do NOT lock it
+ if (_appContext.MainDom.IsMainDom == false)
+ {
+ LogHelper.Debug("Does not run if not MainDom.");
+ return false; // do NOT repeat, going down
+ }
+
+ using (DisposableTimer.DebugDuration(() => "Scheduled publishing executing", () => "Scheduled publishing complete"))
+ {
+ string umbracoAppUrl = null;
+
+ try
+ {
+ umbracoAppUrl = _appContext.UmbracoApplicationUrl;
+ if (umbracoAppUrl.IsNullOrWhiteSpace())
+ {
+ LogHelper.Warn("No url for service (yet), skip.");
+ return true; // repeat
+ }
+
+ var url = umbracoAppUrl + "/RestServices/ScheduledPublish/Index";
+ using (var wc = new HttpClient())
+ {
+ var request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri(url),
+ Method = HttpMethod.Post,
+ Content = new StringContent(string.Empty)
+ };
+ //pass custom the authorization header
+ request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext);
+
+ var result = await wc.SendAsync(request, token);
+ }
+ }
+ catch (Exception e)
+ {
+ LogHelper.Error(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e);
+ }
+ }
+
+ return true; // repeat
+ }
+
public override bool IsAsync
{
- get { return false; }
+ get { return true; }
}
public override bool RunsOnShutdown
diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs
index cba3cb4fc8..def7b606b5 100644
--- a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs
+++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs
@@ -1,15 +1,13 @@
using System;
using System.Collections;
-using System.Linq;
using System.Net;
+using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
-using Umbraco.Core.Configuration;
+using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
-using Umbraco.Core.Publishing;
using Umbraco.Core.Sync;
-using Umbraco.Core;
namespace Umbraco.Web.Scheduling
{
@@ -17,14 +15,13 @@ 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 : DelayedRecurringTaskBase
+ internal class ScheduledTasks : RecurringTaskBase
{
private readonly ApplicationContext _appContext;
private readonly IUmbracoSettingsSection _settings;
private static readonly Hashtable ScheduledTaskTimes = new Hashtable();
- private static bool _isPublishingRunning = false;
- public ScheduledTasks(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds,
+ public ScheduledTasks(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds,
ApplicationContext appContext, IUmbracoSettingsSection settings)
: base(runner, delayMilliseconds, periodMilliseconds)
{
@@ -32,30 +29,19 @@ namespace Umbraco.Web.Scheduling
_settings = settings;
}
- public ScheduledTasks(ScheduledTasks source)
- : base(source)
- {
- _appContext = source._appContext;
- _settings = source._settings;
- }
-
- protected override ScheduledTasks GetRecurring()
- {
- return new ScheduledTasks(this);
- }
-
- private void ProcessTasks()
+ private async Task ProcessTasksAsync(CancellationToken token)
{
var scheduledTasks = _settings.ScheduledTasks.Tasks;
foreach (var t in scheduledTasks)
{
var runTask = false;
- if (!ScheduledTaskTimes.ContainsKey(t.Alias))
+ if (ScheduledTaskTimes.ContainsKey(t.Alias) == false)
{
runTask = true;
ScheduledTaskTimes.Add(t.Alias, DateTime.Now);
}
- /// Add 1 second to timespan to compensate for differencies in timer
+
+ // Add 1 second to timespan to compensate for differencies in timer
else if (
new TimeSpan(
DateTime.Now.Ticks - ((DateTime)ScheduledTaskTimes[t.Alias]).Ticks).TotalSeconds + 1 >= t.Interval)
@@ -66,69 +52,80 @@ namespace Umbraco.Web.Scheduling
if (runTask)
{
- bool taskResult = GetTaskByHttp(t.Url);
+ var taskResult = await GetTaskByHttpAync(t.Url, token);
if (t.Log)
LogHelper.Info(string.Format("{0} has been called with response: {1}", t.Alias, taskResult));
}
}
}
- private bool GetTaskByHttp(string url)
+ private async Task GetTaskByHttpAync(string url, CancellationToken token)
{
- var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
-
- try
+ using (var wc = new HttpClient())
{
- using (var response = (HttpWebResponse)myHttpWebRequest.GetResponse())
+ var request = new HttpRequestMessage()
{
- return response.StatusCode == HttpStatusCode.OK;
- }
- }
- catch (Exception ex)
- {
- LogHelper.Error("An error occurred calling web task for url: " + url, ex);
- }
+ RequestUri = new Uri(url),
+ Method = HttpMethod.Get,
+ Content = new StringContent(string.Empty)
+ };
- return false;
+ //TODO: pass custom the authorization header, currently these aren't really secured!
+ //request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext);
+
+ try
+ {
+ var result = await wc.SendAsync(request, token);
+ return result.StatusCode == HttpStatusCode.OK;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Error("An error occurred calling web task for url: " + url, ex);
+ }
+ return false;
+ }
}
- public override void PerformRun()
+ public override bool PerformRun()
{
+ throw new NotImplementedException();
+ }
+
+ public override async Task PerformRunAsync(CancellationToken token)
+ {
+ if (_appContext == null) return true; // repeat...
+
if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
{
LogHelper.Debug("Does not run on slave servers.");
- return;
+ return false; // do NOT repeat, server status comes from config and will NOT change
+ }
+
+ // ensure we do not run if not main domain, but do NOT lock it
+ if (_appContext.MainDom.IsMainDom == false)
+ {
+ LogHelper.Debug("Does not run if not MainDom.");
+ return false; // do NOT repeat, going down
}
using (DisposableTimer.DebugDuration(() => "Scheduled tasks executing", () => "Scheduled tasks complete"))
{
- if (_isPublishingRunning) return;
-
- _isPublishingRunning = true;
-
try
{
- ProcessTasks();
+ await ProcessTasksAsync(token);
}
catch (Exception ee)
{
LogHelper.Error("Error executing scheduled task", ee);
}
- finally
- {
- _isPublishingRunning = false;
- }
}
- }
- public override Task PerformRunAsync()
- {
- throw new NotImplementedException();
+ return true; // repeat
}
public override bool IsAsync
{
- get { return false; }
+ get { return true; }
}
public override bool RunsOnShutdown
diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs
index 409a67028f..8831a81838 100644
--- a/src/Umbraco.Web/Scheduling/Scheduler.cs
+++ b/src/Umbraco.Web/Scheduling/Scheduler.cs
@@ -1,11 +1,7 @@
-using System;
-using System.Threading;
-using System.Web;
+using System.Web;
using Umbraco.Core;
using Umbraco.Core.Configuration;
-using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
-using Umbraco.Core.Sync;
namespace Umbraco.Web.Scheduling
{
@@ -18,7 +14,7 @@ namespace Umbraco.Web.Scheduling
///
internal sealed class Scheduler : ApplicationEventHandler
{
- private static Timer _pingTimer;
+ private static BackgroundTaskRunner _keepAliveRunner;
private static BackgroundTaskRunner _publishingRunner;
private static BackgroundTaskRunner _tasksRunner;
private static BackgroundTaskRunner _scrubberRunner;
@@ -48,30 +44,24 @@ namespace Umbraco.Web.Scheduling
LogHelper.Debug(() => "Initializing the scheduler");
// backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly
+ _keepAliveRunner = new BackgroundTaskRunner("KeepAlive");
_publishingRunner = new BackgroundTaskRunner("ScheduledPublishing");
_tasksRunner = new BackgroundTaskRunner("ScheduledTasks");
_scrubberRunner = new BackgroundTaskRunner("LogScrubber");
var settings = UmbracoConfig.For.UmbracoSettings();
- // note
- // must use the single-parameter constructor on Timer to avoid it from being GC'd
- // also make the timer static to ensure further GC safety
- // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
-
- // ping/keepalive - no need for a background runner - does not need to be web aware, ok if the app domain dies
- _pingTimer = new Timer(state => KeepAlive.Start(applicationContext, UmbracoConfig.For.UmbracoSettings()));
- _pingTimer.Change(60000, 300000);
+ // ping/keepalive
+ // on all servers
+ _keepAliveRunner.Add(new KeepAlive(_keepAliveRunner, 60000, 300000, applicationContext));
// scheduled publishing/unpublishing
// install on all, will only run on non-slaves servers
- // both are delayed recurring tasks
_publishingRunner.Add(new ScheduledPublishing(_publishingRunner, 60000, 60000, applicationContext, settings));
_tasksRunner.Add(new ScheduledTasks(_tasksRunner, 60000, 60000, applicationContext, settings));
// log scrubbing
- // install & run on all servers
- // LogScrubber is a delayed recurring task
+ // install on all, will only run on non-slaves servers
_scrubberRunner.Add(new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings), applicationContext, settings));
}
}
diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs
index f066476948..918a2c1542 100644
--- a/src/Umbraco.Web/Security/MembershipHelper.cs
+++ b/src/Umbraco.Web/Security/MembershipHelper.cs
@@ -105,9 +105,7 @@ namespace Umbraco.Web.Security
.Where(p => member.ContentType.PropertyTypeExists(p.Alias))
.Where(property => member.Properties.Contains(property.Alias))
//needs to be editable
- .Where(p => member.ContentType.MemberCanEditProperty(p.Alias))
- //needs to have a value
- .Where(p => p.Value != null))
+ .Where(p => member.ContentType.MemberCanEditProperty(p.Alias)))
{
member.Properties[property.Alias].Value = property.Value;
}
diff --git a/src/Umbraco.Web/Templates/TemplateRenderer.cs b/src/Umbraco.Web/Templates/TemplateRenderer.cs
index 22d6b5b54b..d7d331d887 100644
--- a/src/Umbraco.Web/Templates/TemplateRenderer.cs
+++ b/src/Umbraco.Web/Templates/TemplateRenderer.cs
@@ -81,9 +81,9 @@ namespace Umbraco.Web.Templates
//set the doc that was found by id
contentRequest.PublishedContent = doc;
//set the template, either based on the AltTemplate found or the standard template of the doc
- contentRequest.TemplateModel = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates || AltTemplate.HasValue == false
- ? ApplicationContext.Current.Services.FileService.GetTemplate(doc.TemplateId)
- : ApplicationContext.Current.Services.FileService.GetTemplate(AltTemplate.Value);
+ contentRequest.TemplateModel = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates || AltTemplate.HasValue == false
+ ? _umbracoContext.Application.Services.FileService.GetTemplate(doc.TemplateId)
+ : _umbracoContext.Application.Services.FileService.GetTemplate(AltTemplate.Value);
//if there is not template then exit
if (!contentRequest.HasTemplate)
@@ -103,14 +103,20 @@ namespace Umbraco.Web.Templates
//after this page has rendered.
SaveExistingItems();
- //set the new items on context objects for this templates execution
- SetNewItemsOnContextObjects(contentRequest);
+ try
+ {
+ //set the new items on context objects for this templates execution
+ SetNewItemsOnContextObjects(contentRequest);
- //Render the template
- ExecuteTemplateRendering(writer, contentRequest);
-
- //restore items on context objects to continuing rendering the parent template
- RestoreItems();
+ //Render the template
+ ExecuteTemplateRendering(writer, contentRequest);
+ }
+ finally
+ {
+ //restore items on context objects to continuing rendering the parent template
+ RestoreItems();
+ }
+
}
private void ExecuteTemplateRendering(TextWriter sw, PublishedContentRequest contentRequest)
diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js
index 3ff9884ba7..3ae74ba08b 100644
--- a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js
+++ b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js
@@ -20,6 +20,7 @@
'lib/jquery-file-upload/jquery.fileupload-angular.js',
'lib/bootstrap/js/bootstrap.2.3.2.min.js',
+ 'lib/bootstrap-tabdrop/bootstrap-tabdrop.min.js',
'lib/umbraco/Extensions.js',
'lib/umbraco/NamespaceManager.js',
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index d6ed99c4cd..be9cc0881a 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -104,9 +104,9 @@
..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll
-
- False
- ..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll
+
+ ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll
+ TrueFalse
@@ -115,9 +115,9 @@
..\packages\dotless.1.4.1.0\lib\dotless.Core.dll
-
+ False
- ..\packages\Examine.0.1.63.0\lib\Examine.dll
+ ..\packages\Examine.0.1.65.0\lib\Examine.dllFalse
@@ -268,6 +268,7 @@
+
@@ -500,7 +501,6 @@
-
diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs
index 6efe4d4d6a..6f89864d9e 100644
--- a/src/Umbraco.Web/UmbracoModule.cs
+++ b/src/Umbraco.Web/UmbracoModule.cs
@@ -36,37 +36,46 @@ namespace Umbraco.Web
{
#region HttpModule event handlers
+ private static void EnsureApplicationUrl(HttpRequestBase request)
+ {
+ var appctx = ApplicationContext.Current;
+
+ // already initialized = ok
+ // note that getting ApplicationUrl will ALSO try the various settings
+ if (appctx.UmbracoApplicationUrl.IsNullOrWhiteSpace() == false) return;
+
+ // so if we reach that point, nothing was configured
+ // use the current request as application url
+
+ // if (HTTP and SSL not required) or (HTTPS and SSL required),
+ // use ports from request
+ // otherwise,
+ // if non-standard ports used,
+ // user may need to set umbracoApplicationUrl manually per
+ // http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks
+ var port = (request.IsSecureConnection == false && GlobalSettings.UseSSL == false)
+ || (request.IsSecureConnection && GlobalSettings.UseSSL)
+ ? ":" + request.ServerVariables["SERVER_PORT"]
+ : "";
+
+ var ssl = GlobalSettings.UseSSL ? "s" : ""; // force, whatever the first request
+ var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco);
+
+ appctx.UmbracoApplicationUrl = UriUtility.TrimPathEndSlash(url);
+ LogHelper.Info("ApplicationUrl: " + appctx.UmbracoApplicationUrl + " (UmbracoModule request)");
+ }
+
+
///
/// Begins to process a request.
///
///
static void BeginRequest(HttpContextBase httpContext)
{
+ // ensure application url is initialized
+ EnsureApplicationUrl(httpContext.Request);
- //we need to set the initial url in our ApplicationContext, this is so our keep alive service works and this must
- //exist on a global context because the keep alive service doesn't run in a web context.
- //we are NOT going to put a lock on this because locking will slow down the application and we don't really care
- //if two threads write to this at the exact same time during first page hit.
- //see: http://issues.umbraco.org/issue/U4-2059
- if (ApplicationContext.Current.OriginalRequestUrl.IsNullOrWhiteSpace())
- {
- // If (HTTP and SSL not required) or (HTTPS and SSL required), use ports from request to configure OriginalRequestUrl.
- // Otherwise, user may need to set baseUrl manually per http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks if non-standard ports used.
- if ((!httpContext.Request.IsSecureConnection && !GlobalSettings.UseSSL) || (httpContext.Request.IsSecureConnection && GlobalSettings.UseSSL))
- {
- // Use port from request.
- ApplicationContext.Current.OriginalRequestUrl = string.Format("{0}:{1}{2}", httpContext.Request.ServerVariables["SERVER_NAME"], httpContext.Request.ServerVariables["SERVER_PORT"], IOHelper.ResolveUrl(SystemDirectories.Umbraco));
- }
- else
- {
- // Omit port entirely.
- ApplicationContext.Current.OriginalRequestUrl = string.Format("{0}{1}", httpContext.Request.ServerVariables["SERVER_NAME"], IOHelper.ResolveUrl(SystemDirectories.Umbraco));
- }
-
- LogHelper.Info("Setting OriginalRequestUrl: " + ApplicationContext.Current.OriginalRequestUrl);
- }
-
- // do not process if client-side request
+ // do not process if client-side request
if (httpContext.Request.Url.IsClientSideRequest())
return;
diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config
index 315d780852..72a521895e 100644
--- a/src/Umbraco.Web/packages.config
+++ b/src/Umbraco.Web/packages.config
@@ -1,24 +1,24 @@
-
+
-
-
+
+
-
-
-
+
+
+
-
+
-
-
+
+
\ No newline at end of file
diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs
index ff08f36207..7673a9d75d 100644
--- a/src/Umbraco.Web/umbraco.presentation/content.cs
+++ b/src/Umbraco.Web/umbraco.presentation/content.cs
@@ -1,31 +1,30 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
-using System.Web.Hosting;
using System.Xml;
+using umbraco.BusinessLogic;
+using umbraco.cms.businesslogic;
+using umbraco.cms.businesslogic.web;
+using umbraco.DataLayer;
+using umbraco.presentation.nodeFactory;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
-using umbraco.BusinessLogic;
-using umbraco.cms.businesslogic;
-using umbraco.cms.businesslogic.web;
using Umbraco.Core.Models;
using Umbraco.Core.Profiling;
-using umbraco.DataLayer;
-using umbraco.presentation.nodeFactory;
using Umbraco.Web;
using Umbraco.Web.PublishedCache.XmlPublishedCache;
using Umbraco.Web.Scheduling;
-using Node = umbraco.NodeFactory.Node;
using File = System.IO.File;
+using Node = umbraco.NodeFactory.Node;
+using Task = System.Threading.Tasks.Task;
namespace umbraco
{
@@ -36,58 +35,44 @@ namespace umbraco
{
private XmlCacheFilePersister _persisterTask;
+ private volatile bool _released;
+
#region Constructors
private content()
{
if (SyncToXmlFile)
{
- // if we write to file, prepare the lock
- // (if we don't use the file, or just read from it, no need to lock)
- InitializeFileLock();
-
- // and prepare the persister task
+ // prepare the persister task
// there's always be one task keeping a ref to the runner
// so it's safe to just create it as a local var here
var runner = new BackgroundTaskRunner("XmlCacheFilePersister", new BackgroundTaskRunnerOptions
{
LongRunning = true,
- KeepAlive = true
+ KeepAlive = true,
+ Hosted = false // main domain will take care of stopping the runner (see below)
});
- // when the runner is terminating we need to ensure that no modifications
- // to content are possible anymore, as they would not be written out to
- // the xml file - unfortunately that is not possible in 7.x because we
- // cannot lock the content service... and so we do nothing...
- //runner.Terminating += (sender, args) =>
- //{
- //};
-
- // when the runner has terminated we know we will not be writing to the file
- // anymore, so we can release the lock now - no need to wait for the AppDomain
- // unload - which means any "last minute" saves will be lost - but waiting for
- // the AppDomain to unload has issues...
- runner.Terminated += (sender, args) =>
- {
- if (_fileLock == null) return; // not locking (testing?)
- if (_fileLocked == null) return; // not locked
-
- // thread-safety
- // lock something that's readonly and not null..
- lock (_xmlFileName)
- {
- // double-check
- if (_fileLocked == null) return;
-
- LogHelper.Debug("Release file lock.");
- _fileLocked.Dispose();
- _fileLocked = null;
- _fileLock = null; // ensure we don't lock again
- }
- };
-
// create (and add to runner)
_persisterTask = new XmlCacheFilePersister(runner, this);
+
+ var registered = ApplicationContext.Current.MainDom.Register(
+ null,
+ () =>
+ {
+ // once released, the cache still works but does not write to file anymore,
+ // which is OK with database server messenger but will cause data loss with
+ // another messenger...
+
+ runner.Shutdown(false, true); // wait until flushed
+ _released = true;
+ });
+
+ // failed to become the main domain, we will never use the file
+ if (registered == false)
+ runner.Shutdown(false, true);
+
+ _released = (registered == false);
}
// initialize content - populate the cache
@@ -127,23 +112,26 @@ namespace umbraco
private static readonly object DbReadSyncLock = new object();
private const string XmlContextContentItemKey = "UmbracoXmlContextContent";
- private string _umbracoXmlDiskCacheFileName = string.Empty;
+ private static string _umbracoXmlDiskCacheFileName = string.Empty;
private volatile XmlDocument _xmlContent;
///
/// Gets the path of the umbraco XML disk cache file.
///
/// The name of the umbraco XML disk cache file.
+ public static string GetUmbracoXmlDiskFileName()
+ {
+ if (string.IsNullOrEmpty(_umbracoXmlDiskCacheFileName))
+ {
+ _umbracoXmlDiskCacheFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml);
+ }
+ return _umbracoXmlDiskCacheFileName;
+ }
+
+ [Obsolete("Use the safer static GetUmbracoXmlDiskFileName() method instead to retrieve this value")]
public string UmbracoXmlDiskCacheFileName
{
- get
- {
- if (string.IsNullOrEmpty(_umbracoXmlDiskCacheFileName))
- {
- _umbracoXmlDiskCacheFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml);
- }
- return _umbracoXmlDiskCacheFileName;
- }
+ get { return GetUmbracoXmlDiskFileName(); }
set { _umbracoXmlDiskCacheFileName = value; }
}
@@ -204,7 +192,7 @@ namespace umbraco
var node = GetPreviewOrPublishedNode(d, xmlContentCopy, false);
var attr = ((XmlElement)node).GetAttributeNode("sortOrder");
attr.Value = d.sortOrder.ToString();
- AddOrUpdateXmlNode(xmlContentCopy, d.Id, d.Level, parentId, node);
+ xmlContentCopy = GetAddOrUpdateXmlNode(xmlContentCopy, d.Id, d.Level, parentId, node);
// update sitemapprovider
if (updateSitemapProvider && SiteMap.Provider is UmbracoSiteMapProvider)
@@ -368,7 +356,7 @@ namespace umbraco
{
foreach (Document d in Documents)
{
- PublishNodeDo(d, safeXml.Xml, true);
+ safeXml.Xml = PublishNodeDo(d, safeXml.Xml, true);
}
}
@@ -390,7 +378,18 @@ namespace umbraco
public virtual void ClearDocumentCache(int documentId)
{
// Get the document
- var d = new Document(documentId);
+ Document d;
+ try
+ {
+ d = new Document(documentId);
+ }
+ catch
+ {
+ // if we need the document to remove it... this cannot be LB?!
+ // shortcut everything here
+ ClearDocumentXmlCache(documentId);
+ return;
+ }
ClearDocumentCache(d);
}
@@ -411,26 +410,8 @@ namespace umbraco
// remove from xml db cache
doc.XmlRemoveFromDB();
- // We need to lock content cache here, because we cannot allow other threads
- // making changes at the same time, they need to be queued
- using (var safeXml = GetSafeXmlReader())
- {
- // Check if node present, before cloning
- x = safeXml.Xml.GetElementById(doc.Id.ToString());
- if (x == null)
- return;
-
- safeXml.UpgradeToWriter(false);
-
- // Find the document in the xml cache
- x = safeXml.Xml.GetElementById(doc.Id.ToString());
- if (x != null)
- {
- // The document already exists in cache, so repopulate it
- x.ParentNode.RemoveChild(x);
- safeXml.Commit();
- }
- }
+ // clear xml cache
+ ClearDocumentXmlCache(doc.Id);
ClearContextCache();
@@ -446,6 +427,30 @@ namespace umbraco
}
}
+ internal void ClearDocumentXmlCache(int id)
+ {
+ // We need to lock content cache here, because we cannot allow other threads
+ // making changes at the same time, they need to be queued
+ using (var safeXml = GetSafeXmlReader())
+ {
+ // Check if node present, before cloning
+ var x = safeXml.Xml.GetElementById(id.ToString());
+ if (x == null)
+ return;
+
+ safeXml.UpgradeToWriter(false);
+
+ // Find the document in the xml cache
+ x = safeXml.Xml.GetElementById(id.ToString());
+ if (x != null)
+ {
+ // The document already exists in cache, so repopulate it
+ x.ParentNode.RemoveChild(x);
+ safeXml.Commit();
+ }
+ }
+ }
+
///
/// Unpublishes the node.
///
@@ -669,9 +674,9 @@ order by umbracoNode.level, umbracoNode.sortOrder";
{
//TODO: Should there be a try/catch here in case the file is being written to while this is trying to be executed?
- if (File.Exists(UmbracoXmlDiskCacheFileName))
+ if (File.Exists(GetUmbracoXmlDiskFileName()))
{
- return new FileInfo(UmbracoXmlDiskCacheFileName).LastWriteTimeUtc;
+ return new FileInfo(GetUmbracoXmlDiskFileName()).LastWriteTimeUtc;
}
return DateTime.MinValue;
@@ -801,7 +806,7 @@ order by umbracoNode.level, umbracoNode.sortOrder";
return xmlDoc == null ? null : (XmlDocument) xmlDoc.CloneNode(true);
}
- private static void EnsureSchema(string contentTypeAlias, XmlDocument xml)
+ private static XmlDocument EnsureSchema(string contentTypeAlias, XmlDocument xml)
{
string subset = null;
@@ -814,15 +819,22 @@ order by umbracoNode.level, umbracoNode.sortOrder";
// ensure it contains the content type
if (subset != null && subset.Contains(string.Format("", contentTypeAlias)))
- return;
+ return xml;
- // remove current doctype
- xml.RemoveChild(n);
+ // alas, that does not work, replacing a doctype is ignored and GetElementById fails
+ //
+ //// remove current doctype, set new doctype
+ //xml.RemoveChild(n);
+ //subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset);
+ //var doctype = xml.CreateDocumentType("root", null, null, subset);
+ //xml.InsertAfter(doctype, xml.FirstChild);
- // set new doctype
+ var xml2 = new XmlDocument();
subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset);
- var doctype = xml.CreateDocumentType("root", null, null, subset);
- xml.InsertAfter(doctype, xml.FirstChild);
+ var doctype = xml2.CreateDocumentType("root", null, null, subset);
+ xml2.AppendChild(doctype);
+ xml2.AppendChild(xml2.ImportNode(xml.DocumentElement, true));
+ return xml2;
}
private static void InitializeXml(XmlDocument xml, string dtd)
@@ -977,100 +989,6 @@ order by umbracoNode.level, umbracoNode.sortOrder";
private readonly string _xmlFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml);
private DateTime _lastFileRead; // last time the file was read
private DateTime _nextFileCheck; // last time we checked whether the file was changed
- private AsyncLock _fileLock; // protects the file
- private IDisposable _fileLocked; // protects the file
-
- private const int FileLockTimeoutMilliseconds = 4*60*1000; // 4'
-
- private void InitializeFileLock()
- {
- // initialize file lock
- // ApplicationId will look like "/LM/W3SVC/1/Root/AppName"
- // name is system-wide and must be less than 260 chars
- //
- // From MSDN C++ CreateSemaphore doc:
- // "The name can have a "Global\" or "Local\" prefix to explicitly create the object in
- // the global or session namespace. The remainder of the name can contain any character
- // except the backslash character (\). For more information, see Kernel Object Namespaces."
- //
- // From MSDN "Kernel object namespaces" doc:
- // "The separate client session namespaces enable multiple clients to run the same
- // applications without interfering with each other. For processes started under
- // a client session, the system uses the session namespace by default. However, these
- // processes can use the global namespace by prepending the "Global\" prefix to the object name."
- //
- // just use "default" (whatever it is) for now - ie, no prefix
- //
- var name = HostingEnvironment.ApplicationID + "/XmlStore/XmlFile";
- _fileLock = new AsyncLock(name);
-
- // the file lock works with a shared, system-wide, semaphore - and we don't want
- // to leak a count on that semaphore else the whole process will hang - so we have
- // to ensure we dispose of the locker when the domain goes down - in theory the
- // async lock should do it via its finalizer, but then there are some weird cases
- // where the semaphore has been disposed of before it's been released, and then
- // we'd need to GC-pin the semaphore... better dispose the locker explicitely
- // when the app domain unloads.
-
- if (AppDomain.CurrentDomain.IsDefaultAppDomain())
- {
- LogHelper.Debug("Registering Unload handler for default app domain.");
- AppDomain.CurrentDomain.ProcessExit += OnDomainUnloadReleaseFileLock;
- }
- else
- {
- LogHelper.Debug("Registering Unload handler for non-default app domain.");
- AppDomain.CurrentDomain.DomainUnload += OnDomainUnloadReleaseFileLock;
- }
- }
-
- private void EnsureFileLock()
- {
- if (_fileLock == null) return; // not locking (testing?)
- if (_fileLocked != null) return; // locked already
-
- // thread-safety, acquire lock only once!
- // lock something that's readonly and not null..
- lock (_xmlFileName)
- {
- // double-check
- if (_fileLock == null) return;
- if (_fileLocked != null) return;
-
- // don't hang forever, throws if it cannot lock within the timeout
- LogHelper.Debug("Acquiring exclusive access to file for this AppDomain...");
- _fileLocked = _fileLock.Lock(FileLockTimeoutMilliseconds);
- LogHelper.Debug("Acquired exclusive access to file for this AppDomain.");
- }
- }
-
- private void OnDomainUnloadReleaseFileLock(object sender, EventArgs args)
- {
- // the unload event triggers AFTER all hosted objects (eg the file persister
- // background task runner) have been stopped, so we should have released the
- // lock already - this is for safety - might be possible to get rid of it
-
- // NOTE
- // trying to write to the log via LogHelper at that point is a BAD idea
- // it can lead to ugly deadlocks with the named semaphore - DONT do it
-
- if (_fileLock == null) return; // not locking (testing?)
- if (_fileLocked == null) return; // not locked
-
- // thread-safety
- // lock something that's readonly and not null..
- lock (_xmlFileName)
- {
- // double-check
- if (_fileLocked == null) return;
-
- // in case you really need to debug... that should be safe...
- //System.IO.File.AppendAllText(HostingEnvironment.MapPath("~/App_Data/log.txt"), string.Format("{0} {1} unlock", DateTime.Now, AppDomain.CurrentDomain.Id));
- _fileLocked.Dispose();
-
- _fileLock = null; // ensure we don't lock again
- }
- }
// not used - just try to read the file
//private bool XmlFileExists
@@ -1092,7 +1010,9 @@ order by umbracoNode.level, umbracoNode.sortOrder";
}
}
- // assumes file lock
+ // invoked by XmlCacheFilePersister ONLY and that one manages the MainDom, ie it
+ // will NOT try to save once the current app domain is not the main domain anymore
+ // (no need to test _released)
internal void SaveXmlToFile()
{
LogHelper.Info("Save Xml to file...");
@@ -1102,8 +1022,6 @@ order by umbracoNode.level, umbracoNode.sortOrder";
var xml = _xmlContent; // capture (atomic + volatile), immutable anyway
if (xml == null) return;
- EnsureFileLock();
-
// delete existing file, if any
DeleteXmlFile();
@@ -1111,7 +1029,7 @@ order by umbracoNode.level, umbracoNode.sortOrder";
var directoryName = Path.GetDirectoryName(_xmlFileName);
if (directoryName == null)
throw new Exception(string.Format("Invalid XmlFileName \"{0}\".", _xmlFileName));
- if (System.IO.File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false)
+ if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false)
Directory.CreateDirectory(directoryName);
// save
@@ -1132,8 +1050,10 @@ order by umbracoNode.level, umbracoNode.sortOrder";
}
}
- // assumes file lock
- internal async System.Threading.Tasks.Task SaveXmlToFileAsync()
+ // invoked by XmlCacheFilePersister ONLY and that one manages the MainDom, ie it
+ // will NOT try to save once the current app domain is not the main domain anymore
+ // (no need to test _released)
+ internal async Task SaveXmlToFileAsync()
{
LogHelper.Info("Save Xml to file...");
@@ -1142,8 +1062,6 @@ order by umbracoNode.level, umbracoNode.sortOrder";
var xml = _xmlContent; // capture (atomic + volatile), immutable anyway
if (xml == null) return;
- EnsureFileLock();
-
// delete existing file, if any
DeleteXmlFile();
@@ -1151,7 +1069,7 @@ order by umbracoNode.level, umbracoNode.sortOrder";
var directoryName = Path.GetDirectoryName(_xmlFileName);
if (directoryName == null)
throw new Exception(string.Format("Invalid XmlFileName \"{0}\".", _xmlFileName));
- if (System.IO.File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false)
+ if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false)
Directory.CreateDirectory(directoryName);
// save
@@ -1200,17 +1118,15 @@ order by umbracoNode.level, umbracoNode.sortOrder";
return sb.ToString();
}
- // assumes file lock
private XmlDocument LoadXmlFromFile()
{
+ // do NOT try to load if we are not the main domain anymore
+ if (_released) return null;
+
LogHelper.Info("Load Xml from file...");
try
{
- // if we're not writing back to the file, no need to lock
- if (SyncToXmlFile)
- EnsureFileLock();
-
var xml = new XmlDocument();
using (var fs = new FileStream(_xmlFileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
@@ -1233,12 +1149,11 @@ order by umbracoNode.level, umbracoNode.sortOrder";
}
}
- // (files is always locked)
private void DeleteXmlFile()
{
- if (System.IO.File.Exists(_xmlFileName) == false) return;
- System.IO.File.SetAttributes(_xmlFileName, FileAttributes.Normal);
- System.IO.File.Delete(_xmlFileName);
+ if (File.Exists(_xmlFileName) == false) return;
+ File.SetAttributes(_xmlFileName, FileAttributes.Normal);
+ File.Delete(_xmlFileName);
}
private void ReloadXmlFromFileIfChanged()
@@ -1268,8 +1183,15 @@ order by umbracoNode.level, umbracoNode.sortOrder";
#region Manage change
- // adds or updates a node (docNode) into a cache (xml)
+ //TODO remove as soon as we can break backward compatibility
+ [Obsolete("Use GetAddOrUpdateXmlNode which returns an updated Xml document.", false)]
public static void AddOrUpdateXmlNode(XmlDocument xml, int id, int level, int parentId, XmlNode docNode)
+ {
+ GetAddOrUpdateXmlNode(xml, id, level, parentId, docNode);
+ }
+
+ // adds or updates a node (docNode) into a cache (xml)
+ public static XmlDocument GetAddOrUpdateXmlNode(XmlDocument xml, int id, int level, int parentId, XmlNode docNode)
{
// sanity checks
if (id != docNode.AttributeValue("id"))
@@ -1283,7 +1205,12 @@ order by umbracoNode.level, umbracoNode.sortOrder";
// if the document is not there already then it's a new document
// we must make sure that its document type exists in the schema
if (currentNode == null && UseLegacySchema == false)
- EnsureSchema(docNode.Name, xml);
+ {
+ var xml2 = EnsureSchema(docNode.Name, xml);
+ if (ReferenceEquals(xml, xml2) == false)
+ docNode = xml2.ImportNode(docNode, true);
+ xml = xml2;
+ }
// find the parent
XmlNode parentNode = level == 1
@@ -1292,7 +1219,7 @@ order by umbracoNode.level, umbracoNode.sortOrder";
// no parent = cannot do anything
if (parentNode == null)
- return;
+ return xml;
// insert/move the node under the parent
if (currentNode == null)
@@ -1360,6 +1287,7 @@ order by umbracoNode.level, umbracoNode.sortOrder";
// then we just need to ensure that currentNode is at the right position.
// should be faster that moving all the nodes around.
XmlHelper.SortNode(parentNode, ChildNodesXPath, currentNode, x => x.AttributeValue("sortOrder"));
+ return xml;
}
private static void TransferValuesFromDocumentXmlToPublishedXml(XmlNode documentNode, XmlNode publishedNode)
diff --git a/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs b/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs
index 6d80d8bedd..abc3425f3f 100644
--- a/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs
+++ b/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs
@@ -18,9 +18,7 @@ namespace umbraco.presentation
var appContext = (ApplicationContext) sender;
- //TODO: This won't always work, in load balanced scenarios ping will not work because
- // this original request url will be public and not internal to the server.
- var url = string.Format("http://{0}/ping.aspx", appContext.OriginalRequestUrl);
+ var url = appContext.UmbracoApplicationUrl + "/ping.aspx";
try
{
using (var wc = new WebClient())
diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs
index 7b9c63e6b8..136c30c799 100644
--- a/src/Umbraco.Web/umbraco.presentation/macro.cs
+++ b/src/Umbraco.Web/umbraco.presentation/macro.cs
@@ -537,8 +537,8 @@ namespace umbraco
///
private Control AddMacroResultToCache(Control macroControl)
{
- // Add result to cache if successful
- if (Model.CacheDuration > 0)
+ // Add result to cache if successful (and cache is enabled)
+ if (UmbracoContext.Current.InPreviewMode == false && Model.CacheDuration > 0)
{
// do not add to cache if there's no member and it should cache by personalization
if (!Model.CacheByMember || (Model.CacheByMember && Member.IsLoggedOn()))
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.cs
index c9d7a21dfb..436ead7c93 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.cs
@@ -67,7 +67,7 @@ namespace umbraco.cms.presentation.developer.RelationTypes.TreeMenu
///
public string JsSource
{
- get { return "/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js"; }
+ get { return "~/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js"; }
}
///
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.cs
index 2dcbbfb077..4b36559da2 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.cs
@@ -67,7 +67,7 @@ namespace umbraco.cms.presentation.developer.RelationTypes.TreeMenu
///
public string JsSource
{
- get { return "/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.js"; }
+ get { return "~/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.js"; }
}
///
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs
index 6a117e732c..e2a2588620 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs
@@ -102,7 +102,7 @@ namespace umbraco.presentation.preview
if (document.Content.Published == false
&& ApplicationContext.Current.Services.ContentService.HasPublishedVersion(document.Id))
previewXml.Attributes.Append(XmlContent.CreateAttribute("isDraft"));
- content.AddOrUpdateXmlNode(XmlContent, document.Id, document.Level, parentId, previewXml);
+ XmlContent = content.GetAddOrUpdateXmlNode(XmlContent, document.Id, document.Level, parentId, previewXml);
}
if (includeSubs)
@@ -112,7 +112,7 @@ namespace umbraco.presentation.preview
var previewXml = XmlContent.ReadNode(XmlReader.Create(new StringReader(prevNode.Xml)));
if (prevNode.IsDraft)
previewXml.Attributes.Append(XmlContent.CreateAttribute("isDraft"));
- content.AddOrUpdateXmlNode(XmlContent, prevNode.NodeId, prevNode.Level, prevNode.ParentId, previewXml);
+ XmlContent = content.GetAddOrUpdateXmlNode(XmlContent, prevNode.NodeId, prevNode.Level, prevNode.ParentId, previewXml);
}
}
diff --git a/src/UmbracoExamine/Config/IndexSetExtensions.cs b/src/UmbracoExamine/Config/IndexSetExtensions.cs
index ac432b16ec..1255f50a3c 100644
--- a/src/UmbracoExamine/Config/IndexSetExtensions.cs
+++ b/src/UmbracoExamine/Config/IndexSetExtensions.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
using System.Text;
using Examine;
@@ -17,60 +16,7 @@ namespace UmbracoExamine.Config
internal static IIndexCriteria ToIndexCriteria(this IndexSet set, IDataService svc,
IEnumerable indexFieldPolicies)
{
-
- var attributeFields = set.IndexAttributeFields.Cast().ToArray();
- var userFields = set.IndexUserFields.Cast().ToArray();
- var includeNodeTypes = set.IncludeNodeTypes.ToList().Select(x => x.Name).ToArray();
- var excludeNodeTypes = set.ExcludeNodeTypes.ToList().Select(x => x.Name).ToArray();
- var parentId = set.IndexParentId;
-
- //if there are no user fields defined, we'll populate them from the data source (include them all)
- if (set.IndexUserFields.Count == 0)
- {
- //we need to add all user fields to the collection if it is empty (this is the default if none are specified)
- var userProps = svc.ContentService.GetAllUserPropertyNames();
- var fields = new List();
- foreach (var u in userProps)
- {
- var field = new IndexField() { Name = u };
- var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == u);
- if (policy != null)
- {
- field.Type = policy.Type;
- field.EnableSorting = policy.EnableSorting;
- }
- fields.Add(field);
- }
- userFields = fields.ToArray();
- }
-
- //if there are no attribute fields defined, we'll populate them from the data source (include them all)
- if (set.IndexAttributeFields.Count == 0)
- {
- //we need to add all system fields to the collection if it is empty (this is the default if none are specified)
- var sysProps = svc.ContentService.GetAllSystemPropertyNames();
- var fields = new List();
- foreach (var s in sysProps)
- {
- var field = new IndexField() { Name = s };
- var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == s);
- if (policy != null)
- {
- field.Type = policy.Type;
- field.EnableSorting = policy.EnableSorting;
- }
- fields.Add(field);
- }
- attributeFields = fields.ToArray();
- }
-
-
- return new IndexCriteria(
- attributeFields,
- userFields,
- includeNodeTypes,
- excludeNodeTypes,
- parentId);
+ return new LazyIndexCriteria(set, svc, indexFieldPolicies);
}
///
diff --git a/src/UmbracoExamine/Config/LazyIndexCriteria.cs b/src/UmbracoExamine/Config/LazyIndexCriteria.cs
new file mode 100644
index 0000000000..72ab3f31ba
--- /dev/null
+++ b/src/UmbracoExamine/Config/LazyIndexCriteria.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Examine;
+using Examine.LuceneEngine.Config;
+using UmbracoExamine.DataServices;
+
+namespace UmbracoExamine.Config
+{
+ internal class LazyIndexCriteria : IIndexCriteria
+ {
+ public LazyIndexCriteria(
+ IndexSet set,
+ IDataService svc,
+ IEnumerable indexFieldPolicies)
+ {
+ if (set == null) throw new ArgumentNullException("set");
+ if (indexFieldPolicies == null) throw new ArgumentNullException("indexFieldPolicies");
+ if (svc == null) throw new ArgumentNullException("svc");
+
+ _lazyCriteria = new Lazy(() =>
+ {
+ var attributeFields = set.IndexAttributeFields.Cast().ToArray();
+ var userFields = set.IndexUserFields.Cast().ToArray();
+ var includeNodeTypes = set.IncludeNodeTypes.Cast().Select(x => x.Name).ToArray();
+ var excludeNodeTypes = set.ExcludeNodeTypes.Cast().Select(x => x.Name).ToArray();
+ var parentId = set.IndexParentId;
+
+ //if there are no user fields defined, we'll populate them from the data source (include them all)
+ if (set.IndexUserFields.Count == 0)
+ {
+ //we need to add all user fields to the collection if it is empty (this is the default if none are specified)
+ var userProps = svc.ContentService.GetAllUserPropertyNames();
+ var fields = new List();
+ foreach (var u in userProps)
+ {
+ var field = new IndexField() { Name = u };
+ var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == u);
+ if (policy != null)
+ {
+ field.Type = policy.Type;
+ field.EnableSorting = policy.EnableSorting;
+ }
+ fields.Add(field);
+ }
+ userFields = fields.ToArray();
+ }
+
+ //if there are no attribute fields defined, we'll populate them from the data source (include them all)
+ if (set.IndexAttributeFields.Count == 0)
+ {
+ //we need to add all system fields to the collection if it is empty (this is the default if none are specified)
+ var sysProps = svc.ContentService.GetAllSystemPropertyNames();
+ var fields = new List();
+ foreach (var s in sysProps)
+ {
+ var field = new IndexField() { Name = s };
+ var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == s);
+ if (policy != null)
+ {
+ field.Type = policy.Type;
+ field.EnableSorting = policy.EnableSorting;
+ }
+ fields.Add(field);
+ }
+ attributeFields = fields.ToArray();
+ }
+
+
+ return new IndexCriteria(
+ attributeFields,
+ userFields,
+ includeNodeTypes,
+ excludeNodeTypes,
+ parentId);
+ });
+ }
+
+ private readonly Lazy _lazyCriteria;
+
+ public IEnumerable ExcludeNodeTypes
+ {
+ get { return _lazyCriteria.Value.ExcludeNodeTypes; }
+ }
+
+ public IEnumerable IncludeNodeTypes
+ {
+ get { return _lazyCriteria.Value.IncludeNodeTypes; }
+ }
+
+ public int? ParentNodeId
+ {
+ get { return _lazyCriteria.Value.ParentNodeId; }
+ }
+
+ public IEnumerable StandardFields
+ {
+ get { return _lazyCriteria.Value.StandardFields; }
+ }
+
+ public IEnumerable UserFields
+ {
+ get { return _lazyCriteria.Value.UserFields; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj
index b7fc35542a..90e246e3cd 100644
--- a/src/UmbracoExamine/UmbracoExamine.csproj
+++ b/src/UmbracoExamine/UmbracoExamine.csproj
@@ -82,9 +82,9 @@
..\Solution Items\TheFARM-Public.snk
-
+ False
- ..\packages\Examine.0.1.63.0\lib\Examine.dll
+ ..\packages\Examine.0.1.65.0\lib\Examine.dllFalse
@@ -111,6 +111,7 @@
+
diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config
index efa216d153..fad9b5dabd 100644
--- a/src/UmbracoExamine/packages.config
+++ b/src/UmbracoExamine/packages.config
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/src/umbraco.MacroEngines/packages.config b/src/umbraco.MacroEngines/packages.config
index 9951ba7e01..b259997c0e 100644
--- a/src/umbraco.MacroEngines/packages.config
+++ b/src/umbraco.MacroEngines/packages.config
@@ -1,15 +1,15 @@
-
-
+
+
-
-
+
+
-
+
diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj
index a34198b5ce..4e414f3c69 100644
--- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj
+++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj
@@ -45,9 +45,9 @@
false
-
+ False
- ..\packages\Examine.0.1.63.0\lib\Examine.dll
+ ..\packages\Examine.0.1.65.0\lib\Examine.dllFalse
diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs
index 51f059dbb5..8e7f1f47e1 100644
--- a/src/umbraco.businesslogic/BasePages/BasePage.cs
+++ b/src/umbraco.businesslogic/BasePages/BasePage.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
+using System.Web.UI;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
@@ -181,7 +182,14 @@ namespace umbraco.BasePages
///
public static int GetUserId()
{
- var identity = HttpContext.Current.GetCurrentIdentity(true);
+ var identity = HttpContext.Current.GetCurrentIdentity(
+ //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS!
+ // Without this check, anything that is using this legacy API, like ui.Text will
+ // automatically log the back office user in even if it is a front-end request (if there is
+ // a back office user logged in. This can cause problems becaues the identity is changing mid
+ // request. For example: http://issues.umbraco.org/issue/U4-4010
+ HttpContext.Current.CurrentHandler is Page);
+
if (identity == null)
return -1;
return Convert.ToInt32(identity.Id);
@@ -205,7 +213,14 @@ namespace umbraco.BasePages
///
public static bool ValidateCurrentUser()
{
- var identity = HttpContext.Current.GetCurrentIdentity(true);
+ var identity = HttpContext.Current.GetCurrentIdentity(
+ //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS!
+ // Without this check, anything that is using this legacy API, like ui.Text will
+ // automatically log the back office user in even if it is a front-end request (if there is
+ // a back office user logged in. This can cause problems becaues the identity is changing mid
+ // request. For example: http://issues.umbraco.org/issue/U4-4010
+ HttpContext.Current.CurrentHandler is Page);
+
if (identity != null)
{
return true;
@@ -232,7 +247,14 @@ namespace umbraco.BasePages
{
get
{
- var identity = HttpContext.Current.GetCurrentIdentity(true);
+ var identity = HttpContext.Current.GetCurrentIdentity(
+ //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS!
+ // Without this check, anything that is using this legacy API, like ui.Text will
+ // automatically log the back office user in even if it is a front-end request (if there is
+ // a back office user logged in. This can cause problems becaues the identity is changing mid
+ // request. For example: http://issues.umbraco.org/issue/U4-4010
+ HttpContext.Current.CurrentHandler is Page);
+
return identity == null ? "" : identity.SessionId;
}
set
diff --git a/src/umbraco.cms/Actions/Action.cs b/src/umbraco.cms/Actions/Action.cs
index 075cd84436..724ae421da 100644
--- a/src/umbraco.cms/Actions/Action.cs
+++ b/src/umbraco.cms/Actions/Action.cs
@@ -13,6 +13,7 @@ using umbraco.cms.businesslogic.workflow;
using umbraco.interfaces;
using System.Text.RegularExpressions;
using System.Linq;
+using Umbraco.Core.IO;
using TypeFinder = Umbraco.Core.TypeFinder;
namespace umbraco.BusinessLogic.Actions
@@ -88,7 +89,7 @@ namespace umbraco.BusinessLogic.Actions
{
return ActionsResolver.Current.Actions
.Where(x => !string.IsNullOrWhiteSpace(x.JsSource))
- .Select(x => x.JsSource).ToList();
+ .Select(x => IOHelper.ResolveUrl(x.JsSource)).ToList();
//return ActionJsReference;
}
diff --git a/src/umbraco.cms/packages.config b/src/umbraco.cms/packages.config
index bdaf00b95d..539757211e 100644
--- a/src/umbraco.cms/packages.config
+++ b/src/umbraco.cms/packages.config
@@ -1,8 +1,8 @@
-
-
+
+
-
-
+
+
\ No newline at end of file
diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj
index 7f81309863..708c5f4d27 100644
--- a/src/umbraco.cms/umbraco.cms.csproj
+++ b/src/umbraco.cms/umbraco.cms.csproj
@@ -106,9 +106,9 @@
false
-
- False
- ..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll
+
+ ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll
+ TrueFalse
diff --git a/src/umbraco.controls/packages.config b/src/umbraco.controls/packages.config
index 0859f3d4e9..1e49726191 100644
--- a/src/umbraco.controls/packages.config
+++ b/src/umbraco.controls/packages.config
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/src/umbraco.controls/umbraco.controls.csproj b/src/umbraco.controls/umbraco.controls.csproj
index a5c08e525a..3980e5ed27 100644
--- a/src/umbraco.controls/umbraco.controls.csproj
+++ b/src/umbraco.controls/umbraco.controls.csproj
@@ -68,9 +68,9 @@
false
-
- False
- ..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll
+
+ ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll
+ True
diff --git a/src/umbraco.editorControls/packages.config b/src/umbraco.editorControls/packages.config
index 0859f3d4e9..1e49726191 100644
--- a/src/umbraco.editorControls/packages.config
+++ b/src/umbraco.editorControls/packages.config
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/src/umbraco.editorControls/umbraco.editorControls.csproj b/src/umbraco.editorControls/umbraco.editorControls.csproj
index 1266015268..f49aac9dcb 100644
--- a/src/umbraco.editorControls/umbraco.editorControls.csproj
+++ b/src/umbraco.editorControls/umbraco.editorControls.csproj
@@ -114,9 +114,9 @@
{651E1350-91B6-44B7-BD60-7207006D7003}Umbraco.Web
-
- False
- ..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll
+
+ ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll
+ TrueSystem