Merge branch '7.2.7' into master-v7

This commit is contained in:
Sebastiaan Janssen
2015-07-15 12:58:48 +02:00
93 changed files with 2048 additions and 1317 deletions
+2 -2
View File
@@ -28,11 +28,11 @@
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
<dependency id="MySql.Data" version="[6.9.6, 7.0.0)" />
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
<dependency id="ClientDependency" version="[1.8.3.1, 2.0.0)" />
<dependency id="ClientDependency" version="[1.8.4, 2.0.0)" />
<dependency id="ClientDependency-Mvc" version="[1.8.0, 2.0.0)" />
<dependency id="AutoMapper" version="[3.0.0, 4.0.0)" />
<dependency id="Newtonsoft.Json" version="[6.0.5, 7.0.0)" />
<dependency id="Examine" version="[0.1.63, 1.0.0)" />
<dependency id="Examine" version="[0.1.65, 1.0.0)" />
<dependency id="ImageProcessor" version="[1.9.5, 3.0.0)" />
<dependency id="ImageProcessor.Web" version="[3.3.1, 5.0.0)" />
</dependencies>
+1 -1
View File
@@ -1,2 +1,2 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.2.6
7.2.7
+2 -2
View File
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.2.6")]
[assembly: AssemblyInformationalVersion("7.2.6")]
[assembly: AssemblyFileVersion("7.2.7")]
[assembly: AssemblyInformationalVersion("7.2.7")]
+72 -63
View File
@@ -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();
}
/// <summary>
@@ -45,6 +44,8 @@ namespace Umbraco.Core
public ApplicationContext(CacheHelper cache)
{
ApplicationCache = cache;
Init();
}
/// <summary>
@@ -60,13 +61,13 @@ namespace Umbraco.Core
/// </remarks>
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;
}
/// <summary>
@@ -85,14 +86,14 @@ namespace Umbraco.Core
/// </remarks>
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;
}
/// <summary>
@@ -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; }
}
/// <summary>
/// The original/first url that the web application executes
/// The application url.
/// </summary>
/// <remarks>
/// 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
/// </remarks>
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());
/// <summary>
/// Checks if the version configured matches the assembly version
/// </summary>
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<ApplicationContext>("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'");
}
return ok;
}
catch
{
return false;
}
}
}
internal string _umbracoApplicationUrl; // internal for tests
private Lazy<bool> _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<bool>(() =>
{
var configStatus = ConfigurationStatus;
var currentVersion = UmbracoVersion.Current.ToString(3);
var ok = configStatus == currentVersion;
if (ok == false)
{
LogHelper.Debug<ApplicationContext>("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)
+2 -2
View File
@@ -74,7 +74,7 @@ namespace Umbraco.Core
/// <returns></returns>
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;
}
+16 -38
View File
@@ -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<IDisposable> 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<IDisposable> 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<object>();
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:*/ Timeout.Infinite,
/*executeOnlyOnce:*/ true);
}
return tcs.Task;
}
}
}
@@ -90,7 +90,7 @@ namespace Umbraco.Core.Cache
{
foreach (var entry in GetDictionaryEntries()
.ToArray())
RemoveEntry((string) entry.Key);
RemoveEntry((string)entry.Key);
}
}
@@ -105,6 +105,9 @@ namespace Umbraco.Core.Cache
public virtual void ClearCacheObjectTypes(string typeName)
{
var type = TypeFinder.GetTypeByName(typeName);
if (type == null) return;
var isInterface = type.IsInterface;
using (WriteLock)
{
foreach (var entry in GetDictionaryEntries()
@@ -114,16 +117,20 @@ namespace Umbraco.Core.Cache
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue((Lazy<object>)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));
})
.ToArray())
RemoveEntry((string) entry.Key);
RemoveEntry((string)entry.Key);
}
}
public virtual void ClearCacheObjectTypes<T>()
{
var typeOfT = typeof(T);
var isInterface = typeOfT.IsInterface;
using (WriteLock)
{
foreach (var entry in GetDictionaryEntries()
@@ -134,16 +141,20 @@ namespace Umbraco.Core.Cache
// compare on exact type, don't use "is"
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue((Lazy<object>)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));
})
.ToArray())
RemoveEntry((string) entry.Key);
RemoveEntry((string)entry.Key);
}
}
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
{
var typeOfT = typeof(T);
var isInterface = typeOfT.IsInterface;
var plen = CacheItemPrefix.Length + 1;
using (WriteLock)
{
@@ -156,11 +167,14 @@ namespace Umbraco.Core.Cache
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
if (value == null) return true;
return value.GetType() == typeOfT
// 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))
// run predicate on the 'public key' part only, ie without prefix
&& predicate(((string)x.Key).Substring(plen), (T)value);
&& predicate(((string)x.Key).Substring(plen), (T)value);
}))
RemoveEntry((string) entry.Key);
RemoveEntry((string)entry.Key);
}
}
@@ -172,7 +186,7 @@ namespace Umbraco.Core.Cache
foreach (var entry in GetDictionaryEntries()
.Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
.ToArray())
RemoveEntry((string) entry.Key);
RemoveEntry((string)entry.Key);
}
}
@@ -184,7 +198,7 @@ namespace Umbraco.Core.Cache
foreach (var entry in GetDictionaryEntries()
.Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
.ToArray())
RemoveEntry((string) entry.Key);
RemoveEntry((string)entry.Key);
}
}
@@ -102,7 +102,7 @@ namespace Umbraco.Core.Cache
// cannot create value within the lock, so if result.IsValueCreated is false, just
// do nothing here - means that if creation throws, a race condition could cause
// more than one thread to reach the return statement below and throw - accepted.
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
{
result = GetSafeLazy(getCacheItem);
@@ -122,7 +122,7 @@ namespace Umbraco.Core.Cache
if (eh != null) throw eh.Exception; // throw once!
return value;
}
#endregion
#region Insert
@@ -20,16 +20,22 @@ namespace Umbraco.Core.Cache
private readonly System.Web.Caching.Cache _cache;
/// <summary>
/// Used for debugging
/// </summary>
internal Guid InstanceId { get; private set; }
public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache)
{
_cache = cache;
InstanceId = Guid.NewGuid();
}
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
return _cache.Cast<DictionaryEntry>()
.Where(x => x.Key is string && ((string) x.Key).StartsWith(prefix));
.Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix));
}
protected override void RemoveEntry(string key)
@@ -134,6 +140,7 @@ namespace Umbraco.Core.Cache
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
lck.UpgradeToWriteLock();
//NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update!
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
}
}
@@ -184,13 +191,14 @@ namespace Umbraco.Core.Cache
var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
if (value == null) return; // do not store null values (backward compat)
cacheKey = GetCacheKey(cacheKey);
cacheKey = GetCacheKey(cacheKey);
var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
using (new WriteLock(_locker))
{
//NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update!
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
}
}
+38
View File
@@ -8,13 +8,51 @@ namespace Umbraco.Core.Cache
/// </summary>
public interface ICacheProvider
{
/// <summary>
/// Removes all items from the cache.
/// </summary>
void ClearAllCache();
/// <summary>
/// Removes an item from the cache, identified by its key.
/// </summary>
/// <param name="key">The key of the item.</param>
void ClearCacheItem(string key);
/// <summary>
/// Removes items from the cache, of a specified type.
/// </summary>
/// <param name="typeName">The name of the type to remove.</param>
/// <remarks>
/// <para>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).</para>
/// <para>Performs a case-sensitive search.</para>
/// </remarks>
void ClearCacheObjectTypes(string typeName);
/// <summary>
/// Removes items from the cache, of a specified type.
/// </summary>
/// <typeparam name="T">The type of the items to remove.</typeparam>
/// <remarks>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).</remarks>
void ClearCacheObjectTypes<T>();
/// <summary>
/// Removes items from the cache, of a specified type, satisfying a predicate.
/// </summary>
/// <typeparam name="T">The type of the items to remove.</typeparam>
/// <param name="predicate">The predicate to satisfy.</param>
/// <remarks>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).</remarks>
void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate);
void ClearCacheByKeySearch(string keyStartsWith);
void ClearCacheByKeyExpression(string regexString);
IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith);
IEnumerable<object> GetCacheItemsByKeyExpression(string regexString);
@@ -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
/// </summary>
internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
{
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
internal ObjectCache MemoryCache;
/// <summary>
/// Used for debugging
/// </summary>
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<object>)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<object>)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<object>)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<object> getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal,CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
public object GetCacheItem(string cacheKey, Func<object> 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;
}
}
}
@@ -11,6 +11,8 @@
bool DisableFindContentByIdPath { get; }
string UrlProviderMode { get; }
string UmbracoApplicationUrl { get; }
}
}
@@ -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"]; }
}
}
}
@@ -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");
/// <summary>
/// Gets the current version of Umbraco.
+93 -12
View File
@@ -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<CoreBootManager>("Umbraco application starting", "Umbraco application startup complete");
_timer = DisposableTimer.TraceDuration<CoreBootManager>(
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<CoreBootManager>(
() => 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<CoreBootManager>("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<CoreBootManager>(
() => 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<CoreBootManager>("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<CoreBootManager>(
() => 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<CoreBootManager>("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;
}
/// <summary>
/// We cannot continue if the db cannot be connected to
/// </summary>
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.");
}
}
/// <summary>
/// Freeze resolution to not allow Resolvers to be modified
/// </summary>
+3 -17
View File
@@ -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<DatabaseContext>("CanConnect = " + canConnect);
return canConnect;
}
}
+28 -39
View File
@@ -4,69 +4,58 @@ using System.Threading;
namespace Umbraco.Core
{
/// <summary>
/// Abstract implementation of logic commonly required to safely handle disposable unmanaged resources.
/// Abstract implementation of IDisposable.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public abstract class DisposableObject : IDisposable
{
private bool _disposed;
private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim();
private readonly object _locko = new object();
/// <summary>
/// Gets a value indicating whether this instance is disposed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
/// </value>
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; } }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
// 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();
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected abstract void DisposeResources();
protected virtual void DisposeUnmanagedResources()
{
}
{ }
}
}
@@ -0,0 +1,18 @@
using System;
namespace Umbraco.Core.Exceptions
{
/// <summary>
/// An exception that is thrown if the umbraco application cannnot boot
/// </summary>
public class UmbracoStartupFailedException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Exception"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error. </param>
public UmbracoStartupFailedException(string message) : base(message)
{
}
}
}
+6 -2
View File
@@ -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
/// <returns></returns>
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
/// <summary>
+185
View File
@@ -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<int, Action> _callbacks = new SortedList<int, Action>();
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<MainDom>("Signaled" + (_signaled ? " (again)" : "") + " (" + source + ").");
if (_signaled) return;
if (_isMainDom == false) return; // probably not needed
_signaled = true;
}
try
{
LogHelper.Debug<MainDom>("Stopping...");
foreach (var callback in _callbacks.Values)
{
try
{
callback(); // no timeout on callbacks
}
catch (Exception e)
{
LogHelper.Error<MainDom>("Error while running callback, remaining callbacks will not run.", e);
throw;
}
}
LogHelper.Debug<MainDom>("Stopped.");
}
finally
{
// in any case...
_isMainDom = false;
_asyncLocker.Dispose();
LogHelper.Debug<MainDom>("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<MainDom>("Cannot acquire MainDom (signaled).");
return false;
}
LogHelper.Debug<MainDom>("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<MainDom>("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);
}
}
}
}
+15 -1
View File
@@ -137,9 +137,23 @@ namespace Umbraco.Core.Models
new DelegateEqualityComparer<object>(
(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)
@@ -112,7 +112,7 @@ namespace Umbraco.Core.ObjectResolution
/// <exception cref="InvalidOperationException">resolution is already frozen.</exception>
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;
}
}
/// <summary>
@@ -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");
}
@@ -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<string>("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""");
}
}
}
@@ -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<int>(@"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<string, PreValue> 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<int>(@"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<string, PreValue> 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
{
+4 -8
View File
@@ -606,14 +606,10 @@ namespace Umbraco.Core
/// <returns></returns>
internal IEnumerable<T> CreateInstances<T>(IEnumerable<Type> 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<PluginManager>(
// 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<PluginManager>(string.Format("Instantiating {0} objects of type {1}", typesAsArray.Length, typeof (T).FullName));
var instances = new List<T>();
foreach (var t in typesAsArray)
{
@@ -634,7 +630,7 @@ namespace Umbraco.Core
}
}
return instances;
//}
}
/// <summary>
@@ -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
/// </summary>
internal static class ServerEnvironmentHelper
{
/// <summary>
/// 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.
/// </summary>
/// <returns>The full base url including schema (i.e. http://myserver:80/umbraco ) - or <c>null</c> if the url
/// cannot be determined at the moment (usually because the first request has not properly completed yet).</returns>
public static string GetCurrentServerUmbracoBaseUrl(ApplicationContext appContext, 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);
}
/// <summary>
@@ -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('/');
}
}
}
+19 -7
View File
@@ -137,7 +137,7 @@ namespace Umbraco.Core
}
return _allAssemblies;
}
}
}
/// <summary>
@@ -226,7 +226,7 @@ namespace Umbraco.Core
}
return LocalFilteredAssemblyCache;
}
}
}
/// <summary>
@@ -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);
}
}
}
+3
View File
@@ -312,10 +312,12 @@
<Compile Include="Events\SaveEventArgs.cs" />
<Compile Include="Events\SendToPublishEventArgs.cs" />
<Compile Include="Exceptions\InvalidCompositionException.cs" />
<Compile Include="Exceptions\UmbracoStartupFailedException.cs" />
<Compile Include="HideFromTypeFinderAttribute.cs" />
<Compile Include="HttpContextExtensions.cs" />
<Compile Include="IApplicationEventHandler.cs" />
<Compile Include="IDisposeOnRequestEnd.cs" />
<Compile Include="MainDom.cs" />
<Compile Include="Manifest\GridEditorConverter.cs" />
<Compile Include="Media\Exif\BitConverterEx.cs" />
<Compile Include="Media\Exif\ExifBitConverter.cs" />
@@ -1214,6 +1216,7 @@
<Compile Include="UriExtensions.cs" />
<Compile Include="SystemUtilities.cs" />
<Compile Include="UrlHelperExtensions.cs" />
<Compile Include="WaitHandleExtensions.cs" />
<Compile Include="WriteLock.cs" />
<Compile Include="XmlExtensions.cs" />
<Compile Include="XmlHelper.cs" />
+47 -3
View File
@@ -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<UmbracoApplicationBase>(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<UmbracoApplicationBase>("An error occurred in an ApplicationStarting event handler", ex);
throw;
}
}
}
/// <summary>
@@ -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<UmbracoApplicationBase>("An error occurred in an ApplicationStarted event handler", ex);
throw;
}
}
}
/// <summary>
@@ -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<UmbracoApplicationBase>("An error occurred in an ApplicationInit event handler", ex);
throw;
}
}
}
/// <summary>
+1 -1
View File
@@ -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;
+44
View File
@@ -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<object>();
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;
}
}
}
@@ -581,15 +581,11 @@ namespace Umbraco.Tests.Scheduling
var waitHandle = new ManualResetEvent(false);
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(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<IBackgroundTask>(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<MyDelayedRecurringTask>
private class MyDelayedRecurringTask : RecurringTaskBase
{
public bool HasRun { get; private set; }
public MyDelayedRecurringTask(IBackgroundTaskRunner<MyDelayedRecurringTask> runner, int delayMilliseconds, int periodMilliseconds)
public MyDelayedRecurringTask(IBackgroundTaskRunner<RecurringTaskBase> 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<bool> 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<MyRecurringTask>
private class MyRecurringTask : RecurringTaskBase
{
private readonly int _runMilliseconds;
public MyRecurringTask(IBackgroundTaskRunner<MyRecurringTask> runner, int runMilliseconds, int periodMilliseconds)
: base(runner, periodMilliseconds)
public MyRecurringTask(IBackgroundTaskRunner<RecurringTaskBase> 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<bool> 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
@@ -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<IUmbracoSettingsSection>(
section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>()));
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<IUmbracoSettingsSection>(
section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>()));
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<IUmbracoSettingsSection>(
section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world")));
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(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<IUmbracoSettingsSection>(
section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(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<IUmbracoSettingsSection>(
section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == "httpx://whatever.com/hello/world/")
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world")));
Assert.AreEqual("httpx://whatever.com/hello/world", appContext._umbracoApplicationUrl);
}
}
}
+2 -2
View File
@@ -58,9 +58,9 @@
<Reference Include="AutoMapper.Net4">
<HintPath>..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll</HintPath>
</Reference>
<Reference Include="Examine, Version=0.1.63.0, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="Examine, Version=0.1.65.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Examine.0.1.63.0\lib\Examine.dll</HintPath>
<HintPath>..\packages\Examine.0.1.65.0\lib\Examine.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -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
@@ -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);
+9
View File
@@ -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));
}
+9 -9
View File
@@ -2,25 +2,25 @@
<packages>
<package id="AspNetWebApi.SelfHost" version="4.0.20710.0" targetFramework="net45" />
<package id="AutoMapper" version="3.0.0" targetFramework="net45" />
<package id="Examine" version="0.1.63.0" targetFramework="net45" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net40" />
<package id="Examine" version="0.1.65.0" targetFramework="net45" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net4" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" targetFramework="net40" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net4" />
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" targetFramework="net4" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net4" />
<package id="Microsoft.AspNet.WebApi" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Client" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Core" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.SelfHost" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net4" />
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net45" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
<package id="MiniProfiler" version="2.1.0" targetFramework="net45" />
<package id="Moq" version="4.1.1309.0919" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
<package id="NUnit" version="2.6.2" targetFramework="net40" />
<package id="Selenium.WebDriver" version="2.32.0" targetFramework="net40" />
<package id="NUnit" version="2.6.2" targetFramework="net4" />
<package id="Selenium.WebDriver" version="2.32.0" targetFramework="net4" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
<package id="SqlServerCE" version="4.0.0.0" targetFramework="net40" />
<package id="SqlServerCE" version="4.0.0.0" targetFramework="net4" />
</packages>
+8 -4
View File
@@ -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"
}
}
}
}
+1 -1
View File
@@ -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);
}
}
}
@@ -40,6 +40,8 @@ angular.module("umbraco.directives")
}
});
$('.nav-pills, .nav-tabs').tabdrop();
}
scope.showTabs = iAttrs.tabs ? true : false;
@@ -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;
}
}
+194 -138
View File
@@ -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%); }
.umb-panel a.text-success:focus {
color: darken(@formSuccessText, 10%);
}
@@ -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;}
+13 -9
View File
@@ -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',
@@ -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();
});
}
@@ -1,14 +1,26 @@
<div class="umb-editor umb-datepicker" ng-controller="Umbraco.PropertyEditors.DatepickerController">
<div class="input-append date datepicker" style="position: relative;" id="datepicker{{model.alias}}">
<input name="datepicker" data-format="{{model.config.format}}" type="text"
ng-model="datetimePickerValue"
ng-required="model.validation.mandatory"
val-server="value" />
<span class="add-on">
<i class="icon-calendar"></i>
</span>
</div>
<ng-form name="datePickerForm">
<div class="input-append date datepicker" style="position: relative;" id="datepicker{{model.alias}}">
<input name="datepicker" data-format="{{model.config.format}}" type="text"
ng-model="datetimePickerValue"
ng-required="model.validation.mandatory"
val-server="value"
class="datepickerinput" />
<span class="help-inline" val-msg-for="datepicker" val-toggle-msg="required">Required</span>
<span class="help-inline" val-msg-for="datepicker" val-toggle-msg="valServer">{{propertyForm.datepicker.errorMsg}}</span>
<span class="add-on">
<i class="icon-calendar"></i>
</span>
</div>
<span class="help-inline" val-msg-for="datepicker" val-toggle-msg="required">Required</span>
<span class="help-inline" val-msg-for="datepicker" val-toggle-msg="valServer">{{datePickerForm.datepicker.errorMsg}}</span>
<span class="help-inline" val-msg-for="datepicker" val-toggle-msg="pickerError">Invalid date</span>
<p ng-show="hasDatetimePickerValue === true || datePickerForm.datepicker.$error.pickerError === true">
<a href ng-click="clearDate()"><i class="icon-delete"></i><small><localize key="content_removeDate">Clear date</localize></small></a>
</p>
</ng-form>
</div>
+9 -8
View File
@@ -47,6 +47,7 @@
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<MvcProjectUpgradeChecked>true</MvcProjectUpgradeChecked>
<UseGlobalApplicationHostFile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\</OutputPath>
@@ -114,9 +115,9 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll</HintPath>
</Reference>
<Reference Include="ClientDependency.Core, Version=1.8.3.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll</HintPath>
<Reference Include="ClientDependency.Core, Version=1.8.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="ClientDependency.Core.Mvc, Version=1.8.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -126,9 +127,9 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\dotless.1.4.1.0\lib\dotless.Core.dll</HintPath>
</Reference>
<Reference Include="Examine">
<HintPath>..\packages\Examine.0.1.63.0\lib\Examine.dll</HintPath>
<Private>True</Private>
<Reference Include="Examine, Version=0.1.65.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Examine.0.1.65.0\lib\Examine.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -2540,9 +2541,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\"
<WebProjectProperties>
<UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>7260</DevelopmentServerPort>
<DevelopmentServerPort>7270</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:7260</IISUrl>
<IISUrl>http://localhost:7270</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@@ -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
-->
<clientDependency version="908669955" fileDependencyExtensions=".js,.css" loggerType="Umbraco.Web.UI.CdfLogger, umbraco">
<clientDependency version="2118623877" fileDependencyExtensions=".js,.css" loggerType="Umbraco.Web.UI.CdfLogger, umbraco">
<!--
This section is used for Web Forms only, the enableCompositeFiles="true" is optional and by default is set to true.
@@ -147,10 +147,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".
-->
<web.routing
trySkipIisCustomErrors="false"
internalRedirectPreservesTemplate="false" disableAlternativeTemplates="false" disableFindContentByIdPath="false">
internalRedirectPreservesTemplate="false" disableAlternativeTemplates="false" disableFindContentByIdPath="false"
umbracoApplicationUrl="">
</web.routing>
</settings>
@@ -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".
-->
<web.routing
trySkipIisCustomErrors="false"
internalRedirectPreservesTemplate="false" disableAlternativeTemplates="false" disableFindContentByIdPath="false">
<web.routing
trySkipIisCustomErrors="false"
internalRedirectPreservesTemplate="false" disableAlternativeTemplates="false" disableFindContentByIdPath="false"
umbracoApplicationUrl="">
</web.routing>
</settings>
+25 -25
View File
@@ -1,28 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AutoMapper" version="3.0.0" targetFramework="net45" userInstalled="true" />
<package id="ClientDependency" version="1.8.3.1" targetFramework="net45" userInstalled="true" />
<package id="ClientDependency-Mvc" version="1.8.0.0" targetFramework="net45" userInstalled="true" />
<package id="dotless" version="1.4.1.0" targetFramework="net45" userInstalled="true" />
<package id="Examine" version="0.1.63.0" targetFramework="net45" />
<package id="ImageProcessor" version="1.9.5.0" targetFramework="net45" userInstalled="true" />
<package id="ImageProcessor.Web" version="3.3.1.0" targetFramework="net45" userInstalled="true" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net4" userInstalled="true" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net4" userInstalled="true" />
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" targetFramework="net4" userInstalled="true" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net4" userInstalled="true" />
<package id="Microsoft.AspNet.WebApi" version="4.0.30506.0" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.AspNet.WebApi.Client" version="4.0.30506.0" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.AspNet.WebApi.Core" version="4.0.30506.0" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="4.0.30506.0" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net4" userInstalled="true" />
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" userInstalled="true" />
<package id="MiniProfiler" version="2.1.0" targetFramework="net45" userInstalled="true" />
<package id="MySql.Data" version="6.9.6" targetFramework="net45" userInstalled="true" />
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" userInstalled="true" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" userInstalled="true" />
<package id="SqlServerCE" version="4.0.0.0" targetFramework="net4" userInstalled="true" />
<package id="UrlRewritingNet.UrlRewriter" version="2.0.60829.1" targetFramework="net4" userInstalled="true" />
<package id="AutoMapper" version="3.0.0" targetFramework="net45" />
<package id="ClientDependency" version="1.8.4" targetFramework="net45" />
<package id="ClientDependency-Mvc" version="1.8.0.0" targetFramework="net45" />
<package id="dotless" version="1.4.1.0" targetFramework="net45" />
<package id="Examine" version="0.1.65.0" targetFramework="net45" />
<package id="ImageProcessor" version="1.9.5.0" targetFramework="net45" />
<package id="ImageProcessor.Web" version="3.3.1.0" targetFramework="net45" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net4" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net4" />
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" targetFramework="net4" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net4" />
<package id="Microsoft.AspNet.WebApi" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Client" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Core" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net4" />
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net45" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
<package id="MiniProfiler" version="2.1.0" targetFramework="net45" />
<package id="MySql.Data" version="6.9.6" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
<package id="SqlServerCE" version="4.0.0.0" targetFramework="net4" />
<package id="UrlRewritingNet.UrlRewriter" version="2.0.60829.1" targetFramework="net4" />
</packages>
@@ -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<CacheRefresherEventHandler>("Initializing Umbraco internal event handlers for cache refreshing");
//bind to application tree events
ApplicationTreeService.Deleted += ApplicationTreeDeleted;
ApplicationTreeService.Updated += ApplicationTreeUpdated;
@@ -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;
}
@@ -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";
/// <summary>
/// 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
/// </summary>
/// <param name="appContext"></param>
/// <returns></returns>
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);
}
/// <summary>
@@ -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.
/// </remarks>
internal class XmlCacheFilePersister : ILatchedBackgroundTask
internal class XmlCacheFilePersister : LatchedBackgroundTaskBase
{
private readonly IBackgroundTaskRunner<XmlCacheFilePersister> _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<XmlCacheFilePersister> runner, content content)
@@ -141,28 +140,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
lock (_locko)
{
LogHelper.Debug<XmlCacheFilePersister>("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();
}
}
}
@@ -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)
{
@@ -15,6 +15,8 @@ namespace Umbraco.Web.Scheduling
LongRunning = false;
KeepAlive = false;
AutoStart = false;
PreserveRunningTask = false;
Hosted = true;
}
/// <summary>
@@ -36,9 +38,16 @@ namespace Umbraco.Web.Scheduling
public bool AutoStart { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool PreserveRunningTask { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool Hosted { get; set; }
}
}
@@ -1,74 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Umbraco.Web.Scheduling
{
/// <summary>
/// Provides a base class for recurring background tasks.
/// </summary>
/// <typeparam name="T">The type of the managed tasks.</typeparam>
internal abstract class DelayedRecurringTaskBase<T> : RecurringTaskBase<T>, ILatchedBackgroundTask
where T : class, IBackgroundTask
{
private readonly ManualResetEventSlim _latch;
private Timer _timer;
protected DelayedRecurringTaskBase(IBackgroundTaskRunner<T> 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<T> 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; }
}
}
}
+62 -26
View File
@@ -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<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
ApplicationContext appContext)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_appContext = appContext;
}
public override bool PerformRun()
{
throw new NotImplementedException();
}
public override async Task<bool> 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<ScheduledPublishing>("Does not run if not MainDom.");
return false; // do NOT repeat, going down
}
using (DisposableTimer.DebugDuration<KeepAlive>(() => "Keep alive executing", () => "Keep alive complete"))
{
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(
appContext,
settings);
{
string umbracoAppUrl = null;
if (string.IsNullOrWhiteSpace(umbracoBaseUrl))
try
{
LogHelper.Warn<KeepAlive>("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<KeepAlive>("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<KeepAlive>(
string.Format("Error in ping. The base url used in the request was: {0}, see http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks documentation for details on setting a baseUrl if this is in error", umbracoBaseUrl)
, ee);
RequestUri = new Uri(url),
Method = HttpMethod.Get
};
var result = await wc.SendAsync(request, token);
}
}
catch (Exception e)
{
LogHelper.Error<KeepAlive>(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e);
}
}
return true; // repeat
}
public override bool IsAsync
{
get { return true; }
}
public override bool RunsOnShutdown
{
get { return false; }
}
}
}
@@ -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);
}
/// <summary>
/// Implements IBackgroundTask.Run().
/// </summary>
public abstract void Run();
/// <summary>
/// Implements IBackgroundTask.RunAsync().
/// </summary>
public abstract Task RunAsync(CancellationToken token);
/// <summary>
/// Indicates whether the background task can run asynchronously.
/// </summary>
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();
}
}
}
+27 -20
View File
@@ -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<LogScrubber>
internal class LogScrubber : RecurringTaskBase
{
private readonly ApplicationContext _appContext;
private readonly IUmbracoSettingsSection _settings;
public LogScrubber(IBackgroundTaskRunner<LogScrubber> runner, int delayMilliseconds, int periodMilliseconds,
public LogScrubber(IBackgroundTaskRunner<RecurringTaskBase> 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<Scheduler>("Unable to locate a log scrubbing maximum age. Defaulting to 24 horus", e);
LogHelper.Error<LogScrubber>("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<LogScrubber>(() => "Log scrubbing executing", () => "Log scrubbing complete"))
if (_appContext == null) return true; // repeat...
if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
{
LogHelper.Debug<LogScrubber>("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<LogScrubber>("Does not run if not MainDom.");
return false; // do NOT repeat, going down
}
using (DisposableTimer.DebugDuration<LogScrubber>("Log scrubbing executing", "Log scrubbing complete"))
{
Log.CleanLogs(GetLogScrubbingMaximumAge(_settings));
}
}
return true; // repeat
}
public override Task PerformRunAsync()
public override Task<bool> PerformRunAsync(CancellationToken token)
{
throw new NotImplementedException();
}
+40 -59
View File
@@ -6,57 +6,51 @@ namespace Umbraco.Web.Scheduling
/// <summary>
/// Provides a base class for recurring background tasks.
/// </summary>
/// <typeparam name="T">The type of the managed tasks.</typeparam>
internal abstract class RecurringTaskBase<T> : IBackgroundTask
where T : class, IBackgroundTask
internal abstract class RecurringTaskBase : LatchedBackgroundTaskBase
{
private readonly IBackgroundTaskRunner<T> _runner;
private readonly IBackgroundTaskRunner<RecurringTaskBase> _runner;
private readonly int _periodMilliseconds;
private Timer _timer;
private T _recurrent;
private readonly Timer _timer;
private bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="RecurringTaskBase{T}"/> class with a tasks runner and a period.
/// Initializes a new instance of the <see cref="RecurringTaskBase"/> class.
/// </summary>
/// <param name="runner">The task runner.</param>
/// <param name="delayMilliseconds">The delay.</param>
/// <param name="periodMilliseconds">The period.</param>
/// <remarks>The task will repeat itself periodically. Use this constructor to create a new task.</remarks>
protected RecurringTaskBase(IBackgroundTaskRunner<T> runner, int periodMilliseconds)
protected RecurringTaskBase(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds)
{
_runner = runner;
_periodMilliseconds = periodMilliseconds;
}
/// <summary>
/// Initializes a new instance of the <see cref="RecurringTaskBase{T}"/> class with a source task.
/// </summary>
/// <param name="source">The source task.</param>
/// <remarks>Use this constructor to create a new task from a source task in <c>GetRecurring</c>.</remarks>
protected RecurringTaskBase(RecurringTaskBase<T> 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);
}
/// <summary>
/// Implements IBackgroundTask.Run().
/// </summary>
/// <remarks>Classes inheriting from <c>RecurringTaskBase</c> must implement <c>PerformRun</c>.</remarks>
public virtual void Run()
public override void Run()
{
PerformRun();
Repeat();
var shouldRepeat = PerformRun();
if (shouldRepeat) Repeat();
}
/// <summary>
/// Implements IBackgroundTask.RunAsync().
/// </summary>
/// <remarks>Classes inheriting from <c>RecurringTaskBase</c> must implement <c>PerformRun</c>.</remarks>
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);
}
/// <summary>
/// Indicates whether the background task can run asynchronously.
/// </summary>
public abstract bool IsAsync { get; }
/// <summary>
/// Runs the background task.
/// </summary>
public abstract void PerformRun();
/// <returns>A value indicating whether to repeat the task.</returns>
public abstract bool PerformRun();
/// <summary>
/// Runs the task asynchronously.
/// </summary>
/// <returns>A <see cref="Task"/> instance representing the execution of the background task.</returns>
public abstract Task PerformRunAsync();
/// <param name="token">A cancellation token.</param>
/// <returns>A <see cref="Task{T}"/> instance representing the execution of the background task,
/// and returning a value indicating whether to repeat the task.</returns>
public abstract Task<bool> PerformRunAsync(CancellationToken token);
/// <summary>
/// Gets a new occurence of the recurring task.
/// </summary>
/// <returns>A new task instance to be queued, or <c>null</c> to terminate the recurring task.</returns>
/// <remarks>The new task instance must be created via the <c>RecurringTaskBase(RecurringTaskBase{T} source)</c> constructor,
/// where <c>source</c> is the current task, eg: <c>return new MyTask(this);</c></remarks>
protected abstract T GetRecurring();
protected override void DisposeResources()
{
base.DisposeResources();
/// <summary>
/// Dispose the task.
/// </summary>
public virtual void Dispose()
{ }
// stop the timer
_timer.Change(Timeout.Infinite, Timeout.Infinite);
_timer.Dispose();
}
}
}
@@ -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<ScheduledPublishing>
internal class ScheduledPublishing : RecurringTaskBase
{
private readonly ApplicationContext _appContext;
private readonly IUmbracoSettingsSection _settings;
private static bool _isPublishingRunning;
public ScheduledPublishing(IBackgroundTaskRunner<ScheduledPublishing> runner, int delayMilliseconds, int periodMilliseconds,
public ScheduledPublishing(IBackgroundTaskRunner<RecurringTaskBase> 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<ScheduledPublishing>("Does not run on slave servers.");
return;
}
using (DisposableTimer.DebugDuration<ScheduledPublishing>(() => "Scheduled publishing executing", () => "Scheduled publishing complete"))
{
if (_isPublishingRunning) return;
_isPublishingRunning = true;
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(_appContext, _settings);
try
{
if (string.IsNullOrWhiteSpace(umbracoBaseUrl))
{
LogHelper.Warn<ScheduledPublishing>("No url for service (yet), skip.");
}
else
{
var url = string.Format("{0}RestServices/ScheduledPublish/Index", umbracoBaseUrl.EnsureEndsWith('/'));
using (var wc = new WebClient())
{
//pass custom the authorization header
wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(_appContext));
var result = wc.UploadString(url, "");
}
}
}
catch (Exception ee)
{
LogHelper.Error<ScheduledPublishing>(
string.Format("An error occurred with the scheduled publishing. The base url used in the request was: {0}, see http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks documentation for details on setting a baseUrl if this is in error", umbracoBaseUrl)
, ee);
}
finally
{
_isPublishingRunning = false;
}
}
}
public override Task PerformRunAsync()
public override bool PerformRun()
{
throw new NotImplementedException();
}
public override async Task<bool> PerformRunAsync(CancellationToken token)
{
if (_appContext == null) return true; // repeat...
if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
{
LogHelper.Debug<ScheduledPublishing>("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<ScheduledPublishing>("Does not run if not MainDom.");
return false; // do NOT repeat, going down
}
using (DisposableTimer.DebugDuration<ScheduledPublishing>(() => "Scheduled publishing executing", () => "Scheduled publishing complete"))
{
string umbracoAppUrl = null;
try
{
umbracoAppUrl = _appContext.UmbracoApplicationUrl;
if (umbracoAppUrl.IsNullOrWhiteSpace())
{
LogHelper.Warn<ScheduledPublishing>("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<ScheduledPublishing>(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e);
}
}
return true; // repeat
}
public override bool IsAsync
{
get { return false; }
get { return true; }
}
public override bool RunsOnShutdown
+50 -53
View File
@@ -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<ScheduledTasks>
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<ScheduledTasks> runner, int delayMilliseconds, int periodMilliseconds,
public ScheduledTasks(IBackgroundTaskRunner<RecurringTaskBase> 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<ScheduledTasks>(string.Format("{0} has been called with response: {1}", t.Alias, taskResult));
}
}
}
private bool GetTaskByHttp(string url)
private async Task<bool> 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<ScheduledTasks>("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<ScheduledTasks>("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<bool> PerformRunAsync(CancellationToken token)
{
if (_appContext == null) return true; // repeat...
if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
{
LogHelper.Debug<ScheduledTasks>("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<ScheduledTasks>("Does not run if not MainDom.");
return false; // do NOT repeat, going down
}
using (DisposableTimer.DebugDuration<ScheduledTasks>(() => "Scheduled tasks executing", () => "Scheduled tasks complete"))
{
if (_isPublishingRunning) return;
_isPublishingRunning = true;
try
{
ProcessTasks();
await ProcessTasksAsync(token);
}
catch (Exception ee)
{
LogHelper.Error<ScheduledTasks>("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
+7 -17
View File
@@ -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
/// </remarks>
internal sealed class Scheduler : ApplicationEventHandler
{
private static Timer _pingTimer;
private static BackgroundTaskRunner<IBackgroundTask> _keepAliveRunner;
private static BackgroundTaskRunner<IBackgroundTask> _publishingRunner;
private static BackgroundTaskRunner<IBackgroundTask> _tasksRunner;
private static BackgroundTaskRunner<IBackgroundTask> _scrubberRunner;
@@ -48,30 +44,24 @@ namespace Umbraco.Web.Scheduling
LogHelper.Debug<Scheduler>(() => "Initializing the scheduler");
// backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly
_keepAliveRunner = new BackgroundTaskRunner<IBackgroundTask>("KeepAlive");
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPublishing");
_tasksRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledTasks");
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>("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));
}
}
+1 -3
View File
@@ -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;
}
+16 -10
View File
@@ -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)
@@ -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',
+6 -6
View File
@@ -104,9 +104,9 @@
<Reference Include="AutoMapper.Net4">
<HintPath>..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll</HintPath>
</Reference>
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll</HintPath>
<Reference Include="ClientDependency.Core, Version=1.8.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="CookComputing.XmlRpcV2, Version=2.5.0.0, Culture=neutral, PublicKeyToken=a7d6e17aa302004d, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -115,9 +115,9 @@
<Reference Include="dotless.Core">
<HintPath>..\packages\dotless.1.4.1.0\lib\dotless.Core.dll</HintPath>
</Reference>
<Reference Include="Examine, Version=0.1.63.0, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="Examine, Version=0.1.65.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Examine.0.1.63.0\lib\Examine.dll</HintPath>
<HintPath>..\packages\Examine.0.1.65.0\lib\Examine.dll</HintPath>
</Reference>
<Reference Include="HtmlAgilityPack, Version=1.4.6.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -268,6 +268,7 @@
</Compile>
<Compile Include="ApplicationContextExtensions.cs" />
<Compile Include="AreaRegistrationContextExtensions.cs" />
<Compile Include="Scheduling\LatchedBackgroundTaskBase.cs" />
<Compile Include="Models\ContentExtensions.cs" />
<Compile Include="Models\LegacyConvertedNode.cs" />
<Compile Include="Models\LegacyConvertedNodeProperty.cs" />
@@ -500,7 +501,6 @@
<Compile Include="Routing\CustomRouteUrlProvider.cs" />
<Compile Include="Routing\UrlProviderExtensions.cs" />
<Compile Include="Scheduling\BackgroundTaskRunnerOptions.cs" />
<Compile Include="Scheduling\DelayedRecurringTaskBase.cs" />
<Compile Include="Scheduling\IBackgroundTaskRunner.cs" />
<Compile Include="Scheduling\ILatchedBackgroundTask.cs" />
<Compile Include="Scheduling\RecurringTaskBase.cs" />
+33 -24
View File
@@ -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<ApplicationContext>("ApplicationUrl: " + appctx.UmbracoApplicationUrl + " (UmbracoModule request)");
}
/// <summary>
/// Begins to process a request.
/// </summary>
/// <param name="httpContext"></param>
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<UmbracoModule>("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;
+9 -9
View File
@@ -1,24 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AutoMapper" version="3.0.0" targetFramework="net45" />
<package id="ClientDependency" version="1.8.3.1" targetFramework="net45" />
<package id="ClientDependency" version="1.8.4" targetFramework="net45" />
<package id="dotless" version="1.4.1.0" targetFramework="net45" />
<package id="Examine" version="0.1.63.0" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net40" />
<package id="Examine" version="0.1.65.0" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net4" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" targetFramework="net40" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net4" />
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" targetFramework="net4" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net4" />
<package id="Microsoft.AspNet.WebApi" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Client" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Core" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net4" />
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net45" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
<package id="MiniProfiler" version="2.1.0" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
<package id="UrlRewritingNet.UrlRewriter" version="2.0.60829.1" targetFramework="net40" />
<package id="xmlrpcnet" version="2.5.0" targetFramework="net40" />
<package id="UrlRewritingNet.UrlRewriter" version="2.0.60829.1" targetFramework="net4" />
<package id="xmlrpcnet" version="2.5.0" targetFramework="net4" />
</packages>
+129 -201
View File
@@ -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>("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<content>("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;
/// <summary>
/// Gets the path of the umbraco XML disk cache file.
/// </summary>
/// <value>The name of the umbraco XML disk cache file.</value>
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();
}
}
}
/// <summary>
/// Unpublishes the node.
/// </summary>
@@ -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("<!ATTLIST {0} id ID #REQUIRED>", 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("<!ELEMENT {1} ANY>{0}<!ATTLIST {1} id ID #REQUIRED>{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("<!ELEMENT {1} ANY>{0}<!ATTLIST {1} id ID #REQUIRED>{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<content>("Registering Unload handler for default app domain.");
AppDomain.CurrentDomain.ProcessExit += OnDomainUnloadReleaseFileLock;
}
else
{
LogHelper.Debug<content>("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<content>("Acquiring exclusive access to file for this AppDomain...");
_fileLocked = _fileLock.Lock(FileLockTimeoutMilliseconds);
LogHelper.Debug<content>("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<content>("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<content>("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<content>("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<int>("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<int>("sortOrder"));
return xml;
}
private static void TransferValuesFromDocumentXmlToPublishedXml(XmlNode documentNode, XmlNode publishedNode)
@@ -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())
@@ -537,8 +537,8 @@ namespace umbraco
/// <returns></returns>
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()))
@@ -67,7 +67,7 @@ namespace umbraco.cms.presentation.developer.RelationTypes.TreeMenu
/// </summary>
public string JsSource
{
get { return "/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js"; }
get { return "~/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js"; }
}
/// <summary>
@@ -67,7 +67,7 @@ namespace umbraco.cms.presentation.developer.RelationTypes.TreeMenu
/// </summary>
public string JsSource
{
get { return "/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.js"; }
get { return "~/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.js"; }
}
/// <summary>
@@ -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);
}
}
@@ -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<StaticField> indexFieldPolicies)
{
var attributeFields = set.IndexAttributeFields.Cast<IIndexField>().ToArray();
var userFields = set.IndexUserFields.Cast<IIndexField>().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<IIndexField>();
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<IIndexField>();
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);
}
/// <summary>
@@ -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<StaticField> 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<IIndexCriteria>(() =>
{
var attributeFields = set.IndexAttributeFields.Cast<IIndexField>().ToArray();
var userFields = set.IndexUserFields.Cast<IIndexField>().ToArray();
var includeNodeTypes = set.IncludeNodeTypes.Cast<IIndexField>().Select(x => x.Name).ToArray();
var excludeNodeTypes = set.ExcludeNodeTypes.Cast<IIndexField>().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<IIndexField>();
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<IIndexField>();
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<IIndexCriteria> _lazyCriteria;
public IEnumerable<string> ExcludeNodeTypes
{
get { return _lazyCriteria.Value.ExcludeNodeTypes; }
}
public IEnumerable<string> IncludeNodeTypes
{
get { return _lazyCriteria.Value.IncludeNodeTypes; }
}
public int? ParentNodeId
{
get { return _lazyCriteria.Value.ParentNodeId; }
}
public IEnumerable<IIndexField> StandardFields
{
get { return _lazyCriteria.Value.StandardFields; }
}
public IEnumerable<IIndexField> UserFields
{
get { return _lazyCriteria.Value.UserFields; }
}
}
}
+3 -2
View File
@@ -82,9 +82,9 @@
<AssemblyOriginatorKeyFile>..\Solution Items\TheFARM-Public.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Examine, Version=0.1.63.0, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="Examine, Version=0.1.65.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Examine.0.1.63.0\lib\Examine.dll</HintPath>
<HintPath>..\packages\Examine.0.1.65.0\lib\Examine.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -111,6 +111,7 @@
<ItemGroup>
<Compile Include="BaseUmbracoIndexer.cs" />
<Compile Include="Config\IndexSetExtensions.cs" />
<Compile Include="Config\LazyIndexCriteria.cs" />
<Compile Include="DataServices\IContentService.cs" />
<Compile Include="DataServices\IDataService.cs" />
<Compile Include="DataServices\ILogService.cs" />
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Examine" version="0.1.63.0" targetFramework="net45" />
<package id="Examine" version="0.1.65.0" targetFramework="net45" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
</packages>
+5 -5
View File
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Examine" version="0.1.63.0" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net40" />
<package id="Examine" version="0.1.65.0" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net4" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net4" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net4" />
<package id="Microsoft.AspNet.WebApi" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Client" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Core" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net4" />
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net45" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
@@ -45,9 +45,9 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Examine, Version=0.1.63.0, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="Examine, Version=0.1.65.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Examine.0.1.63.0\lib\Examine.dll</HintPath>
<HintPath>..\packages\Examine.0.1.65.0\lib\Examine.dll</HintPath>
</Reference>
<Reference Include="HtmlAgilityPack, Version=1.4.6.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -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
/// <returns></returns>
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
/// <returns></returns>
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
+2 -1
View File
@@ -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;
}
+4 -4
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.8.3.1" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net40" />
<package id="ClientDependency" version="1.8.4" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net4" />
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
<package id="Tidy.Net" version="1.0.0" targetFramework="net40" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net4" />
<package id="Tidy.Net" version="1.0.0" targetFramework="net4" />
</packages>
+3 -3
View File
@@ -106,9 +106,9 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="ClientDependency.Core, Version=1.8.3.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll</HintPath>
<Reference Include="ClientDependency.Core, Version=1.8.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="HtmlAgilityPack, Version=1.4.6.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.8.3.1" targetFramework="net45" />
<package id="ClientDependency" version="1.8.4" targetFramework="net45" />
</packages>
+3 -3
View File
@@ -68,9 +68,9 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="ClientDependency.Core, Version=1.8.3.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll</HintPath>
<Reference Include="ClientDependency.Core, Version=1.8.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.configuration" />
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.8.3.1" targetFramework="net45" />
<package id="ClientDependency" version="1.8.4" targetFramework="net45" />
</packages>
@@ -114,9 +114,9 @@
<Project>{651E1350-91B6-44B7-BD60-7207006D7003}</Project>
<Name>Umbraco.Web</Name>
</ProjectReference>
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll</HintPath>
<Reference Include="ClientDependency.Core, Version=1.8.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System">
<Name>System</Name>