Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f714d0b3e | |||
| 0bbcaca067 | |||
| 55d3d8437a | |||
| 9e2ab8507f | |||
| 6ee6c77d6e | |||
| 9b5d4e5b08 | |||
| 49f8b530b7 | |||
| 56176d0e36 | |||
| ab41c900da | |||
| 90645e5e85 | |||
| 872c345ac9 | |||
| 8baaae4da6 | |||
| ed05a3223f | |||
| 25e3134e35 | |||
| f1b9b98f5d | |||
| d2f7eda074 | |||
| 3062eedb4f | |||
| 1e3dd4c5cd | |||
| a7871abbd9 | |||
| bf3a08cf85 | |||
| 7bc208e490 | |||
| 6aa633c8c5 | |||
| 9be2f99e7b | |||
| 2d5bfc0fe2 | |||
| c01f864e37 | |||
| a8be796d68 | |||
| 4acbdbe896 | |||
| 34a9eef8b8 | |||
| 0f56fa4baa | |||
| 82a8404b2e | |||
| bce8affe16 | |||
| 4bbd8097a2 | |||
| da9c810d78 | |||
| 431d066300 | |||
| 6d50bd891a | |||
| 1d00e3c5d8 | |||
| e69422c8d8 | |||
| a277ac2fe4 | |||
| 1067b1e245 | |||
| 7987f13a11 | |||
| ccda1bdd73 | |||
| 76a6f2d2d0 | |||
| 69428b2281 | |||
| 7cd39918a0 | |||
| 9a626c9da6 | |||
| 0520797c09 | |||
| 9b85ba5b90 | |||
| ca738979ce | |||
| 8cfb56adf4 | |||
| 053346d79b | |||
| 044c9c95f8 | |||
| b23ea69b3e | |||
| 46f9c5ff05 | |||
| 3bc4b50d74 | |||
| 1a2849dfb0 | |||
| bcb5883230 | |||
| 18080c1cfb | |||
| 2600a3887e | |||
| 45bec2a67d | |||
| da7456fd2a | |||
| 275ba00e24 | |||
| d00bf4649d | |||
| 04976f8063 | |||
| 3e7c9fc81e | |||
| a1f4d7b16f | |||
| 811ef4a444 | |||
| 69ac621f5a | |||
| 610582df38 | |||
| 181f7c1819 | |||
| 73fbe7d78c | |||
| f100d82e2c | |||
| 60ce993c09 | |||
| c07021d2a0 | |||
| 744ee39904 | |||
| d669bf64c0 | |||
| ff277cd99c | |||
| 30cd303a13 | |||
| 60ce987507 | |||
| a231899052 | |||
| 7ddcccb0c5 | |||
| db824d8daf | |||
| a1a30240f6 | |||
| 7c4a189aa3 | |||
| 4ec66bc868 | |||
| fa8fe8ec66 | |||
| a76ba9a0a7 | |||
| b804ff6107 | |||
| 72046a6ab9 | |||
| ea2303c327 | |||
| 4c4117a052 | |||
| 521b8076d2 | |||
| 1def88d764 | |||
| 910c349f7c | |||
| 6cfb5bc678 | |||
| a3b5996372 | |||
| 31b4560daa | |||
| 2f2dd9c9b6 | |||
| 02599e1254 | |||
| ac7ff31a67 | |||
| 009f071f48 | |||
| e0ecb3291d | |||
| dbaf7c0190 | |||
| 16e06b6d4d | |||
| 444a028e30 | |||
| 0e5a469577 | |||
| 9d8f33e98e | |||
| 2abb3177af | |||
| 43f99e11a5 | |||
| d1d9878ab6 | |||
| e9753c72a7 | |||
| 3b791c75f6 | |||
| 35cb4d5690 | |||
| adde7c2f50 | |||
| 62943d391f | |||
| 0942eb839d | |||
| 21a4dfe33e | |||
| 8121fc42e5 | |||
| c356ed1df8 | |||
| 92f56ccedd | |||
| 7cbe2410e4 | |||
| 1dda3ce45d | |||
| 1e29a4a8ca | |||
| 01de0bd6a1 | |||
| 4d0d29c004 | |||
| 741c2bb085 | |||
| 2c809857af | |||
| 236d6ee302 | |||
| 3094dea5d4 | |||
| f829b0b16c | |||
| 5d1340e864 | |||
| c867f609f1 | |||
| 69879dc8f8 | |||
| f663a13391 |
@@ -26,13 +26,13 @@
|
||||
<dependency id="HtmlAgilityPack" version="[1.4.6, 2.0.0)" />
|
||||
<dependency id="Lucene.Net" version="[2.9.4.1, 3.0.0.0)" />
|
||||
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
|
||||
<dependency id="MySql.Data" version="[6.9.6, 7.0.0)" />
|
||||
<dependency id="MySql.Data" version="[6.9.7, 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,2 +1,2 @@
|
||||
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
|
||||
7.2.5
|
||||
7.2.8
|
||||
+2
-2
@@ -11,5 +11,5 @@ using System.Resources;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.2.5")]
|
||||
[assembly: AssemblyInformationalVersion("7.2.5")]
|
||||
[assembly: AssemblyFileVersion("7.2.8")]
|
||||
[assembly: AssemblyInformationalVersion("7.2.8")]
|
||||
@@ -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,57 +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;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the version configured matches the assembly version
|
||||
/// </summary>
|
||||
private bool Configured
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
string configStatus = ConfigurationStatus;
|
||||
string currentVersion = UmbracoVersion.Current.ToString(3);
|
||||
// try settings
|
||||
ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(this, UmbracoConfig.For.UmbracoSettings());
|
||||
|
||||
// and return what we have, may be null
|
||||
return _umbracoApplicationUrl;
|
||||
}
|
||||
set
|
||||
{
|
||||
_umbracoApplicationUrl = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentVersion != configStatus)
|
||||
{
|
||||
LogHelper.Info<ApplicationContext>("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'");
|
||||
}
|
||||
|
||||
internal string _umbracoApplicationUrl; // internal for tests
|
||||
|
||||
return (configStatus == currentVersion);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
{
|
||||
@@ -208,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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,15 @@ namespace Umbraco.Core
|
||||
// - this is NOT a reader/writer lock
|
||||
// - this is NOT a recursive lock
|
||||
//
|
||||
// using a named Semaphore here and not a Mutex because mutexes have thread
|
||||
// affinity which does not work with async situations
|
||||
//
|
||||
// it is important that managed code properly release the Semaphore before
|
||||
// going down else it will maintain the lock - however note that when the
|
||||
// whole process (w3wp.exe) goes down and all handles to the Semaphore have
|
||||
// been closed, the Semaphore system object is destroyed - so in any case
|
||||
// an iisreset should clean up everything
|
||||
//
|
||||
internal class AsyncLock
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore;
|
||||
@@ -54,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
|
||||
@@ -70,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)
|
||||
@@ -159,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Cache
|
||||
protected const string CacheItemPrefix = "umbrtmche";
|
||||
|
||||
// an object that represent a value that has not been created yet
|
||||
protected readonly object ValueNotCreated = new object();
|
||||
protected internal static readonly object ValueNotCreated = new object();
|
||||
|
||||
// manupulate the underlying cache entries
|
||||
// these *must* be called from within the appropriate locks
|
||||
@@ -30,21 +30,58 @@ namespace Umbraco.Core.Cache
|
||||
return string.Format("{0}-{1}", CacheItemPrefix, key);
|
||||
}
|
||||
|
||||
protected object GetSafeLazyValue(Lazy<object> lazy, bool onlyIfValueIsCreated = false)
|
||||
protected internal static Lazy<object> GetSafeLazy(Func<object> getCacheItem)
|
||||
{
|
||||
// try to generate the value and if it fails,
|
||||
// wrap in an ExceptionHolder - would be much simpler
|
||||
// to just use lazy.IsValueFaulted alas that field is
|
||||
// internal
|
||||
return new Lazy<object>(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return getCacheItem();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ExceptionHolder(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected internal static object GetSafeLazyValue(Lazy<object> lazy, bool onlyIfValueIsCreated = false)
|
||||
{
|
||||
// if onlyIfValueIsCreated, do not trigger value creation
|
||||
// must return something, though, to differenciate from null values
|
||||
if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
|
||||
|
||||
// if execution has thrown then lazy.IsValueCreated is false
|
||||
// and lazy.IsValueFaulted is true (but internal) so we use our
|
||||
// own exception holder (see Lazy<T> source code) to return null
|
||||
if (lazy.Value is ExceptionHolder) return null;
|
||||
|
||||
// we have a value and execution has not thrown so returning
|
||||
// here does not throw - unless we're re-entering, take care of it
|
||||
try
|
||||
{
|
||||
// if onlyIfValueIsCreated, do not trigger value creation
|
||||
// must return something, though, to differenciate from null values
|
||||
if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
|
||||
return lazy.Value;
|
||||
}
|
||||
catch
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
return null;
|
||||
throw new InvalidOperationException("The method that computes a value for the cache has tried to read that value from the cache.", e);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ExceptionHolder
|
||||
{
|
||||
public ExceptionHolder(Exception e)
|
||||
{
|
||||
Exception = e;
|
||||
}
|
||||
|
||||
public Exception Exception { get; private set; }
|
||||
}
|
||||
|
||||
#region Clear
|
||||
|
||||
public virtual void ClearAllCache()
|
||||
@@ -53,7 +90,7 @@ namespace Umbraco.Core.Cache
|
||||
{
|
||||
foreach (var entry in GetDictionaryEntries()
|
||||
.ToArray())
|
||||
RemoveEntry((string) entry.Key);
|
||||
RemoveEntry((string)entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,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()
|
||||
@@ -77,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()
|
||||
@@ -97,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)
|
||||
{
|
||||
@@ -119,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,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,20 +102,27 @@ 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 = new Lazy<object>(getCacheItem);
|
||||
result = GetSafeLazy(getCacheItem);
|
||||
ContextItems[cacheKey] = result;
|
||||
}
|
||||
}
|
||||
|
||||
// this may throw if getCacheItem throws, but this is the only place where
|
||||
// it would throw as everywhere else we use GetLazySaveValue() to hide exceptions
|
||||
// and pretend exceptions were never inserted into cache to begin with.
|
||||
return result.Value;
|
||||
// using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
|
||||
// exceptions (but try again and again) and silently eat them - however at
|
||||
// some point we have to report them - so need to re-throw here
|
||||
|
||||
// this does not throw anymore
|
||||
//return result.Value;
|
||||
|
||||
var value = result.Value; // will not throw (safe lazy)
|
||||
var eh = value as ExceptionHolder;
|
||||
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)
|
||||
@@ -129,19 +135,27 @@ namespace Umbraco.Core.Cache
|
||||
|
||||
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
||||
{
|
||||
result = new Lazy<object>(getCacheItem);
|
||||
result = GetSafeLazy(getCacheItem);
|
||||
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);
|
||||
|
||||
lck.UpgradeToWriteLock();
|
||||
//NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update!
|
||||
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
// this may throw if getCacheItem throws, but this is the only place where
|
||||
// it would throw as everywhere else we use GetLazySaveValue() to hide exceptions
|
||||
// and pretend exceptions were never inserted into cache to begin with.
|
||||
return result.Value;
|
||||
// using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
|
||||
// exceptions (but try again and again) and silently eat them - however at
|
||||
// some point we have to report them - so need to re-throw here
|
||||
|
||||
// this does not throw anymore
|
||||
//return result.Value;
|
||||
|
||||
value = result.Value; // will not throw (safe lazy)
|
||||
var eh = value as ExceptionHolder;
|
||||
if (eh != null) throw eh.Exception; // throw once!
|
||||
return value;
|
||||
}
|
||||
|
||||
public object GetCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
@@ -173,17 +187,18 @@ namespace Umbraco.Core.Cache
|
||||
// NOTE - here also we must insert a Lazy<object> but we can evaluate it right now
|
||||
// and make sure we don't store a null value.
|
||||
|
||||
var result = new Lazy<object>(getCacheItem);
|
||||
var result = GetSafeLazy(getCacheItem);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,30 +17,19 @@ namespace Umbraco.Core.Cache
|
||||
/// </summary>
|
||||
internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
|
||||
{
|
||||
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
internal ObjectCache MemoryCache;
|
||||
|
||||
// an object that represent a value that has not been created yet
|
||||
protected readonly object ValueNotCreated = new object();
|
||||
/// <summary>
|
||||
/// Used for debugging
|
||||
/// </summary>
|
||||
internal Guid InstanceId { get; private set; }
|
||||
|
||||
public ObjectCacheRuntimeCacheProvider()
|
||||
{
|
||||
MemoryCache = new MemoryCache("in-memory");
|
||||
}
|
||||
|
||||
protected object GetSafeLazyValue(Lazy<object> lazy, bool onlyIfValueIsCreated = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// if onlyIfValueIsCreated, do not trigger value creation
|
||||
// must return something, though, to differenciate from null values
|
||||
if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
|
||||
return lazy.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
InstanceId = Guid.NewGuid();
|
||||
}
|
||||
|
||||
#region Clear
|
||||
@@ -59,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
|
||||
@@ -72,8 +65,11 @@ namespace Umbraco.Core.Cache
|
||||
// x.Value is Lazy<object> and not null, its value may be null
|
||||
// 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);
|
||||
var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
|
||||
// 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
|
||||
@@ -85,15 +81,20 @@ 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 =>
|
||||
{
|
||||
// x.Value is Lazy<object> and not null, its value may be null
|
||||
// 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() == typeOfT;
|
||||
var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
|
||||
// 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
|
||||
@@ -106,16 +107,20 @@ namespace Umbraco.Core.Cache
|
||||
using (new WriteLock(_locker))
|
||||
{
|
||||
var typeOfT = typeof(T);
|
||||
var isInterface = typeOfT.IsInterface;
|
||||
foreach (var key in MemoryCache
|
||||
.Where(x =>
|
||||
{
|
||||
// x.Value is Lazy<object> and not null, its value may be null
|
||||
// remove null values as well, does not hurt
|
||||
// get non-created as NonCreatedValue & exceptions as null
|
||||
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
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
|
||||
@@ -132,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)
|
||||
@@ -144,7 +149,7 @@ namespace Umbraco.Core.Cache
|
||||
.Select(x => x.Key)
|
||||
.ToArray()) // ToArray required to remove
|
||||
MemoryCache.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -161,7 +166,7 @@ namespace Umbraco.Core.Cache
|
||||
.ToArray(); // evaluate while locked
|
||||
}
|
||||
return entries
|
||||
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
|
||||
.Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
|
||||
.Where(x => x != null) // backward compat, don't store null values in the cache
|
||||
.ToList();
|
||||
}
|
||||
@@ -176,7 +181,7 @@ namespace Umbraco.Core.Cache
|
||||
.ToArray(); // evaluate while locked
|
||||
}
|
||||
return entries
|
||||
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
|
||||
.Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
|
||||
.Where(x => x != null) // backward compat, don't store null values in the cache
|
||||
.ToList();
|
||||
}
|
||||
@@ -188,7 +193,7 @@ namespace Umbraco.Core.Cache
|
||||
{
|
||||
result = MemoryCache.Get(cacheKey) as Lazy<object>; // null if key not found
|
||||
}
|
||||
return result == null ? null : GetSafeLazyValue(result); // return exceptions as null
|
||||
return result == null ? null : DictionaryCacheProviderBase.GetSafeLazyValue(result); // return exceptions as null
|
||||
}
|
||||
|
||||
public object GetCacheItem(string cacheKey, Func<object> getCacheItem)
|
||||
@@ -196,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
|
||||
|
||||
@@ -205,17 +210,23 @@ namespace Umbraco.Core.Cache
|
||||
using (var lck = new UpgradeableReadLock(_locker))
|
||||
{
|
||||
result = MemoryCache.Get(cacheKey) as Lazy<object>;
|
||||
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
||||
if (result == null || DictionaryCacheProviderBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
||||
{
|
||||
result = new Lazy<object>(getCacheItem);
|
||||
result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem);
|
||||
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
|
||||
|
||||
lck.UpgradeToWriteLock();
|
||||
//NOTE: This does an add or update
|
||||
MemoryCache.Set(cacheKey, result, policy);
|
||||
}
|
||||
}
|
||||
|
||||
return result.Value;
|
||||
//return result.Value;
|
||||
|
||||
var value = result.Value; // will not throw (safe lazy)
|
||||
var eh = value as DictionaryCacheProviderBase.ExceptionHolder;
|
||||
if (eh != null) throw eh.Exception; // throw once!
|
||||
return value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -227,11 +238,12 @@ namespace Umbraco.Core.Cache
|
||||
// NOTE - here also we must insert a Lazy<object> but we can evaluate it right now
|
||||
// and make sure we don't store a null value.
|
||||
|
||||
var result = new Lazy<object>(getCacheItem);
|
||||
var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem);
|
||||
var value = result.Value; // force evaluation now
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -252,7 +264,7 @@ namespace Umbraco.Core.Cache
|
||||
{
|
||||
policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependentFiles.ToList()));
|
||||
}
|
||||
|
||||
|
||||
if (removedCallback != null)
|
||||
{
|
||||
policy.RemovedCallback = arguments =>
|
||||
@@ -283,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.5");
|
||||
private static readonly Version Version = new Version("7.2.8");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
|
||||
@@ -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.DebugDuration<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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,23 @@ namespace Umbraco.Core.IO
|
||||
return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root);
|
||||
}
|
||||
|
||||
[Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")]
|
||||
public static Attempt<string> TryResolveUrl(string virtualPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (virtualPath.StartsWith("~"))
|
||||
return Attempt.Succeed(virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/"));
|
||||
if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute))
|
||||
return Attempt.Succeed(virtualPath);
|
||||
return Attempt.Succeed(VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Attempt.Fail(virtualPath, ex);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")]
|
||||
internal static string ResolveUrlsFromTextString(string text)
|
||||
{
|
||||
if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
@@ -40,9 +41,12 @@ namespace Umbraco.Core.Logging
|
||||
/// <returns></returns>
|
||||
private static string PrefixThreadId(string generateMessageFormat)
|
||||
{
|
||||
return "[Thread " + Thread.CurrentThread.ManagedThreadId + "] " + 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>
|
||||
/// Adds an error log
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Macros
|
||||
internal class MacroTagParser
|
||||
{
|
||||
private static readonly Regex MacroRteContent = new Regex(@"(<!--\s*?)(<\?UMBRACO_MACRO.*?/>)(\s*?-->)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?</\?UMBRACO_MACRO>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO (?:.+?)?macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?</\?UMBRACO_MACRO>)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// This formats the persisted string to something useful for the rte so that the macro renders properly since we
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
+8
-3
@@ -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");
|
||||
}
|
||||
|
||||
+50
-5
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -15,6 +15,7 @@ using Umbraco.Core.Configuration;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.CodeAnnotations;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -59,6 +60,50 @@ namespace Umbraco.Core
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Based on the input string, this will detect if the strnig is a JS path or a JS snippet.
|
||||
/// If a path cannot be determined, then it is assumed to be a snippet the original text is returned
|
||||
/// with an invalid attempt, otherwise a valid attempt is returned with the resolved path
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is only used for legacy purposes for the Action.JsSource stuff and shouldn't be needed in v8
|
||||
/// </remarks>
|
||||
internal static Attempt<string> DetectIsJavaScriptPath(this string input)
|
||||
{
|
||||
//validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text
|
||||
//block instead.
|
||||
var isValid = true;
|
||||
|
||||
if (Uri.IsWellFormedUriString(input, UriKind.RelativeOrAbsolute))
|
||||
{
|
||||
//ok it validates, but so does alert('hello'); ! so we need to do more checks
|
||||
|
||||
//here are the valid chars in a url without escaping
|
||||
if (Regex.IsMatch(input, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]"))
|
||||
isValid = false;
|
||||
|
||||
//we'll have to be smarter and just check for certain js patterns now too!
|
||||
var jsPatterns = new[] { @"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"==" };
|
||||
if (jsPatterns.Any(p => Regex.IsMatch(input, p)))
|
||||
isValid = false;
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
var resolvedUrlResult = IOHelper.TryResolveUrl(input);
|
||||
//if the resolution was success, return it, otherwise just return the path, we've detected
|
||||
// it's a path but maybe it's relative and resolution has failed, etc... in which case we're just
|
||||
// returning what was given to us.
|
||||
return resolvedUrlResult.Success
|
||||
? resolvedUrlResult
|
||||
: Attempt.Succeed(input);
|
||||
}
|
||||
}
|
||||
|
||||
return Attempt.Fail(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This tries to detect a json string, this is not a fail safe way but it is quicker than doing
|
||||
/// a try/catch when deserializing when it is not json.
|
||||
|
||||
@@ -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('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
</Reference>
|
||||
<Reference Include="MySql.Data">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\MySql.Data.6.9.6\lib\net45\MySql.Data.dll</HintPath>
|
||||
<HintPath>..\packages\MySql.Data.6.9.7\lib\net45\MySql.Data.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="AutoMapper" version="3.0.0" targetFramework="net45" userInstalled="true" />
|
||||
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net45" userInstalled="true" />
|
||||
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net4" userInstalled="true" />
|
||||
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net4" userInstalled="true" />
|
||||
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net4" userInstalled="true" />
|
||||
<package id="Microsoft.AspNet.WebApi.Client" 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.Bcl" version="1.1.9" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.Net.Http" version="2.2.22" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net4" 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="net4" userInstalled="true" />
|
||||
<package id="SqlServerCE" version="4.0.0.0" targetFramework="net4" userInstalled="true" />
|
||||
<package id="AutoMapper" version="3.0.0" targetFramework="net45" />
|
||||
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net45" />
|
||||
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net4" />
|
||||
<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.Client" version="4.0.30506.0" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net4" />
|
||||
<package id="Microsoft.Bcl" version="1.1.9" targetFramework="net45" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net45" />
|
||||
<package id="Microsoft.Net.Http" version="2.2.22" targetFramework="net45" />
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net4" />
|
||||
<package id="MiniProfiler" version="2.1.0" targetFramework="net45" />
|
||||
<package id="MySql.Data" version="6.9.7" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net4" />
|
||||
<package id="SqlServerCE" version="4.0.0.0" targetFramework="net4" />
|
||||
</packages>
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Web.UI;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Cache;
|
||||
@@ -21,6 +22,83 @@ namespace Umbraco.Tests.Cache
|
||||
public virtual void TearDown()
|
||||
{
|
||||
Provider.ClearAllCache();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Throws_On_Reentry()
|
||||
{
|
||||
// don't run for StaticCacheProvider - not making sense
|
||||
if (GetType() == typeof (StaticCacheProviderTests))
|
||||
Assert.Ignore("Do not run for StaticCacheProvider.");
|
||||
|
||||
Exception exception = null;
|
||||
var result = Provider.GetCacheItem("blah", () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var result2 = Provider.GetCacheItem("blah");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exception = e;
|
||||
}
|
||||
return "value";
|
||||
});
|
||||
Assert.IsNotNull(exception);
|
||||
Assert.IsAssignableFrom<InvalidOperationException>(exception);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Cache_Exceptions()
|
||||
{
|
||||
var counter = 0;
|
||||
|
||||
object result;
|
||||
try
|
||||
{
|
||||
result = Provider.GetCacheItem("Blah", () =>
|
||||
{
|
||||
counter++;
|
||||
throw new Exception("Do not cache this");
|
||||
});
|
||||
}
|
||||
catch (Exception){}
|
||||
|
||||
try
|
||||
{
|
||||
result = Provider.GetCacheItem("Blah", () =>
|
||||
{
|
||||
counter++;
|
||||
throw new Exception("Do not cache this");
|
||||
});
|
||||
}
|
||||
catch (Exception){}
|
||||
|
||||
Assert.Greater(counter, 1);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Ensures_Delegate_Result_Is_Cached_Once()
|
||||
{
|
||||
var counter = 0;
|
||||
|
||||
object result;
|
||||
|
||||
result = Provider.GetCacheItem("Blah", () =>
|
||||
{
|
||||
counter++;
|
||||
return "";
|
||||
});
|
||||
|
||||
result = Provider.GetCacheItem("Blah", () =>
|
||||
{
|
||||
counter++;
|
||||
return "";
|
||||
});
|
||||
|
||||
Assert.AreEqual(counter, 1);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Web;
|
||||
using System;
|
||||
using System.Web;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Cache;
|
||||
|
||||
@@ -29,5 +30,29 @@ namespace Umbraco.Tests.Cache
|
||||
{
|
||||
get { return _provider; }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DoesNotCacheExceptions()
|
||||
{
|
||||
string value;
|
||||
Assert.Throws<Exception>(() => { value = (string)_provider.GetCacheItem("key", () => GetValue(1)); });
|
||||
Assert.Throws<Exception>(() => { value = (string)_provider.GetCacheItem("key", () => GetValue(2)); });
|
||||
|
||||
// does not throw
|
||||
value = (string)_provider.GetCacheItem("key", () => GetValue(3));
|
||||
Assert.AreEqual("succ3", value);
|
||||
|
||||
// cache
|
||||
value = (string)_provider.GetCacheItem("key", () => GetValue(4));
|
||||
Assert.AreEqual("succ3", value);
|
||||
}
|
||||
|
||||
private static string GetValue(int i)
|
||||
{
|
||||
Console.WriteLine("get" + i);
|
||||
if (i < 3)
|
||||
throw new Exception("fail");
|
||||
return "succ" + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web.Editors;
|
||||
|
||||
namespace Umbraco.Tests.Controllers
|
||||
{
|
||||
[TestFixture]
|
||||
public class BackOfficeControllerUnitTests
|
||||
{
|
||||
public static object[] TestLegacyJsActionPaths = new object[] {
|
||||
new string[]
|
||||
{
|
||||
"alert('hello');",
|
||||
"function test() { window.location = 'http://www.google.com'; }",
|
||||
"function openCourierSecurity(userid){ UmbClientMgr.contentFrame('page?userid=123); }",
|
||||
@"function openRepository(repo, folder){ UmbClientMgr.contentFrame('page?repo=repo&folder=folder); }
|
||||
function openTransfer(revision, repo, folder){ UmbClientMgr.contentFrame('page?revision=revision&repo=repo&folder=folder); }",
|
||||
"umbraco/js/test.js",
|
||||
"/umbraco/js/test.js",
|
||||
"~/umbraco/js/test.js"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
[TestCaseSource("TestLegacyJsActionPaths")]
|
||||
public void Separates_Legacy_JsActions_By_Block_Or_Url(object[] jsActions)
|
||||
{
|
||||
var jsBlocks =
|
||||
BackOfficeController.GetLegacyActionJsForActions(BackOfficeController.LegacyJsActionType.JsBlock,
|
||||
jsActions.Select(n => n.ToString()));
|
||||
|
||||
var jsUrls =
|
||||
BackOfficeController.GetLegacyActionJsForActions(BackOfficeController.LegacyJsActionType.JsUrl,
|
||||
jsActions.Select(n => n.ToString()));
|
||||
|
||||
Assert.That(jsBlocks.Count() == 4);
|
||||
Assert.That(jsUrls.Count() == 3);
|
||||
Assert.That(jsUrls.Last().StartsWith("~/") == false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,19 @@ namespace Umbraco.Tests.CoreStrings
|
||||
ShortStringHelperResolver.Reset();
|
||||
}
|
||||
|
||||
[TestCase("alert('hello');", false)]
|
||||
[TestCase("~/Test.js", true)]
|
||||
[TestCase("../Test.js", true)]
|
||||
[TestCase("/Test.js", true)]
|
||||
[TestCase("Test.js", true)]
|
||||
[TestCase("Test.js==", false)]
|
||||
[TestCase("/Test.js function(){return true;}", false)]
|
||||
public void Detect_Is_JavaScript_Path(string input, bool result)
|
||||
{
|
||||
var output = input.DetectIsJavaScriptPath();
|
||||
Assert.AreEqual(result, output.Success);
|
||||
}
|
||||
|
||||
[TestCase("hello.txt", "hello")]
|
||||
[TestCase("this.is.a.Txt", "this.is.a")]
|
||||
[TestCase("this.is.not.a. Txt", "this.is.not.a. Txt")]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Tests.IO
|
||||
@@ -13,11 +14,20 @@ namespace Umbraco.Tests.IO
|
||||
public class IOHelperTest
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void IOHelper_ResolveUrl()
|
||||
[TestCase("~/Scripts", "/Scripts", null)]
|
||||
[TestCase("/Scripts", "/Scripts", null)]
|
||||
[TestCase("../Scripts", "/Scripts", typeof(ArgumentException))]
|
||||
public void IOHelper_ResolveUrl(string input, string expected, Type expectedExceptionType)
|
||||
{
|
||||
var result = IOHelper.ResolveUrl("~/Scripts");
|
||||
Assert.AreEqual("/Scripts", result);
|
||||
if (expectedExceptionType != null)
|
||||
{
|
||||
Assert.Throws(expectedExceptionType, () => IOHelper.ResolveUrl(input));
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = IOHelper.ResolveUrl(input);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -109,6 +109,120 @@ namespace Umbraco.Tests.Macros
|
||||
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Format_RTE_Data_For_Editor_With_Params_When_MacroAlias_Not_First()
|
||||
{
|
||||
var content = @"<p>asdfasdf</p>
|
||||
<p>asdfsadf</p>
|
||||
<?UMBRACO_MACRO test1=""value1"" test2=""value2"" macroAlias=""Map"" />
|
||||
<p>asdfasdf</p>";
|
||||
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
|
||||
|
||||
// Assert.AreEqual(@"<p>asdfasdf</p>
|
||||
//<p>asdfsadf</p>
|
||||
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
//<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
|
||||
//<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
//<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
|
||||
Assert.AreEqual(@"<p>asdfasdf</p>
|
||||
<p>asdfsadf</p>
|
||||
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
<!-- <?UMBRACO_MACRO test1=""value1"" test2=""value2"" macroAlias=""Map"" /> -->
|
||||
<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Format_RTE_Data_For_Editor_With_Params_When_MacroAlias_Is_First()
|
||||
{
|
||||
|
||||
var content = @"<p>asdfasdf</p>
|
||||
<p>asdfsadf</p>
|
||||
<?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" />
|
||||
<p>asdfasdf</p>";
|
||||
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
|
||||
|
||||
// Assert.AreEqual(@"<p>asdfasdf</p>
|
||||
//<p>asdfsadf</p>
|
||||
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
//<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
|
||||
//<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
//<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
|
||||
Assert.AreEqual(@"<p>asdfasdf</p>
|
||||
<p>asdfsadf</p>
|
||||
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
|
||||
<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Format_RTE_Data_For_Editor_With_Params_When_Multiple_Macros()
|
||||
{
|
||||
var content = @"<p>asdfasdf</p>
|
||||
<p>asdfsadf</p>
|
||||
<?UMBRACO_MACRO test1=""value1"" test2=""value2"" macroAlias=""Map"" />
|
||||
<p>asdfsadf</p>
|
||||
<?UMBRACO_MACRO test1=""value1"" macroAlias=""Map"" test2=""value2"" />
|
||||
<p>asdfsadf</p>
|
||||
<?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" />
|
||||
<p>asdfasdf</p>";
|
||||
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
|
||||
|
||||
// Assert.AreEqual(@"<p>asdfasdf</p>
|
||||
//<p>asdfsadf</p>
|
||||
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
//<!-- <?UMBRACO_MACRO test1=""value1"" test2=""value2"" macroAlias=""Map"" /> -->
|
||||
//<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
//<p>asdfsadf</p>
|
||||
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
//<!-- <?UMBRACO_MACRO test1=""value1"" macroAlias=""Map"" test2=""value2"" /> -->
|
||||
//<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
//<p>asdfsadf</p>
|
||||
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
//<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
|
||||
//<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
//<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
|
||||
|
||||
Assert.AreEqual(@"<p>asdfasdf</p>
|
||||
<p>asdfsadf</p>
|
||||
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
<!-- <?UMBRACO_MACRO test1=""value1"" test2=""value2"" macroAlias=""Map"" /> -->
|
||||
<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
<p>asdfsadf</p>
|
||||
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
<!-- <?UMBRACO_MACRO test1=""value1"" macroAlias=""Map"" test2=""value2"" /> -->
|
||||
<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
<p>asdfsadf</p>
|
||||
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
|
||||
<ins>Macro alias: <strong>Map</strong></ins></div>
|
||||
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Format_RTE_Data_For_Editor_With_Multiple_Macros()
|
||||
{
|
||||
var content = @"<p>asdfasdf</p>
|
||||
<?UMBRACO_MACRO macroAlias=""Breadcrumb"" />
|
||||
<p>asdfsadf</p>
|
||||
<p> </p>
|
||||
<?UMBRACO_MACRO macroAlias=""login"" />
|
||||
<p> </p>";
|
||||
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>());
|
||||
|
||||
Assert.AreEqual(@"<p>asdfasdf</p>
|
||||
<div class=""umb-macro-holder mceNonEditable"">
|
||||
<!-- <?UMBRACO_MACRO macroAlias=""Breadcrumb"" /> -->
|
||||
<ins>Macro alias: <strong>Breadcrumb</strong></ins></div>
|
||||
<p>asdfsadf</p>
|
||||
<p> </p>
|
||||
<div class=""umb-macro-holder mceNonEditable"">
|
||||
<!-- <?UMBRACO_MACRO macroAlias=""login"" /> -->
|
||||
<ins>Macro alias: <strong>login</strong></ins></div>
|
||||
<p> </p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Format_RTE_Data_For_Editor_With_Params_Closing_Tag()
|
||||
{
|
||||
|
||||
@@ -21,17 +21,29 @@ namespace Umbraco.Tests.Scheduling
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
}
|
||||
|
||||
/*
|
||||
[Test]
|
||||
public async void ShutdownWaitWhenRunning()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true }))
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions
|
||||
{
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
Thread.Sleep(800); // for long
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
AutoStart = true,
|
||||
KeepAlive = true
|
||||
}))
|
||||
{
|
||||
var stopped = false;
|
||||
runner.Stopped += (sender, args) => { stopped = true; };
|
||||
|
||||
Assert.IsTrue(runner.IsRunning); // because AutoStart is true
|
||||
Thread.Sleep(500); // and because KeepAlive is true...
|
||||
Assert.IsTrue(runner.IsRunning); // ...it keeps running
|
||||
|
||||
runner.Shutdown(false, true); // -force +wait
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
Assert.IsTrue(runner.IsCompleted);
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
|
||||
Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner
|
||||
Assert.IsFalse(runner.IsRunning); // no more running tasks
|
||||
Assert.IsTrue(stopped);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,38 +56,121 @@ namespace Umbraco.Tests.Scheduling
|
||||
|
||||
runner.TaskStarting += (sender, args) => Console.WriteLine("starting {0}", DateTime.Now);
|
||||
runner.TaskCompleted += (sender, args) => Console.WriteLine("completed {0}", DateTime.Now);
|
||||
runner.Stopped += (sender, args) => Console.WriteLine("stopped {0}", DateTime.Now);
|
||||
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
Assert.IsFalse(runner.IsRunning); // because AutoStart is false
|
||||
|
||||
Console.WriteLine("Adding task {0}", DateTime.Now);
|
||||
runner.Add(new MyTask(5000));
|
||||
Thread.Sleep(500);
|
||||
Assert.IsTrue(runner.IsRunning); // is running the task
|
||||
|
||||
Console.WriteLine("Shutting down {0}", DateTime.Now);
|
||||
runner.Shutdown(false, false); // -force -wait
|
||||
Assert.IsTrue(runner.IsCompleted);
|
||||
|
||||
Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner
|
||||
Assert.IsTrue(runner.IsRunning); // still running that task
|
||||
Thread.Sleep(3000); // wait slightly less than the task takes to complete
|
||||
Assert.IsTrue(runner.IsRunning); // still running that task
|
||||
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
|
||||
Console.WriteLine("End {0}", DateTime.Now);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
[Test]
|
||||
public async void ShutdownWhenRunningWithWait()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
var stopped = false;
|
||||
runner.Stopped += (sender, args) => { stopped = true; };
|
||||
|
||||
Assert.IsFalse(runner.IsRunning); // because AutoStart is false
|
||||
runner.Add(new MyTask(5000));
|
||||
Assert.IsTrue(runner.IsRunning); // is running the task
|
||||
|
||||
runner.Shutdown(false, true); // -force +wait
|
||||
|
||||
// all this before we await because +wait
|
||||
Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner
|
||||
Assert.IsFalse(runner.IsRunning); // no more running tasks
|
||||
Assert.IsTrue(stopped);
|
||||
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async void ShutdownWhenRunningWithoutWait()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
var stopped = false;
|
||||
runner.Stopped += (sender, args) => { stopped = true; };
|
||||
|
||||
Assert.IsFalse(runner.IsRunning); // because AutoStart is false
|
||||
runner.Add(new MyTask(5000));
|
||||
Assert.IsTrue(runner.IsRunning); // is running the task
|
||||
|
||||
runner.Shutdown(false, false); // -force +wait
|
||||
|
||||
// all this before we await because -wait
|
||||
Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner
|
||||
Assert.IsTrue(runner.IsRunning); // still running the task
|
||||
Assert.IsFalse(stopped);
|
||||
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
|
||||
// and then...
|
||||
Assert.IsFalse(runner.IsRunning); // no more running tasks
|
||||
Assert.IsTrue(stopped);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async void ShutdownCompletesTheRunner()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
Assert.IsFalse(runner.IsRunning); // because AutoStart is false
|
||||
|
||||
// shutdown -force => run all queued tasks
|
||||
runner.Shutdown(false, false); // -force -wait
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
|
||||
Assert.IsFalse(runner.IsRunning); // still not running anything
|
||||
Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner
|
||||
|
||||
// cannot add tasks to it anymore
|
||||
Assert.IsFalse(runner.TryAdd(new MyTask()));
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
runner.Add(new MyTask());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async void ShutdownFlushesTheQueue()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
MyTask t;
|
||||
|
||||
Assert.IsFalse(runner.IsRunning); // because AutoStart is false
|
||||
runner.Add(new MyTask(5000));
|
||||
runner.Add(new MyTask());
|
||||
var t = new MyTask();
|
||||
runner.Add(t);
|
||||
Assert.IsTrue(runner.IsRunning); // is running the first task
|
||||
runner.Add(t = new MyTask());
|
||||
Assert.IsTrue(runner.IsRunning); // is running tasks
|
||||
|
||||
// shutdown -force => run all queued tasks
|
||||
runner.Shutdown(false, false); // -force -wait
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
Assert.IsTrue(runner.IsRunning); // is running tasks
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
|
||||
Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run
|
||||
}
|
||||
}
|
||||
@@ -85,15 +180,20 @@ namespace Umbraco.Tests.Scheduling
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
MyTask t;
|
||||
|
||||
Assert.IsFalse(runner.IsRunning); // because AutoStart is false
|
||||
runner.Add(new MyTask(5000));
|
||||
runner.Add(new MyTask());
|
||||
var t = new MyTask();
|
||||
runner.Add(t);
|
||||
Assert.IsTrue(runner.IsRunning); // is running the first task
|
||||
runner.Add(t = new MyTask());
|
||||
Assert.IsTrue(runner.IsRunning); // is running tasks
|
||||
|
||||
// shutdown +force => tries to cancel the current task, ignores queued tasks
|
||||
runner.Shutdown(true, false); // +force -wait
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
Assert.AreEqual(DateTime.MinValue, t.Ended); // t has not run
|
||||
Assert.IsTrue(runner.IsRunning); // is running that long task it cannot cancel
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
|
||||
Assert.AreEqual(DateTime.MinValue, t.Ended); // t has *not* run
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,23 +202,106 @@ namespace Umbraco.Tests.Scheduling
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
|
||||
Assert.IsFalse(runner.IsRunning); // because AutoStart is false
|
||||
runner.Add(new MyTask(5000));
|
||||
runner.Add(new MyTask());
|
||||
runner.Add(new MyTask());
|
||||
Assert.IsTrue(runner.IsRunning); // is running the task
|
||||
Assert.IsTrue(runner.IsRunning); // is running tasks
|
||||
|
||||
// shutdown -force => run all queued tasks
|
||||
runner.Shutdown(false, false); // -force -wait
|
||||
Assert.IsTrue(runner.IsCompleted);
|
||||
Assert.IsTrue(runner.IsRunning); // still running that task
|
||||
|
||||
Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner
|
||||
Assert.IsTrue(runner.IsRunning); // still running a task
|
||||
Thread.Sleep(3000);
|
||||
Assert.IsTrue(runner.IsRunning); // still running that task
|
||||
Assert.IsTrue(runner.IsRunning); // still running a task
|
||||
|
||||
// shutdown +force => tries to cancel the current task, ignores queued tasks
|
||||
runner.Shutdown(true, false); // +force -wait
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async void HostingStopNonImmediate()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
MyTask t;
|
||||
|
||||
var stopped = false;
|
||||
runner.Stopped += (sender, args) => { stopped = true; };
|
||||
var terminating = false;
|
||||
runner.Terminating += (sender, args) => { terminating = true; };
|
||||
var terminated = false;
|
||||
runner.Terminated += (sender, args) => { terminated = true; };
|
||||
|
||||
Assert.IsFalse(runner.IsRunning); // because AutoStart is false
|
||||
runner.Add(new MyTask(5000));
|
||||
runner.Add(new MyTask());
|
||||
runner.Add(t = new MyTask());
|
||||
Assert.IsTrue(runner.IsRunning); // is running the task
|
||||
|
||||
runner.Stop(false); // -immediate = -force, -wait
|
||||
Assert.IsTrue(terminating); // has raised that event
|
||||
Assert.IsFalse(terminated); // but not terminated yet
|
||||
|
||||
// all this before we await because -wait
|
||||
Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner
|
||||
Assert.IsTrue(runner.IsRunning); // still running the task
|
||||
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
Assert.IsTrue(stopped);
|
||||
|
||||
await runner.TerminatedAwaitable; // runner terminates, within test's timeout
|
||||
Assert.IsTrue(terminated); // has raised that event
|
||||
|
||||
Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Create_IsRunning()
|
||||
public async void HostingStopImmediate()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
MyTask t;
|
||||
|
||||
var stopped = false;
|
||||
runner.Stopped += (sender, args) => { stopped = true; };
|
||||
var terminating = false;
|
||||
runner.Terminating += (sender, args) => { terminating = true; };
|
||||
var terminated = false;
|
||||
runner.Terminated += (sender, args) => { terminated = true; };
|
||||
|
||||
Assert.IsFalse(runner.IsRunning); // because AutoStart is false
|
||||
runner.Add(new MyTask(5000));
|
||||
runner.Add(new MyTask());
|
||||
runner.Add(t = new MyTask());
|
||||
Assert.IsTrue(runner.IsRunning); // is running the task
|
||||
|
||||
runner.Stop(true); // +immediate = +force, +wait
|
||||
Assert.IsTrue(terminating); // has raised that event
|
||||
Assert.IsTrue(terminated); // and that event
|
||||
Assert.IsTrue(stopped); // and that one
|
||||
|
||||
// and all this before we await because +wait
|
||||
Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner
|
||||
Assert.IsFalse(runner.IsRunning); // done running
|
||||
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
await runner.TerminatedAwaitable; // runner terminates, within test's timeout
|
||||
|
||||
Assert.AreEqual(DateTime.MinValue, t.Ended); // t has *not* run
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Create_IsNotRunning()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
@@ -126,14 +309,17 @@ namespace Umbraco.Tests.Scheduling
|
||||
}
|
||||
}
|
||||
|
||||
[Ignore]
|
||||
|
||||
[Test]
|
||||
public async void Create_AutoStart_IsRunning()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions { AutoStart = true }))
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions
|
||||
{
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
AutoStart = true
|
||||
}))
|
||||
{
|
||||
Assert.IsTrue(runner.IsRunning); // because AutoStart is true
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +328,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true }))
|
||||
{
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
Assert.IsTrue(runner.IsRunning); // because AutoStart is true
|
||||
Thread.Sleep(800); // for long
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
// dispose will stop it
|
||||
@@ -159,8 +345,13 @@ namespace Umbraco.Tests.Scheduling
|
||||
// dispose will stop it
|
||||
}
|
||||
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // runner stops, within test's timeout
|
||||
//await runner.TerminatedAwaitable; // NO! see note below
|
||||
Assert.Throws<InvalidOperationException>(() => runner.Add(new MyTask()));
|
||||
|
||||
// but do NOT await on TerminatedAwaitable - disposing just shuts the runner down
|
||||
// so that we don't have a runaway task in tests, etc - but it does NOT terminate
|
||||
// the runner - it really is NOT a nice way to end a runner - it's there for tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -188,7 +379,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
runner.Add(new MyTask());
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
waitHandle.WaitOne();
|
||||
await runner; //since we are not being kept alive, it will quit
|
||||
await runner.StoppedAwaitable; //since we are not being kept alive, it will quit
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
}
|
||||
}
|
||||
@@ -221,11 +412,11 @@ namespace Umbraco.Tests.Scheduling
|
||||
runner.Add(task);
|
||||
await runner.CurrentThreadingTask; // wait for the Task operation to complete
|
||||
Assert.IsTrue(task.Ended != default(DateTime)); // task is done
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
[Ignore]
|
||||
|
||||
[Test]
|
||||
public async void WaitOnRunner_Tasks()
|
||||
{
|
||||
@@ -237,7 +428,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
{
|
||||
tasks.ForEach(runner.Add);
|
||||
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
|
||||
// check that tasks are done
|
||||
Assert.IsTrue(tasks.All(x => x.Ended != default(DateTime)));
|
||||
@@ -260,7 +451,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
runner.Add(task);
|
||||
waitHandle.WaitOne(); // wait 'til task is done
|
||||
Assert.IsTrue(task.Ended != default(DateTime)); // task is done
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +471,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
|
||||
Assert.IsTrue(tasks.All(x => x.Key.Ended != default(DateTime)));
|
||||
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +573,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
}
|
||||
}
|
||||
|
||||
[Ignore]
|
||||
|
||||
[Test]
|
||||
public void RecurringTaskTest()
|
||||
{
|
||||
@@ -390,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);
|
||||
@@ -413,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +612,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
var task = new MyDelayedTask(200);
|
||||
var task = new MyDelayedTask(200, false);
|
||||
runner.Add(task);
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
Thread.Sleep(5000);
|
||||
@@ -431,7 +621,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
task.Release();
|
||||
await runner.CurrentThreadingTask; //wait for current task to complete
|
||||
Assert.IsTrue(task.HasRun);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,19 +630,19 @@ namespace Umbraco.Tests.Scheduling
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
var task = new MyDelayedTask(200);
|
||||
var task = new MyDelayedTask(200, true);
|
||||
runner.Add(task);
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
Thread.Sleep(5000);
|
||||
Assert.IsTrue(runner.IsRunning); // still waiting for the task to release
|
||||
Assert.IsFalse(task.HasRun);
|
||||
runner.Shutdown(false, false);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
Assert.IsTrue(task.HasRun);
|
||||
}
|
||||
}
|
||||
|
||||
[Ignore]
|
||||
|
||||
[Test]
|
||||
public void DelayedRecurring()
|
||||
{
|
||||
@@ -460,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);
|
||||
@@ -478,6 +665,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
|
||||
waitHandle.WaitOne();
|
||||
Assert.GreaterOrEqual(runCount, 4);
|
||||
Assert.IsTrue(task.HasRun);
|
||||
|
||||
// stops recurring
|
||||
runner.Shutdown(false, false);
|
||||
@@ -492,10 +680,27 @@ namespace Umbraco.Tests.Scheduling
|
||||
var exceptions = new ConcurrentQueue<Exception>();
|
||||
runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception);
|
||||
|
||||
var task = new MyFailingTask(false); // -async
|
||||
var task = new MyFailingTask(false, true, false); // -async, +running, -disposing
|
||||
runner.Add(task);
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
|
||||
Assert.AreEqual(1, exceptions.Count); // traced and reported
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async void FailingTaskDisposing()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
var exceptions = new ConcurrentQueue<Exception>();
|
||||
runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception);
|
||||
|
||||
var task = new MyFailingTask(false, false, true); // -async, -running, +disposing
|
||||
runner.Add(task);
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
|
||||
Assert.AreEqual(1, exceptions.Count); // traced and reported
|
||||
}
|
||||
@@ -509,10 +714,27 @@ namespace Umbraco.Tests.Scheduling
|
||||
var exceptions = new ConcurrentQueue<Exception>();
|
||||
runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception);
|
||||
|
||||
var task = new MyFailingTask(true); // +async
|
||||
var task = new MyFailingTask(true, true, false); // +async, +running, -disposing
|
||||
runner.Add(task);
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
|
||||
Assert.AreEqual(1, exceptions.Count); // traced and reported
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async void FailingTaskDisposingAsync()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
var exceptions = new ConcurrentQueue<Exception>();
|
||||
runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception);
|
||||
|
||||
var task = new MyFailingTask(false, false, true); // -async, -running, +disposing
|
||||
runner.Add(task);
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
await runner.StoppedAwaitable; // wait for the entire runner operation to complete
|
||||
|
||||
Assert.AreEqual(1, exceptions.Count); // traced and reported
|
||||
}
|
||||
@@ -521,22 +743,28 @@ namespace Umbraco.Tests.Scheduling
|
||||
private class MyFailingTask : IBackgroundTask
|
||||
{
|
||||
private readonly bool _isAsync;
|
||||
private readonly bool _running;
|
||||
private readonly bool _disposing;
|
||||
|
||||
public MyFailingTask(bool isAsync)
|
||||
public MyFailingTask(bool isAsync, bool running, bool disposing)
|
||||
{
|
||||
_isAsync = isAsync;
|
||||
_running = running;
|
||||
_disposing = disposing;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
throw new Exception("Task has thrown.");
|
||||
if (_running)
|
||||
throw new Exception("Task has thrown.");
|
||||
}
|
||||
|
||||
public async Task RunAsync(CancellationToken token)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
throw new Exception("Task has thrown.");
|
||||
if (_running)
|
||||
throw new Exception("Task has thrown.");
|
||||
}
|
||||
|
||||
public bool IsAsync
|
||||
@@ -544,19 +772,19 @@ namespace Umbraco.Tests.Scheduling
|
||||
get { return _isAsync; }
|
||||
}
|
||||
|
||||
// fixme - must also test what happens if we throw on dispose!
|
||||
public void Dispose()
|
||||
{ }
|
||||
{
|
||||
if (_disposing)
|
||||
throw new Exception("Task has thrown.");
|
||||
}
|
||||
}
|
||||
|
||||
private class MyDelayedRecurringTask : DelayedRecurringTaskBase<MyDelayedRecurringTask>
|
||||
private class MyDelayedRecurringTask : RecurringTaskBase
|
||||
{
|
||||
public MyDelayedRecurringTask(IBackgroundTaskRunner<MyDelayedRecurringTask> runner, int delayMilliseconds, int periodMilliseconds)
|
||||
: base(runner, delayMilliseconds, periodMilliseconds)
|
||||
{ }
|
||||
public bool HasRun { get; private set; }
|
||||
|
||||
private MyDelayedRecurringTask(MyDelayedRecurringTask source)
|
||||
: base(source)
|
||||
public MyDelayedRecurringTask(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds)
|
||||
: base(runner, delayMilliseconds, periodMilliseconds)
|
||||
{ }
|
||||
|
||||
public override bool IsAsync
|
||||
@@ -564,49 +792,45 @@ namespace Umbraco.Tests.Scheduling
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override void PerformRun()
|
||||
public override bool PerformRun()
|
||||
{
|
||||
// nothing to do at the moment
|
||||
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
|
||||
{
|
||||
private readonly int _runMilliseconds;
|
||||
private readonly ManualResetEvent _gate;
|
||||
private readonly ManualResetEventSlim _gate;
|
||||
|
||||
public bool HasRun { get; private set; }
|
||||
|
||||
public MyDelayedTask(int runMilliseconds)
|
||||
public MyDelayedTask(int runMilliseconds, bool runsOnShutdown)
|
||||
{
|
||||
_runMilliseconds = runMilliseconds;
|
||||
_gate = new ManualResetEvent(false);
|
||||
_gate = new ManualResetEventSlim(false);
|
||||
RunsOnShutdown = runsOnShutdown;
|
||||
}
|
||||
|
||||
public WaitHandle Latch
|
||||
{
|
||||
get { return _gate; }
|
||||
get { return _gate.WaitHandle; }
|
||||
}
|
||||
|
||||
public bool IsLatched
|
||||
{
|
||||
get { return true; }
|
||||
get { return _gate.IsSet == false; }
|
||||
}
|
||||
|
||||
public bool RunsOnShutdown
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
public bool RunsOnShutdown { get; private set; }
|
||||
|
||||
public void Run()
|
||||
{
|
||||
@@ -633,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();
|
||||
}
|
||||
@@ -665,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -169,6 +169,7 @@
|
||||
<Compile Include="AttemptTests.cs" />
|
||||
<Compile Include="Cache\CacheRefresherTests.cs" />
|
||||
<Compile Include="CoreStrings\StringValidationTests.cs" />
|
||||
<Compile Include="Controllers\BackOfficeControllerUnitTests.cs" />
|
||||
<Compile Include="FrontEnd\UmbracoHelperTests.cs" />
|
||||
<Compile Include="Integration\GetCultureTests.cs" />
|
||||
<Compile Include="Membership\DynamicMemberContentTests.cs" />
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Tests.UmbracoExamine
|
||||
public void Events_Ignoring_Node()
|
||||
{
|
||||
//change the parent id so that they are all ignored
|
||||
var existingCriteria = ((IndexCriteria)_indexer.IndexerData);
|
||||
var existingCriteria = _indexer.IndexerData;
|
||||
_indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
|
||||
999); //change to 999
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ LazyLoad.js([
|
||||
'/Umbraco/js/umbraco.security.js',
|
||||
'/Umbraco/ServerVariables',
|
||||
'/Umbraco/lib/spectrum/spectrum.js',
|
||||
'http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js',
|
||||
'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js',
|
||||
'/umbraco/js/canvasdesigner.panel.js',
|
||||
], function () {
|
||||
jQuery(document).ready(function () {
|
||||
|
||||
@@ -35,6 +35,14 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider',
|
||||
$scope.previewDevice = device;
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Exit Preview */
|
||||
/*****************************************************************************/
|
||||
|
||||
$scope.exitPreview = function () {
|
||||
window.top.location.href = "/umbraco/endPreview.aspx?redir=%2f" + $scope.pageId;
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
/* UI designer managment */
|
||||
/*****************************************************************************/
|
||||
@@ -180,7 +188,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider',
|
||||
// TODO: special init for font family picker
|
||||
if (item.type == "googlefontpicker" && item.values.fontFamily) {
|
||||
var variant = item.values.fontWeight != "" || item.values.fontStyle != "" ? ":" + item.values.fontWeight + item.values.fontStyle : "";
|
||||
var gimport = "@import url('http://fonts.googleapis.com/css?family=" + item.values.fontFamily + variant + "');";
|
||||
var gimport = "@import url('https://fonts.googleapis.com/css?family=" + item.values.fontFamily + variant + "');";
|
||||
if ($.inArray(gimport, parameters) < 0) {
|
||||
parameters.splice(0, 0, gimport);
|
||||
}
|
||||
@@ -412,7 +420,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider',
|
||||
var webFontScriptLoaded = false;
|
||||
var loadGoogleFont = function (font) {
|
||||
if (!webFontScriptLoaded) {
|
||||
$.getScript('http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js')
|
||||
$.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js')
|
||||
.done(function () {
|
||||
webFontScriptLoaded = true;
|
||||
// Recursively call once webfont script is available.
|
||||
|
||||
@@ -19,7 +19,7 @@ var refreshLayout = function (parameters) {
|
||||
var webFontScriptLoaded = false;
|
||||
var getFont = function (font) {
|
||||
if (!webFontScriptLoaded) {
|
||||
$.getScript('http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js')
|
||||
$.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js')
|
||||
.done(function () {
|
||||
webFontScriptLoaded = true;
|
||||
// Recursively call once webfont script is available.
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
<i></i>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-click="exitPreview()">
|
||||
<a href="#" title="Exit Preview"><i class="icon icon-wrong"></i><span> </span></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="sections" ng-class="{selected: !showDevicesPreview}" ng-if="enableCanvasdesigner > 0">
|
||||
|
||||
@@ -40,6 +40,8 @@ angular.module("umbraco.directives")
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$('.nav-pills, .nav-tabs').tabdrop();
|
||||
}
|
||||
|
||||
scope.showTabs = iAttrs.tabs ? true : false;
|
||||
|
||||
+4
-2
@@ -70,7 +70,7 @@ function valFormManager(serverValidationManager, $rootScope, $log, $timeout, not
|
||||
|
||||
//This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but
|
||||
// the form has pending changes
|
||||
unsubscribe.push($rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) {
|
||||
var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) {
|
||||
if (!formCtrl.$dirty || isSavingNewItem) {
|
||||
return;
|
||||
}
|
||||
@@ -93,7 +93,9 @@ function valFormManager(serverValidationManager, $rootScope, $log, $timeout, not
|
||||
eventsService.emit("valFormManager.pendingChanges", true);
|
||||
}
|
||||
|
||||
}));
|
||||
});
|
||||
unsubscribe.push(locationEvent);
|
||||
|
||||
//Ensure to remove the event handler when this instance is destroyted
|
||||
scope.$on('$destroy', function() {
|
||||
for (var u in unsubscribe) {
|
||||
|
||||
@@ -15,7 +15,7 @@ function macroService() {
|
||||
|
||||
//This regex will match an alias of anything except characters that are quotes or new lines (for legacy reasons, when new macros are created
|
||||
// their aliases are cleaned an invalid chars are stripped)
|
||||
var expression = /(<\?UMBRACO_MACRO macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i;
|
||||
var expression = /(<\?UMBRACO_MACRO (?:.+?)?macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i;
|
||||
var match = expression.exec(syntax);
|
||||
if (!match || match.length < 3) {
|
||||
return null;
|
||||
|
||||
@@ -28,7 +28,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
|
||||
'Umbraco is the best of both worlds: 100% free and open source, and backed by a professional and profitable company',
|
||||
"There's a pretty big chance, you've visited a website powered by Umbraco today",
|
||||
"'Umbraco-spotting' is the game of spotting big brands running Umbraco",
|
||||
"At least 2 people have the Umbraco logo tattooed on them",
|
||||
"At least 4 people have the Umbraco logo tattooed on them",
|
||||
"'Umbraco' is the danish name for an allen key",
|
||||
"Umbraco has been around since 2005, that's a looong time in IT",
|
||||
"More than 400 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='http://codegarden14.com'>CodeGarden</a>",
|
||||
@@ -335,4 +335,4 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
|
||||
};
|
||||
|
||||
return service;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -794,3 +794,17 @@ legend + .control-group {
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
||||
// Labels on own row
|
||||
.form-horizontal .control-label {
|
||||
width: 100%;
|
||||
}
|
||||
.form-horizontal .controls {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,10 @@
|
||||
}
|
||||
|
||||
.login-overlay .form {
|
||||
position:fixed;
|
||||
display: block;
|
||||
padding-top: 100px;
|
||||
padding-left: 165px;
|
||||
top: 100px;
|
||||
left: 165px;
|
||||
width: 370px;
|
||||
text-align: right
|
||||
}
|
||||
@@ -44,4 +45,12 @@
|
||||
padding-left: 6px;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 565px) {
|
||||
// Remove padding on login-form on smaller devices
|
||||
.login-overlay .form {
|
||||
left: inherit;
|
||||
right:25px;
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ h5{
|
||||
|
||||
.controls-row {
|
||||
padding-top: 5px;
|
||||
margin-left: 240px !important;
|
||||
margin-left: 240px;
|
||||
}
|
||||
.controls-row label {
|
||||
display: inline-block
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -37,77 +46,84 @@
|
||||
bottom: 31px !important;
|
||||
}
|
||||
|
||||
/*
|
||||
.umb-tab-buttons.umb-bottom-bar {
|
||||
bottom: 50px !important;
|
||||
}*/
|
||||
|
||||
.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 {
|
||||
@@ -120,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
|
||||
}
|
||||
|
||||
.umb-btn-toolbar .dropdown-menu small {
|
||||
background: #fef9db;
|
||||
display: block;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.umb-btn-toolbar .dropdown-menu .btn {
|
||||
margin: 20px 29px;
|
||||
width: 80px
|
||||
}
|
||||
|
||||
/* tab buttons */
|
||||
.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);
|
||||
|
||||
border-top: 1px solid @grayLighter;
|
||||
|
||||
padding: 10px 0 10px 0;
|
||||
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 100px;
|
||||
right: 20px;
|
||||
z-index: 6010;
|
||||
};
|
||||
|
||||
@media (min-width: 1101px) {
|
||||
.umb-bottom-bar {left: 460px;}
|
||||
right: 0;
|
||||
left: auto;
|
||||
border-radius: @tabsBorderRadius;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.umb-tab-buttons{padding-left: 240px;}
|
||||
.umb-tab-pane{padding-bottom: 90px}
|
||||
.umb-btn-toolbar .dropdown-menu small {
|
||||
background: #fef9db;
|
||||
display: block;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.tab-content{overflow: visible; }
|
||||
.umb-btn-toolbar .dropdown-menu .btn {
|
||||
margin: 20px 29px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.umb-panel-footer-nav{
|
||||
/* tab buttons */
|
||||
.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);
|
||||
border-top: 1px solid @grayLighter;
|
||||
padding: 10px 0 10px 0;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 100px;
|
||||
right: 40px;
|
||||
z-index: 6010;
|
||||
|
||||
@media (min-width: 1101px) {
|
||||
left: 460px;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-tab-buttons {
|
||||
padding-left: 0;
|
||||
|
||||
> .btn-group:not([style*="display:none"]):not([style*="display: none"]) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -192,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%);
|
||||
}
|
||||
@@ -308,10 +308,6 @@ ul.color-picker li a {
|
||||
line-height: 120px
|
||||
}
|
||||
|
||||
.umb-folderbrowser .selector-overlay{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.umb-upload-drop-zone{
|
||||
margin-bottom:5px;
|
||||
}
|
||||
@@ -369,7 +365,7 @@ ul.color-picker li a {
|
||||
|
||||
|
||||
.umb-photo-folder .picrow div a:first-child {
|
||||
width:100%;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
@@ -389,6 +385,10 @@ ul.color-picker li a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.umb-photo-folder .selector-overlay{
|
||||
display: none;
|
||||
}
|
||||
|
||||
//this is a temp hack, to provide selectors in the dialog:
|
||||
.umb-photo-folder .pic:hover .selector-overlay {
|
||||
position: absolute;
|
||||
@@ -421,6 +421,14 @@ ul.color-picker li a {
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.umb-photo-folder .umb-non-thumbnail span{
|
||||
position: absolute;
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.umb-photo-folder .selected{
|
||||
position: relative;
|
||||
}
|
||||
@@ -514,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;}
|
||||
@@ -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',
|
||||
|
||||
@@ -169,12 +169,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="textSearch" class="radio inline">
|
||||
<input type="radio" name="searchType" id="textSearch" value="text" ng-model="searcher.searchType" />
|
||||
<label for="{{searcher.name}}-textSearch" class="radio inline">
|
||||
<input type="radio" name="searchType" id="{{searcher.name}}-textSearch" value="text" ng-model="searcher.searchType" />
|
||||
Text Search
|
||||
</label>
|
||||
<label for="luceneSearch" class="radio inline">
|
||||
<input type="radio" name="searchType" id="luceneSearch" value="lucene" ng-model="searcher.searchType" />
|
||||
<label for="{{searcher.name}}-luceneSearch" class="radio inline">
|
||||
<input type="radio" name="searchType" id="{{searcher.name}}-luceneSearch" value="lucene" ng-model="searcher.searchType" />
|
||||
Lucene Search
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
|
||||
<div ng-style="img.style" class="umb-non-thumbnail" ng-if="!img.thumbnail">
|
||||
<i class="icon large {{img.icon}}"></i>
|
||||
{{img.name}}
|
||||
|
||||
<span>{{img.name}}</span>
|
||||
</div>
|
||||
|
||||
<div ng-if="img.thumbnail" class="umb-photo" ng-style="img.thumbStyle" alt="{{img.name}}">
|
||||
|
||||
+11
-8
@@ -20,29 +20,32 @@
|
||||
|
||||
<!-- we need to show the old pass field when the provider cannot retrieve the password -->
|
||||
<umb-control-group alias="oldPassword" label="Old password" ng-show="$parent.showOldPass()">
|
||||
<input type="text" name="oldPassword" ng-model="$parent.model.value.oldPassword"
|
||||
<input type="password" name="oldPassword" ng-model="$parent.model.value.oldPassword"
|
||||
class="input-large umb-textstring textstring"
|
||||
ng-required="$parent.showOldPass()"
|
||||
val-server="oldPassword" no-dirty-check/>
|
||||
val-server="oldPassword" no-dirty-check
|
||||
autocomplete="off"/>
|
||||
<span class="help-inline" val-msg-for="oldPassword" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="oldPassword" val-toggle-msg="valServer"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group alias="password" label="New password" ng-show="$parent.showNewPass()">
|
||||
<input type="text" name="password" ng-model="$parent.model.value.newPassword"
|
||||
class="input-large umb-textstring textstring"
|
||||
<input type="password" name="password" ng-model="$parent.model.value.newPassword"
|
||||
class="input-large umb-textstring textstring"
|
||||
ng-required="!$parent.model.value.reset"
|
||||
val-server="value"
|
||||
ng-minlength="{{$parent.model.config.minPasswordLength}}" no-dirty-check/>
|
||||
val-server="value"
|
||||
ng-minlength="{{$parent.model.config.minPasswordLength}}" no-dirty-check
|
||||
autocomplete="off" />
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="minlength">Minimum {{$parent.model.config.minPasswordLength}} characters</span>
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="valServer"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group alias="confirmpassword" label="Confirm password" ng-show="$parent.showConfirmPass()">
|
||||
<input type="text" name="confirmpassword" ng-model="$parent.model.confirm"
|
||||
<input type="password" name="confirmpassword" ng-model="$parent.model.confirm"
|
||||
class="input-large umb-textstring textstring"
|
||||
val-compare="password" no-dirty-check/>
|
||||
val-compare="password" no-dirty-check
|
||||
autocomplete="off" />
|
||||
|
||||
<span class="help-inline" val-msg-for="confirmpassword" val-toggle-msg="valCompare">Passwords must match</span>
|
||||
</umb-control-group>
|
||||
|
||||
+73
-13
@@ -18,9 +18,17 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
|
||||
//map the user config
|
||||
$scope.model.config = angular.extend(config, $scope.model.config);
|
||||
|
||||
$scope.hasDatetimePickerValue = $scope.model.value ? true : false;
|
||||
$scope.datetimePickerValue = null;
|
||||
|
||||
//hide picker if clicking on the document
|
||||
$scope.hidePicker = function () {
|
||||
$element.find("div:first").datetimepicker("hide");
|
||||
//$element.find("div:first").datetimepicker("hide");
|
||||
// Sometimes the statement above fails and generates errors in the browser console. The following statements fix that.
|
||||
var dtp = $element.find("div:first");
|
||||
if (dtp && dtp.datetimepicker) {
|
||||
dtp.datetimepicker("hide");
|
||||
}
|
||||
};
|
||||
$(document).bind("click", $scope.hidePicker);
|
||||
|
||||
@@ -28,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);
|
||||
@@ -43,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) {
|
||||
|
||||
@@ -62,17 +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
|
||||
$element.find("div:first").datetimepicker("setValue", $scope.model.value ? $scope.model.value : null);
|
||||
// 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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -80,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="model.value"
|
||||
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>
|
||||
|
||||
@@ -40,6 +40,18 @@ describe('macro service tests', function () {
|
||||
expect(result.macroParamsDictionary.test2).toBe("hello");
|
||||
});
|
||||
|
||||
it('can parse syntax for macros when macroAlias is not the first parameter', function () {
|
||||
|
||||
var result = macroService.parseMacroSyntax("<?UMBRACO_MACRO test=\"asdf\" test2='hello' macroAlias='Map.Test' />");
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.macroAlias).toBe("Map.Test");
|
||||
expect(result.macroParamsDictionary.test).not.toBeUndefined();
|
||||
expect(result.macroParamsDictionary.test).toBe("asdf");
|
||||
expect(result.macroParamsDictionary.test2).not.toBeUndefined();
|
||||
expect(result.macroParamsDictionary.test2).toBe("hello");
|
||||
});
|
||||
|
||||
it('can parse syntax for macros with aliases containing whitespace and other chars', function () {
|
||||
|
||||
var result = macroService.parseMacroSyntax("<?UMBRACO_MACRO macroAlias='Map Test [Hello\\World]' test=\"asdf\" test2='hello' />");
|
||||
|
||||
@@ -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>
|
||||
@@ -165,7 +166,7 @@
|
||||
</Reference>
|
||||
<Reference Include="MySql.Data">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\MySql.Data.6.9.6\lib\net45\MySql.Data.dll</HintPath>
|
||||
<HintPath>..\packages\MySql.Data.6.9.7\lib\net45\MySql.Data.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, 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>7250</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>7280</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:7250</IISUrl>
|
||||
<IISUrl>http://localhost:7280</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>
|
||||
|
||||
@@ -118,33 +118,10 @@
|
||||
<useDomainPrefixes>false</useDomainPrefixes>
|
||||
<!-- this will add a trailing slash (/) to urls when in directory url mode -->
|
||||
<addTrailingSlash>true</addTrailingSlash>
|
||||
<urlReplacing removeDoubleDashes="true">
|
||||
<urlReplacing removeDoubleDashes="true" toAscii="true">
|
||||
<char org=" ">-</char>
|
||||
<char org="""></char>
|
||||
<char org="'"></char>
|
||||
<char org="%"></char>
|
||||
<char org="."></char>
|
||||
<char org=";"></char>
|
||||
<char org="/"></char>
|
||||
<char org="\"></char>
|
||||
<char org=":"></char>
|
||||
<char org="#"></char>
|
||||
<char org="+">plus</char>
|
||||
<char org="*">star</char>
|
||||
<char org="&"></char>
|
||||
<char org="?"></char>
|
||||
<char org="æ">ae</char>
|
||||
<char org="ø">oe</char>
|
||||
<char org="å">aa</char>
|
||||
<char org="ä">ae</char>
|
||||
<char org="ö">oe</char>
|
||||
<char org="ü">ue</char>
|
||||
<char org="ß">ss</char>
|
||||
<char org="Ä">ae</char>
|
||||
<char org="Ö">oe</char>
|
||||
<char org="|">-</char>
|
||||
<char org="<"></char>
|
||||
<char org=">"></char>
|
||||
<char org="ö">oxxx</char>
|
||||
<!--<char org="ü">ue</char>-->
|
||||
</urlReplacing>
|
||||
</requestHandler>
|
||||
|
||||
@@ -299,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>
|
||||
@@ -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.7" 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>
|
||||
@@ -863,7 +863,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="editors">Redaktør</key>
|
||||
<key alias="excerptField">Uddragsfelt</key>
|
||||
<key alias="language">Sprog</key>
|
||||
<key alias="loginname">Login</key>
|
||||
<key alias="loginname">Brugernavn</key>
|
||||
<key alias="mediastartnode">Startnode i mediearkivet</key>
|
||||
<key alias="modules">Moduler</key>
|
||||
<key alias="noConsole">Deaktivér adgang til Umbraco</key>
|
||||
@@ -881,12 +881,11 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="permissionSelectPages">Vælg sider for at ændre deres rettigheder</key>
|
||||
<key alias="searchAllChildren">Søg alle 'børn'</key>
|
||||
<key alias="startnode">Start node</key>
|
||||
<key alias="username">Brugernavn</key>
|
||||
<key alias="username">Navn</key>
|
||||
<key alias="userPermissions">Bruger tilladelser</key>
|
||||
<key alias="usertype">Brugertype</key>
|
||||
<key alias="userTypes">Bruger typer</key>
|
||||
<key alias="writer">Forfatter</key>
|
||||
|
||||
<key alias="yourProfile">Din profil</key>
|
||||
<key alias="yourHistory">Din historik</key>
|
||||
<key alias="sessionExpires">Session udløber</key>
|
||||
|
||||
@@ -849,7 +849,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
|
||||
<area alias="grid">
|
||||
<key alias="insertControl">Insert control</key>
|
||||
<key alias="addRows">Add rows to your layout</key>
|
||||
<key alias="addRows">Choose a layout for the page</key>
|
||||
<key alias="addElement"><![CDATA[To start, click the <i class="icon icon-add blue"></i> below and add your first element]]></key>
|
||||
|
||||
<key alias="clickToEmbed">Click to embed</key>
|
||||
|
||||
@@ -849,7 +849,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
</area>
|
||||
<area alias="grid">
|
||||
<key alias="insertControl">Insert control</key>
|
||||
<key alias="addRows">Add rows to your layout</key>
|
||||
<key alias="addRows">Choose a layout for the page</key>
|
||||
<key alias="addElement"><![CDATA[To start, click the <i class="icon icon-add blue"></i> below and add your first element]]></key>
|
||||
|
||||
<key alias="clickToEmbed">Click to embed</key>
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
<key alias="disable">Uitschakelen</key>
|
||||
<key alias="emptyTrashcan">Prullenbak leegmaken</key>
|
||||
<key alias="exportDocumentType">Documenttype exporteren</key>
|
||||
<key alias="exportDocumentTypeAsCode">Exporteer naar .NET</key>
|
||||
<key alias="exportDocumentTypeAsCode-Full">Exporteer naar .NET</key>
|
||||
<key alias="importDocumentType">Documenttype importeren</key>
|
||||
<key alias="importPackage">Package importeren</key>
|
||||
<key alias="liveEdit">Aanpassen in Canvas</key>
|
||||
@@ -51,11 +49,11 @@
|
||||
<key alias="domainCreated">Nieuw domein '%0%' is aangemaakt</key>
|
||||
<key alias="domainDeleted">Domein '%0%' is verwijderd</key>
|
||||
<key alias="domainExists">Domein '%0' is al aanwezig</key>
|
||||
<key alias="domainUpdated">Domein '%0%' is bijgewerkt</key>
|
||||
<key alias="orEdit">Bewerk huidige domeinen</key>
|
||||
<key alias="domainHelp"><![CDATA[Geldige domeinnamen zijn: "example.com", "www.example.com", "example.com:8080" of
|
||||
"https://www.example.com/".<br /><br />Zgn. 'one-level' paden in domeinen worden ondersteund, bijv. "example.com/en". Echter, ze
|
||||
zouden moeten worden vermeden. Gebruik bij voorkeur de cultuurinstelling hierboven.]]></key>
|
||||
<key alias="domainUpdated">Domein '%0%' is bijgewerkt</key>
|
||||
<key alias="orEdit">Bewerk huidige domeinen</key>
|
||||
<key alias="inherit">Overerven</key>
|
||||
<key alias="setLanguage">Cultuur</key>
|
||||
<key alias="setLanguageHelp"><![CDATA[Zet de cultuur voor de nodes onder de huidige node,<br /> of erf de cultuur over van de ouder nodes. Zal ook van toepassing <br />
|
||||
@@ -87,6 +85,7 @@
|
||||
<key alias="macroInsert">Macro invoegen</key>
|
||||
<key alias="pictureInsert">Afbeelding invoegen</key>
|
||||
<key alias="relations">Relaties wijzigen</key>
|
||||
<key alias="returnToList">Terug naar overzicht</key>
|
||||
<key alias="save">Opslaan</key>
|
||||
<key alias="saveAndPublish">Opslaan en publiceren</key>
|
||||
<key alias="saveToPublish">Opslaan en verzenden voor goedkeuring</key>
|
||||
@@ -117,6 +116,7 @@
|
||||
<key alias="validDocTypesNote">Alleen alternatieve types geldig voor de huidige locatie worden weergegeven.</key>
|
||||
</area>
|
||||
<area alias="content">
|
||||
<key alias="isPublished" version="7.2">Is gepubliceerd</key>
|
||||
<key alias="about">Over deze pagina</key>
|
||||
<key alias="alias">Alternatieve link</key>
|
||||
<key alias="alternativeTextHelp">(hoe zou jij de foto beschrijven via de telefoon)</key>
|
||||
@@ -133,6 +133,7 @@
|
||||
<key alias="itemChanged">Dit item is gewijzigd na publicatie</key>
|
||||
<key alias="itemNotPublished">Dit item is niet gepubliceerd</key>
|
||||
<key alias="lastPublished">Laatst gepubliceerd op</key>
|
||||
<key alias="listViewNoItems" version="7.1.5">Nog geen items om weer te geven.</key>
|
||||
<key alias="mediatype">Mediatype</key>
|
||||
<key alias="mediaLinks">Link naar media item(s)</key>
|
||||
<key alias="membergroup">Ledengroep</key>
|
||||
@@ -152,11 +153,12 @@
|
||||
<key alias="sortHelp">Om nodes te sorteren, sleep de nodes of klik op één van de kolomtitels. Je kan meerdere nodes tegelijk selecteren door de "shift"- of "control"knop in te drukken tijdens het selecteren.</key>
|
||||
<key alias="statistics">Statistieken</key>
|
||||
<key alias="titleOptional">Titel (optioneel)</key>
|
||||
<key alias="altTextOptional">Alternatieve tekst (optioneel)</key>
|
||||
<key alias="type">Type</key>
|
||||
<key alias="unPublish">Depubliceren</key>
|
||||
<key alias="updateDate">Laatst gewijzigd</key>
|
||||
<key alias="updateDateDesc" version="7.0">Date/time this document was edited</key>
|
||||
<key alias="uploadClear">Bestand verwijderen</key>
|
||||
<key alias="uploadClear">Bestand(en) verwijderen</key>
|
||||
<key alias="urls">Link naar het document</key>
|
||||
<key alias="memberof">Lid van groep(en)</key>
|
||||
<key alias="notmemberof">Geen lid van groep(en)</key>
|
||||
@@ -170,7 +172,7 @@
|
||||
</area>
|
||||
<area alias="create">
|
||||
<key alias="chooseNode">Waar wil je de nieuwe %0% aanmaken?</key>
|
||||
<key alias="createUnder">Aanmaken op</key>
|
||||
<key alias="createUnder">Aanmaken onder</key>
|
||||
<key alias="updateData">Kies een type en een titel</key>
|
||||
|
||||
<key alias="noDocumentTypes" version="7.0"><![CDATA[Er zijn geen toegestane documenttypes beschikbaar. Je moet deze inschakelen in de Instellingen sectie onder <strong>"Documenttypes"</ strong>.]]></key>
|
||||
@@ -241,16 +243,20 @@
|
||||
<key alias="displayName">Cultuurnaam</key>
|
||||
</area>
|
||||
<area alias="placeholders">
|
||||
<key alias="username">Type je gebruikersnaam</key>
|
||||
<key alias="password">Type je wachtwoord</key>
|
||||
<key alias="username">Typ je gebruikersnaam</key>
|
||||
<key alias="password">Typ je wachtwoord</key>
|
||||
<key alias="nameentity">Benoem de %0%...</key>
|
||||
<key alias="entername">Type een naam...</key>
|
||||
<key alias="search">Type om te zoeken...</key>
|
||||
<key alias="filter">Type om te filteren...</key>
|
||||
<key alias="entername">Typ een naam...</key>
|
||||
<key alias="search">Typ om te zoeken...</key>
|
||||
<key alias="filter">Typ om te filteren...</key>
|
||||
<key alias="enterTags">Typ om tags toe te voegen (druk op enter na elke tag)...</key>
|
||||
</area>
|
||||
|
||||
<area alias="editcontenttype">
|
||||
<key alias="allowAtRoot" version="7.2">Toestaan op root-niveau</key>
|
||||
<key alias="allowAtRootDesc" version="7.2">Wanneer aangevinkt dan mag dit document type aangemaakt worden op het root-niveau van content of media trees.</key>
|
||||
<key alias="allowedchildnodetypes">Toegelaten subnodetypes</key>
|
||||
<key alias="contenttypecompositions">Document Type Composities</key>
|
||||
<key alias="create">Nieuw</key>
|
||||
<key alias="deletetab">Tab verwijderen</key>
|
||||
<key alias="description">Omschrijving</key>
|
||||
@@ -258,6 +264,11 @@
|
||||
<key alias="tab">Tab</key>
|
||||
<key alias="thumbnail">Miniatuur</key>
|
||||
<key alias="hasListView">Lijstweergave inschakelen</key>
|
||||
<key alias="hasListViewDesc" version="7.2">Stelt het content item in zodat een sorteer- en zoekbare lijstweergave van onderliggende nodes wordt getoond. De onderliggende nodes worden niet in de tree getoond</key>
|
||||
<key alias="currentListView" version="7.2">Huidige lijstweergave</key>
|
||||
<key alias="currentListViewDesc" version="7.2">De actieve data type in lijstweergave</key>
|
||||
<key alias="createListView" version="7.2">Maak een aangepaste lijstweergave</key>
|
||||
<key alias="removeListView" version="7.2">Verwijder aangepaste lijstweergave</key>
|
||||
</area>
|
||||
<area alias="editdatatype">
|
||||
<key alias="addPrevalue">Prevalue toevoegen</key>
|
||||
@@ -501,7 +512,6 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
|
||||
<key alias="timeout">Sessie is verlopen</key>
|
||||
|
||||
<key alias="bottomText"><![CDATA[<p style="text-align:right;">© 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.com</a></p>]]></key>
|
||||
<key alias="topText">Welkom bij Umbraco, geef je gebruikersnaam en wachtwoord op in de onderstaande velden:</key>
|
||||
</area>
|
||||
<area alias="main">
|
||||
<key alias="dashboard">Dashboard</key>
|
||||
@@ -662,6 +672,9 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
|
||||
<key alias="newWindow">Open in nieuw venster</key>
|
||||
<key alias="removeLink">Verwijder link</key>
|
||||
</area>
|
||||
<area alias="imagecropper">
|
||||
<key alias="reset">Reset</key>
|
||||
</area>
|
||||
<area alias="rollback">
|
||||
<key alias="currentVersion">Huidige versie</key>
|
||||
<key alias="diffHelp"><![CDATA[Hier worden de verschillen getoond tussen de huidige en de geselecteerde versie<br /><del>Rode</del> tekst wordt niet getoond in de geselecteerde versie , <ins>groen betekent toegevoegd</ins>]]></key>
|
||||
@@ -690,6 +703,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
|
||||
<key alias="contour" version="4.0">Umbraco Contour</key>
|
||||
|
||||
<key alias="help" version="7.0">Help</key>
|
||||
<key alias="forms">Formulieren</key>
|
||||
<key alias="analytics">Analytics</key>
|
||||
</area>
|
||||
<area alias="settings">
|
||||
<key alias="defaulttemplate">Standaard template</key>
|
||||
@@ -708,6 +723,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
|
||||
<key alias="contentTypeUses">Dit inhoudstype gebruikt</key>
|
||||
<key alias="asAContentMasterType">als basis inhoudstype. Tabs van basis inhoudstypes worden niet getoond en kunnen alleen worden aangepast op het basis inhoudstype zelf</key>
|
||||
<key alias="noPropertiesDefinedOnTab">Geen eigenschappen gedefinieerd op dit tabblad. Klik op de link "voeg een nieuwe eigenschap" aan de bovenkant om een nieuwe eigenschap te creëren.</key>
|
||||
<key alias="masterDocumentType">Master Document Type</key>
|
||||
<key alias="createMatchingTemplate">Maak een bijbehorend template</key>
|
||||
</area>
|
||||
<area alias="sort">
|
||||
<key alias="sortDone">Sorteren gereed.</key>
|
||||
@@ -788,36 +805,40 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
|
||||
<key alias="quickGuide">Quick Guide voor Umbraco template tags</key>
|
||||
<key alias="template">Sjabloon</key>
|
||||
</area>
|
||||
<area alias="grid">
|
||||
<key alias="insertControl">Item toevoegen</key>
|
||||
<key alias="addRows">Een rij aan de lay-out toevoegen</key>
|
||||
<key alias="addElement"><![CDATA[Klik om te starten op het <i class="icon icon-add blue"></i> teken onderaan en voeg je eerste item toe]]></key>
|
||||
<area alias="grid">
|
||||
<key alias="insertControl">Item toevoegen</key>
|
||||
<key alias="addRows">Een rij aan de lay-out toevoegen</key>
|
||||
<key alias="addElement"><![CDATA[Klik om te starten op het <i class="icon icon-add blue"></i> teken onderaan en voeg je eerste item toe]]></key>
|
||||
|
||||
<key alias="gridLayouts">Grid lay-outs</key>
|
||||
<key alias="gridLayoutsDetail">Lay-outs zijn het globale werkgebied voor de grid editor. Je hebt meestal maar één of twee verschillende lay-outs nodig</key>
|
||||
<key alias="addGridLayout">Een grid layout toevoegen</key>
|
||||
<key alias="addGridLayoutDetail">De lay-out aanpassen door de kolombreedte aan te passen en extra kolommen toe te voegen</key>
|
||||
<key alias="clickToEmbed">Klik om een item te embedden</key>
|
||||
<key alias="clickToInsertImage">Klik om een afbeelding in te voegen</key>
|
||||
<key alias="placeholderImageCaption">Afbeelding ondertitel...</key>
|
||||
<key alias="placeholderWriteHere">Typ hier......</key>
|
||||
<key alias="gridLayouts">Grid lay-outs</key>
|
||||
<key alias="gridLayoutsDetail">Lay-outs zijn het globale werkgebied voor de grid editor. Je hebt meestal maar één of twee verschillende lay-outs nodig</key>
|
||||
<key alias="addGridLayout">Een grid layout toevoegen</key>
|
||||
<key alias="addGridLayoutDetail">De lay-out aanpassen door de kolombreedte aan te passen en extra kolommen toe te voegen</key>
|
||||
|
||||
<key alias="rowConfigurations">Rijconfiguratie</key>
|
||||
<key alias="rowConfigurationsDetail">Rijen zijn voorgedefinieerde cellen die horizontaal zijn gerangschikt</key>
|
||||
<key alias="addRowConfiguration">Een rijconfiguratie toevoegen</key>
|
||||
<key alias="addRowConfigurationDetail">De rijconfiguratie aanpassen door de breedte van de cel in te stellen en extra cellen toe te voegen</key>
|
||||
<key alias="rowConfigurations">Rijconfiguratie</key>
|
||||
<key alias="rowConfigurationsDetail">Rijen zijn voorgedefinieerde cellen die horizontaal zijn gerangschikt</key>
|
||||
<key alias="addRowConfiguration">Een rijconfiguratie toevoegen</key>
|
||||
<key alias="addRowConfigurationDetail">De rijconfiguratie aanpassen door de breedte van de cel in te stellen en extra cellen toe te voegen</key>
|
||||
|
||||
<key alias="columns">Kolommen</key>
|
||||
<key alias="columnsDetails">Het totaal aantal gecombineerde kolommen in de grid layout</key>
|
||||
<key alias="columns">Kolommen</key>
|
||||
<key alias="columnsDetails">Het totaal aantal gecombineerde kolommen in de grid layout</key>
|
||||
|
||||
<key alias="settings">Instellingen</key>
|
||||
<key alias="settingsDetails">Configureren welke instellingen de editors kunnen aanpassen</key>
|
||||
<key alias="settings">Instellingen</key>
|
||||
<key alias="settingsDetails">Configureren welke instellingen de editors kunnen aanpassen</key>
|
||||
|
||||
|
||||
<key alias="styles">Styles</key>
|
||||
<key alias="stylesDetails">Configureren welke stijlen de editors kunnen aanpassen</key>
|
||||
<key alias="styles">Styles</key>
|
||||
<key alias="stylesDetails">Configureren welke stijlen de editors kunnen aanpassen</key>
|
||||
|
||||
<key alias="settingDialogDetails">De instellingen worden enkel bewaard indien de ingevoerde Json geldig is</key>
|
||||
<key alias="settingDialogDetails">De instellingen worden enkel bewaard indien de ingevoerde Json geldig is</key>
|
||||
|
||||
<key alias="allowAllEditors">Alle editors toelaten</key>
|
||||
<key alias="allowAllRowConfigurations">Alle rijconfiguraties toelaten</key>
|
||||
</area>
|
||||
<key alias="allowAllEditors">Alle editors toelaten</key>
|
||||
<key alias="allowAllRowConfigurations">Alle rijconfiguraties toelaten</key>
|
||||
</area>
|
||||
<area alias="templateEditor">
|
||||
<key alias="alternativeField">Alternatief veld</key>
|
||||
<key alias="alternativeText">Alternatieve tekst</key>
|
||||
|
||||
@@ -40,11 +40,11 @@
|
||||
<area alias="assignDomain">
|
||||
<key alias="addNew">Lägg till nytt domännamn</key>
|
||||
<key alias="domain">Domännamn</key>
|
||||
<key alias="domainCreated">Har skapat domännamnet {0}</key>
|
||||
<key alias="domainDeleted">Har tagit bort domännamnet {0}</key>
|
||||
<key alias="domainExists">Domänen {0} är redan tillagd</key>
|
||||
<key alias="domainCreated">Har skapat domännamnet %0%</key>
|
||||
<key alias="domainDeleted">Har tagit bort domännamnet %0%</key>
|
||||
<key alias="domainExists">Domänen %0% är redan tillagd</key>
|
||||
<key alias="domainHelp">t.ex.: dittdomannamn.se, www.dittdomannamn.se</key>
|
||||
<key alias="domainUpdated">Domännamnet {0} har uppdaterats</key>
|
||||
<key alias="domainUpdated">Domännamnet %0% har uppdaterats</key>
|
||||
<key alias="duplicateDomain">Domänen är redan tilldelad</key>
|
||||
<key alias="inherit">Ärv</key>
|
||||
<key alias="invalidDomain">Ogiltigt domännamn</key>
|
||||
@@ -145,7 +145,7 @@
|
||||
<key alias="nodeName">Sidnamn</key>
|
||||
<key alias="notmemberof">Ej medlem av grupp(er)</key>
|
||||
<key alias="otherElements">Egenskaper</key>
|
||||
<key alias="parentNotPublished">Detta dokument är publicerat men syns inte eftersom den överordnade sidan {0} inte är publicerad</key>
|
||||
<key alias="parentNotPublished">Detta dokument är publicerat men syns inte eftersom den överordnade sidan %0% inte är publicerad</key>
|
||||
<key alias="parentNotPublishedAnomaly">Oops: detta dokument är publicerat men finns inte i cacheminnet (internt fel)</key>
|
||||
<key alias="publish">Publicera</key>
|
||||
<key alias="publishStatus">Publiceringsstatus</key>
|
||||
@@ -166,7 +166,7 @@
|
||||
<key alias="urls">Länk till dokument</key>
|
||||
</area>
|
||||
<area alias="create">
|
||||
<key alias="chooseNode">Var vill du skapa den nya {0}</key>
|
||||
<key alias="chooseNode">Var vill du skapa den nya %0%</key>
|
||||
<key alias="createUnder">Skapa innehåll under</key>
|
||||
<key alias="noDocumentTypes"><![CDATA[Det finns inga giltiga dokumenttyper tillgängliga. Du måste aktivera dessa under sektionen inställningar och under <strong>"dokumenttyper"</strong>.]]></key>
|
||||
<key alias="noMediaTypes"><![CDATA[Det finns inga giltiga mediatyper tillgängliga. Du måste aktivera dessa under sektionen inställningar och under <strong>"mediatyper"</strong>.]]></key>
|
||||
@@ -187,7 +187,7 @@
|
||||
<key alias="closeThisWindow">Stäng fönstret</key>
|
||||
<key alias="confirmdelete">Är du säker på att du vill ta bort</key>
|
||||
<key alias="confirmdisable">Är du säker på att du vill avaktivera</key>
|
||||
<key alias="confirmEmptyTrashcan">Kryssa i denna ruta för att bekräfta att {0} objekt tas bort</key>
|
||||
<key alias="confirmEmptyTrashcan">Kryssa i denna ruta för att bekräfta att %0% objekt tas bort</key>
|
||||
<key alias="confirmlogout">Är du säker?</key>
|
||||
<key alias="confirmSure">Är du säker?</key>
|
||||
<key alias="cut">Klipp ut</key>
|
||||
@@ -228,7 +228,7 @@
|
||||
<key alias="viewCacheItem">Se cachat objekt</key>
|
||||
</area>
|
||||
<area alias="dictionaryItem">
|
||||
<key alias="description">Redigera de olika översättningarna för ordboksinlägget {0} nedan. Du kan lägga till ytterligare språk under 'språk' i menyn till vänster.</key>
|
||||
<key alias="description">Redigera de olika översättningarna för ordboksinlägget %0% nedan. Du kan lägga till ytterligare språk under 'språk' i menyn till vänster.</key>
|
||||
<key alias="displayName">Språknamn</key>
|
||||
</area>
|
||||
<area alias="editcontenttype">
|
||||
@@ -265,15 +265,15 @@
|
||||
<area alias="errorHandling">
|
||||
<key alias="errorButDataWasSaved">Informationen har sparats, men innan du kan publicera denna sida måste du åtgärda följande fel:</key>
|
||||
<key alias="errorChangingProviderPassword">Det går inte att byta lösenord i den medlemshanterare du har valt (EnablePasswordRetrieval måste vara satt till 'true').</key>
|
||||
<key alias="errorExistsWithoutTab">{0} redan finns</key>
|
||||
<key alias="errorExistsWithoutTab">%0% redan finns</key>
|
||||
<key alias="errorHeader">Följande fel inträffade:</key>
|
||||
<key alias="errorHeaderWithoutTab">Följande fel inträffade:</key>
|
||||
<key alias="errorInPasswordFormat">Lösenordet måste bestå av minst {0} tecken varav minst {1} är icke-alfanumeriska tecken (t.ex. %, #, !, @).</key>
|
||||
<key alias="errorIntegerWithoutTab">{0} måste vara ett heltal</key>
|
||||
<key alias="errorMandatory">{0} under {1} är ett obligatoriskt fält</key>
|
||||
<key alias="errorMandatoryWithoutTab">{0} är ett obligatoriskt fält</key>
|
||||
<key alias="errorRegExp">{0} under {1} har ett felaktigt format</key>
|
||||
<key alias="errorRegExpWithoutTab">{0} har ett felaktigt format</key>
|
||||
<key alias="errorInPasswordFormat">Lösenordet måste bestå av minst %0% tecken varav minst %1% är icke-alfanumeriska tecken (t.ex. %, #, !, @).</key>
|
||||
<key alias="errorIntegerWithoutTab">%0% måste vara ett heltal</key>
|
||||
<key alias="errorMandatory">%0% under %1% är ett obligatoriskt fält</key>
|
||||
<key alias="errorMandatoryWithoutTab">%0% är ett obligatoriskt fält</key>
|
||||
<key alias="errorRegExp">%0% under %1% har ett felaktigt format</key>
|
||||
<key alias="errorRegExpWithoutTab">%0% har ett felaktigt format</key>
|
||||
</area>
|
||||
<area alias="errors">
|
||||
<key alias="codemirroriewarning">Även om CodeMirror är aktiverad i konfigurationen, så är den avaktiverad i Internet Explorer på grund av att den inte är tillräckligt stabil</key>
|
||||
@@ -408,12 +408,12 @@
|
||||
<key alias="databaseErrorWebConfig">Kunde inte spara filen web.config. Vänligen ändra databasanslutnings-inställningarna manuellt.</key>
|
||||
<key alias="databaseFound">Din databas har lokaliserats och är identifierad som</key>
|
||||
<key alias="databaseHeader">Databaskonfiguration</key>
|
||||
<key alias="databaseInstall"><![CDATA[För att installera Umbraco {0} databasen, tryck på knappen <strong>installera</strong>]]></key>
|
||||
<key alias="databaseInstallDone"><![CDATA[Nu har Umbraco {0} kopierats till din databas. Tryck <strong>Nästa</strong> för att fortsätta.]]></key>
|
||||
<key alias="databaseInstall"><![CDATA[För att installera Umbraco %0% databasen, tryck på knappen <strong>installera</strong>]]></key>
|
||||
<key alias="databaseInstallDone"><![CDATA[Nu har Umbraco %0% kopierats till din databas. Tryck <strong>Nästa</strong> för att fortsätta.]]></key>
|
||||
<key alias="databaseNotFound"><![CDATA[<p>Databasen kunde inte hittas! Kontrollera att informationen i databasanslutnings-inställningarna i filen "web.config" är rätt.</p> <p>För att fortsätta måste du redigera filen "web.config" (du kan använda Visual Studio eller din favorit text-redigerare), bläddra till slutet, lägg till databasanslutnings-inställningarna för din databas i fältet som heter "umbracoDbDSN" och spara filen. </p> <p> Klicka på <strong>Försök igen</strong> knappen när du är klar.<br />><a href="http://our.umbraco.org/documentation/Using-Umbraco/Config-files/webconfig7" target="_blank"> Mer information om att redigera web.config hittar du här.</a></p>]]></key>
|
||||
<key alias="databaseText"><![CDATA[För att avsluta det här steget måste du veta lite information om din databasserver ("connection string").<br /> Eventuellt kan du behöva kontakta ditt webb-hotell. Om du installerar på en lokal maskin eller server kan du få informationen från din systemadministratör.]]></key>
|
||||
<key alias="databaseUpgrade"><![CDATA[<p> Tryck <strong>Uppgradera</strong> knappen för att uppgradera din databas till Umbraco {0}</p> <p> Du behöver inte vara orolig. Inget innehåll kommer att raderas och efteråt kommer allt att fungera som vanligt! </p>]]></key>
|
||||
<key alias="databaseUpgradeDone"><![CDATA[Din databas har nu uppgraderats till den senaste versionen {0}.<br />Tryck <strong>Nästa</strong> för att fortsätta.]]></key>
|
||||
<key alias="databaseUpgrade"><![CDATA[<p> Tryck <strong>Uppgradera</strong> knappen för att uppgradera din databas till Umbraco %0%</p> <p> Du behöver inte vara orolig. Inget innehåll kommer att raderas och efteråt kommer allt att fungera som vanligt! </p>]]></key>
|
||||
<key alias="databaseUpgradeDone"><![CDATA[Din databas har nu uppgraderats till den senaste versionen %0%.<br />Tryck <strong>Nästa</strong> för att fortsätta.]]></key>
|
||||
<key alias="databaseUpToDate"><![CDATA[Din nuvarande databas behöver inte uppgraderas! Klicka <strong>Nästa</strong> för att fortsätta med konfigurationsguiden]]></key>
|
||||
<key alias="defaultUserChangePass"><![CDATA[<strong>Lösenordet på standardanvändaren måste bytas!</strong>]]></key>
|
||||
<key alias="defaultUserDisabled"><![CDATA[<strong>Standardanvändaren har avaktiverats eller har inte åtkomst till Umbraco!</strong></p><p>Du behöver inte göra något ytterligare här. Klicka <b>Next</b> för att fortsätta.]]></key>
|
||||
@@ -455,13 +455,13 @@
|
||||
<key alias="thankYou">Tack för att du valde Umbraco</key>
|
||||
<key alias="theEndBrowseSite"><![CDATA[<h3>Besök din nya webbplats</h3> Du installerade Runway, så varför inte se hur din nya webbplats ser ut.]]></key>
|
||||
<key alias="theEndFurtherHelp"><![CDATA[<h3>Ytterligare hjälp och information</h3> Få hjälp från våra prisbelönta community, bläddra i dokumentationen eller titta på några gratis videor om hur man bygger en enkel webbplats, hur du använder paket eller en snabbguide till Umbracos terminologi]]></key>
|
||||
<key alias="theEndHeader">Umbraco {0} är installerat och klart för användning</key>
|
||||
<key alias="theEndInstallFailed"><![CDATA[För att avsluta installationen måste du manuellt redigera <strong>/web.config</strong> filen och ändra AppSettingsnyckeln <strong>UmbracoConfigurationStatus</strong> på slutet till <strong>{0}</strong>]]></key>
|
||||
<key alias="theEndHeader">Umbraco %0% är installerat och klart för användning</key>
|
||||
<key alias="theEndInstallFailed"><![CDATA[För att avsluta installationen måste du manuellt redigera <strong>/web.config</strong> filen och ändra AppSettingsnyckeln <strong>UmbracoConfigurationStatus</strong> på slutet till <strong>%0%</strong>]]></key>
|
||||
<key alias="theEndInstallSuccess"><![CDATA[Du kan <strong>börja omedelbart</strong> genom att klicka på "Starta Umbraco"-knappen nedan. <br />Om du är en <strong>ny Umbraco användare</strong>kan du hitta massor av resurser på våra kom igång sidor.]]></key>
|
||||
<key alias="theEndOpenUmbraco"><![CDATA[<h3>Starta Umbraco</h3> För att administrera din webbplats öppnar du bara Umbraco back office och börjar lägga till innehåll, uppdatera mallar och stilmallar eller lägga till nya funktioner.]]></key>
|
||||
<key alias="Unavailable">Anslutningen till databasen misslyckades.</key>
|
||||
<key alias="watch">Se</key>
|
||||
<key alias="welcomeIntro"><![CDATA[Den här guiden kommer att guida dig genom processen med att konfigurera <strong>Umbraco {0}</strong> antingen för en ny installation eller en uppgradering från version 3.0. <br /><br /> Tryck på <strong>"next"</strong> för att börja.]]></key>
|
||||
<key alias="welcomeIntro"><![CDATA[Den här guiden kommer att guida dig genom processen med att konfigurera <strong>Umbraco %0%</strong> antingen för en ny installation eller en uppgradering från version 3.0. <br /><br /> Tryck på <strong>"next"</strong> för att börja.]]></key>
|
||||
<key alias="Version3">Umbraco Version 3</key>
|
||||
<key alias="Version4">Umbraco Version 4</key>
|
||||
</area>
|
||||
@@ -474,7 +474,7 @@
|
||||
<key alias="renewSession">Förnya nu för att spara ditt arbete</key>
|
||||
</area>
|
||||
<area alias="login">
|
||||
<key alias="bottomText"><![CDATA[<p style="text-align:right;">© 2001 - {0} <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.com</a></p> ]]></key>
|
||||
<key alias="bottomText"><![CDATA[<p style="text-align:right;">© 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.com</a></p> ]]></key>
|
||||
<key alias="greeting0">Happy super Sunday</key>
|
||||
<key alias="greeting1">Happy manic Monday</key>
|
||||
<key alias="greeting2">Happy tremendous Tuesday</key>
|
||||
@@ -496,10 +496,10 @@
|
||||
</area>
|
||||
<area alias="moveOrCopy">
|
||||
<key alias="choose">Välj sida ovan...</key>
|
||||
<key alias="copyDone">{0} har kopierats till {1}</key>
|
||||
<key alias="copyTo">Ange mål att kopiera sidan {0} till nedan</key>
|
||||
<key alias="moveDone">{0} har flyttats till {1}</key>
|
||||
<key alias="moveTo">Ange vart sidan {0} skall flyttas till nedan</key>
|
||||
<key alias="copyDone">%0% har kopierats till %1%</key>
|
||||
<key alias="copyTo">Ange mål att kopiera sidan %0% till nedan</key>
|
||||
<key alias="moveDone">%0% har flyttats till %1%</key>
|
||||
<key alias="moveTo">Ange vart sidan %0% skall flyttas till nedan</key>
|
||||
<key alias="nodeSelected">är nu roten för ditt nya innehåll. Klicka 'ok' nedan.</key>
|
||||
<key alias="noNodeSelected">Du har inte valt någon sida än. Välj en sida i listan ovan och klicka sedan 'fortsätt'.</key>
|
||||
<key alias="notAllowedAtRoot">Aktuell nod får inte existera i roten</key>
|
||||
@@ -550,7 +550,7 @@
|
||||
<area alias="placeholders">
|
||||
<key alias="entername">Fyll i ett namn...</key>
|
||||
<key alias="filter">Skriv för att filtrera...</key>
|
||||
<key alias="nameentity">Namnge {0}...</key>
|
||||
<key alias="nameentity">Namnge %0%...</key>
|
||||
<key alias="password">Fyll i ditt lösenord</key>
|
||||
<key alias="search">Skriv för att söka...</key>
|
||||
<key alias="username">Fyll i ditt lösenord</key>
|
||||
@@ -562,8 +562,8 @@
|
||||
<key alias="paErrorPage">Sida med felmeddelande</key>
|
||||
<key alias="paErrorPageHelp">Används när en användare är inloggad, men saknar rättigheter att se sidan</key>
|
||||
<key alias="paHowWould">Välj hur du vill lösenordsskydda sidan</key>
|
||||
<key alias="paIsProtected">{0} är nu lösenordsskyddad</key>
|
||||
<key alias="paIsRemoved">Lösenordsskyddet är nu borttaget på {0}</key>
|
||||
<key alias="paIsProtected">%0% är nu lösenordsskyddad</key>
|
||||
<key alias="paIsRemoved">Lösenordsskyddet är nu borttaget på %0%</key>
|
||||
<key alias="paLoginPage">Inloggningssida</key>
|
||||
<key alias="paLoginPageHelp">Välj sidan med inloggningsformuläret</key>
|
||||
<key alias="paRemoveProtection">Ta bort lösenordsskydd</key>
|
||||
@@ -574,17 +574,17 @@
|
||||
<key alias="paSimpleHelp">Välj detta alternativ om du vill skydda sidan med ett enkelt användarnamn och lösenord. Alla loggar då in med samma inloggningsuppgifter.</key>
|
||||
</area>
|
||||
<area alias="publish">
|
||||
<key alias="contentPublishedFailedAwaitingRelease"> {0} kunde inte publiceras på grund av dess tidsinställda publicering.</key>
|
||||
<key alias="contentPublishedFailedByEvent">{0} kunde inte publiceras på grund av att ett tredjepartstillägg avbröt publiceringen.</key>
|
||||
<key alias="contentPublishedFailedByParent">{0} kan inte publiceras, på grund av att överordnad nod inte är publicerad.</key>
|
||||
<key alias="contentPublishedFailedInvalid">{0} kunde inte publiceras på grund av följande orsaker: {1} passerade inte valideringen.</key>
|
||||
<key alias="contentPublishedFailedAwaitingRelease"> %0% kunde inte publiceras på grund av dess tidsinställda publicering.</key>
|
||||
<key alias="contentPublishedFailedByEvent">%0% kunde inte publiceras på grund av att ett tredjepartstillägg avbröt publiceringen.</key>
|
||||
<key alias="contentPublishedFailedByParent">%0% kan inte publiceras, på grund av att överordnad nod inte är publicerad.</key>
|
||||
<key alias="contentPublishedFailedInvalid">%0% kunde inte publiceras på grund av följande orsaker: %1% passerade inte valideringen.</key>
|
||||
<key alias="includeUnpublished">Inkludera opublicerade undersidor</key>
|
||||
<key alias="inProgress">Publicering pågår - vänligen vänta...</key>
|
||||
<key alias="inProgressCounter">{0} av {1} sidor har publicerats...</key>
|
||||
<key alias="nodePublish">{0} har publicerats</key>
|
||||
<key alias="nodePublishAll">{0} och underliggande sidor har publicerats</key>
|
||||
<key alias="publishAll">Publicera {0} och alla dess underordnade sidor</key>
|
||||
<key alias="publishHelp"><![CDATA[Klicka på <em>ok</em> för att publicera <strong>{0}</strong>. Därmed blir innehållet publikt.<br/><br /> Du kan publicera denna sida och alla dess undersidor genom att kryssa i <em>publicera alla undersidor</em>. ]]></key>
|
||||
<key alias="inProgressCounter">%0% av %1% sidor har publicerats...</key>
|
||||
<key alias="nodePublish">%0% har publicerats</key>
|
||||
<key alias="nodePublishAll">%0% och underliggande sidor har publicerats</key>
|
||||
<key alias="publishAll">Publicera %0% och alla dess underordnade sidor</key>
|
||||
<key alias="publishHelp"><![CDATA[Klicka på <em>ok</em> för att publicera <strong>%0%</strong>. Därmed blir innehållet publikt.<br/><br /> Du kan publicera denna sida och alla dess undersidor genom att kryssa i <em>publicera alla undersidor</em>. ]]></key>
|
||||
</area>
|
||||
<area alias="relatedlinks">
|
||||
<key alias="addExternal">Lägg till extern länk</key>
|
||||
@@ -654,12 +654,12 @@
|
||||
<key alias="contentPublishedFailedByEvent">Publiceringen avbröts av ett tredjepartstillägg</key>
|
||||
<key alias="contentTypeDublicatePropertyType">Egenskapstyp finns redan</key>
|
||||
<key alias="contentTypePropertyTypeCreated">Egenskapstyp skapad</key>
|
||||
<key alias="contentTypePropertyTypeCreatedText"><![CDATA[Namn: {0} <br /> Datatyp: {1}]]></key>
|
||||
<key alias="contentTypePropertyTypeCreatedText"><![CDATA[Namn: %0% <br /> Datatyp: %1%]]></key>
|
||||
<key alias="contentTypePropertyTypeDeleted">Egenskapstypen har tagits bort</key>
|
||||
<key alias="contentTypeSavedHeader">Innehållstypen har sparats</key>
|
||||
<key alias="contentTypeTabCreated">Ny flik skapad</key>
|
||||
<key alias="contentTypeTabDeleted">Fliken har tagits bort</key>
|
||||
<key alias="contentTypeTabDeletedText">Fliken med id: {0} har tagits bort</key>
|
||||
<key alias="contentTypeTabDeletedText">Fliken med id: %0% har tagits bort</key>
|
||||
<key alias="contentUnpublished">Innehållet är avpublicerat</key>
|
||||
<key alias="cssErrorHeader">Stilmallen kunde inte sparas</key>
|
||||
<key alias="cssSavedHeader">Stilmallen sparades</key>
|
||||
@@ -724,6 +724,32 @@
|
||||
<key alias="quickGuide">Snabbguide för taggar i Umbracos sidmallar</key>
|
||||
<key alias="template">Sidmall</key>
|
||||
</area>
|
||||
<area alias="grid">
|
||||
<key alias="insertControl">Lägg till</key>
|
||||
<key alias="addRows">Lägg till rad</key>
|
||||
<key alias="addElement"><![CDATA[Klicka på <i class="icon icon-add blue"></i> nedan för att lägga till ditt första element]]></key>
|
||||
<key alias="clickToEmbed">Klicka för att lägga in</key>
|
||||
<key alias="clickToInsertImage">Klicka för att lägga till bild</key>
|
||||
<key alias="placeholderImageCaption">Bildtext...</key>
|
||||
<key alias="placeholderWriteHere">Skriv här...</key>
|
||||
<key alias="gridLayouts">Rutnätslayouter</key>
|
||||
<key alias="gridLayoutsDetail">Layouter är arbetsytan för rutnätet, oftast så behöver du bara en eller två layouter</key>
|
||||
<key alias="addGridLayout">Lägg till layout</key>
|
||||
<key alias="addGridLayoutDetail">Redigera layouten genom att sätta kolumnbredd och lägg till fler sektioner</key>
|
||||
<key alias="rowConfigurations">Radkonfigureringar</key>
|
||||
<key alias="rowConfigurationsDetail">Rader är fördefinierade celler, arrangerade horisontellt</key>
|
||||
<key alias="addRowConfiguration">Lägg till radkonfiguration</key>
|
||||
<key alias="addRowConfigurationDetail">Justera rad genom att ställa in cellbredder och lägga till ytterligare celler</key>
|
||||
<key alias="columns">Kolumner</key>
|
||||
<key alias="columnsDetails">Sammanlagda antalet kolumner i rutnätslayout</key>
|
||||
<key alias="settings">Inställningar</key>
|
||||
<key alias="settingsDetails">Konfigurera vilka inställningar redaktörer kan ändra</key>
|
||||
<key alias="styles">Stilar</key>
|
||||
<key alias="stylesDetails">Konfigurera vilken styling redaktörer kan ändra</key>
|
||||
<key alias="settingDialogDetails">Inställningarna kommer bara att sparas om det inmatade json-konfigurationen är giltig</key>
|
||||
<key alias="allowAllEditors">Tillåt alla editors</key>
|
||||
<key alias="allowAllRowConfigurations">Tillåt alla rad- konfigurationer</key>
|
||||
</area>
|
||||
<area alias="templateEditor">
|
||||
<key alias="alternativeField">Alternativt fält</key>
|
||||
<key alias="alternativeText">Alternativ text</key>
|
||||
@@ -764,13 +790,13 @@
|
||||
<key alias="DownloadXmlDTD">Ladda hem DTD för XML</key>
|
||||
<key alias="fields">Fält</key>
|
||||
<key alias="includeSubpages">Inkludera undersidor</key>
|
||||
<key alias="mailBody">Hej {0}. Detta är ett automatisk mail skickat for att informera dig om att det finns en översättningsförfrågan på dokument '%1' till '{5}' skickad av {2}. För att redigere, besök http://{3}/translation/details.aspx?id={4}. För att få en översikt över dina översättningsuppgigter loggar du in i Umbraco på: http://{3}</key>
|
||||
<key alias="mailSubject">[{0}] Översättningsuppgit för {1}</key>
|
||||
<key alias="mailBody">Hej %0%. Detta är ett automatisk mail skickat for att informera dig om att det finns en översättningsförfrågan på dokument '%1%' till '%5%' skickad av %2%. För att redigera, besök http://%3%/translation/details.aspx?id=%4%. För att få en översikt över dina översättningsuppgigter loggar du in i Umbraco på: http://%3%</key>
|
||||
<key alias="mailSubject">[%0%] Översättningsuppgift för %1%</key>
|
||||
<key alias="noTranslators">Hittade inga användare som är översättare. Vänligen skapa en användare som är översättare innan du börjar skicka innehåll för översättning</key>
|
||||
<key alias="ownedTasks">Arbetsuppgifter som du har skapat</key>
|
||||
<key alias="ownedTasksHelp"><![CDATA[Listan nedan visar sidor <strong>som du har skapat</strong>. För att se en detaljvy med kommentarer, klicka på "Detaljer" eller på sidans namn. Du kan också ladda ned sidan i XML-format genom att klicka på länken "Ladda ned XML".<br/> För att markera ett översättningsjobb som avslutat, gå till detaljvyn och klicka på knappen "Stäng".]]></key>
|
||||
<key alias="pageHasBeenSendToTranslation">Sidan {0} har skickats för översättning</key>
|
||||
<key alias="sendToTranslate">Skicka sidan {0} för översättning</key>
|
||||
<key alias="pageHasBeenSendToTranslation">Sidan %0% har skickats för översättning</key>
|
||||
<key alias="sendToTranslate">Skicka sidan %0% för översättning</key>
|
||||
<key alias="taskAssignedBy">Tilldelat av</key>
|
||||
<key alias="taskOpened">Arbetsuppgift öppnad</key>
|
||||
<key alias="totalWords">Totalt antal ord</key>
|
||||
@@ -815,7 +841,7 @@
|
||||
</area>
|
||||
<area alias="update">
|
||||
<key alias="updateAvailable">Ny uppdatering tillgänglig</key>
|
||||
<key alias="updateDownloadText">{0} är klart, klicka här för att ladda ner</key>
|
||||
<key alias="updateDownloadText">%0% är klart, klicka här för att ladda ner</key>
|
||||
<key alias="updateNoServer">Ingen kontakt med server</key>
|
||||
<key alias="updateNoServerError">Fel vid kontroll av uppdatering. Se trace-stack för mer information.</key>
|
||||
</area>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Umbraco.Web.Editors
|
||||
[HttpGet]
|
||||
public JsonNetResult LocalizedText(string culture = null)
|
||||
{
|
||||
var cultureInfo = culture == null
|
||||
var cultureInfo = string.IsNullOrWhiteSpace(culture)
|
||||
//if the user is logged in, get their culture, otherwise default to 'en'
|
||||
? User.Identity.IsAuthenticated && User.Identity is UmbracoBackOfficeIdentity
|
||||
? Security.CurrentUser.GetUserCulture(Services.TextService)
|
||||
@@ -445,48 +445,20 @@ namespace Umbraco.Web.Editors
|
||||
return JavaScript(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders out all JavaScript references that have bee declared in IActions
|
||||
/// </summary>
|
||||
private static IEnumerable<string> GetLegacyActionJs(LegacyJsActionType type)
|
||||
internal static IEnumerable<string> GetLegacyActionJsForActions(LegacyJsActionType type, IEnumerable<string> values)
|
||||
{
|
||||
var blockList = new List<string>();
|
||||
var urlList = new List<string>();
|
||||
foreach (var jsFile in global::umbraco.BusinessLogic.Actions.Action.GetJavaScriptFileReferences())
|
||||
foreach (var jsFile in values)
|
||||
{
|
||||
//validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text
|
||||
//block instead.
|
||||
var isValid = true;
|
||||
|
||||
if (Uri.IsWellFormedUriString(jsFile, UriKind.RelativeOrAbsolute))
|
||||
var isJsPath = jsFile.DetectIsJavaScriptPath();
|
||||
if (isJsPath.Success)
|
||||
{
|
||||
//ok it validates, but so does alert('hello'); ! so we need to do more checks
|
||||
|
||||
//here are the valid chars in a url without escaping
|
||||
if (Regex.IsMatch(jsFile, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]"))
|
||||
isValid = false;
|
||||
|
||||
//we'll have to be smarter and just check for certain js patterns now too!
|
||||
var jsPatterns = new string[] {@"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"=="};
|
||||
if (jsPatterns.Any(p => Regex.IsMatch(jsFile, p)))
|
||||
{
|
||||
isValid = false;
|
||||
}
|
||||
if (isValid)
|
||||
{
|
||||
//it is a valid URL add to Url list
|
||||
urlList.Add(jsFile);
|
||||
}
|
||||
urlList.Add(isJsPath.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (isValid == false)
|
||||
{
|
||||
//it isn't a valid URL, must be a js block
|
||||
blockList.Add(jsFile);
|
||||
blockList.Add(isJsPath.Result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,8 +472,16 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
return blockList;
|
||||
}
|
||||
|
||||
private enum LegacyJsActionType
|
||||
|
||||
/// <summary>
|
||||
/// Renders out all JavaScript references that have bee declared in IActions
|
||||
/// </summary>
|
||||
private static IEnumerable<string> GetLegacyActionJs(LegacyJsActionType type)
|
||||
{
|
||||
return GetLegacyActionJsForActions(type, global::umbraco.BusinessLogic.Actions.Action.GetJavaScriptFileReferences());
|
||||
}
|
||||
|
||||
internal enum LegacyJsActionType
|
||||
{
|
||||
JsBlock,
|
||||
JsUrl
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -23,9 +23,6 @@ namespace Umbraco.Web.PropertyEditors
|
||||
|
||||
private IDictionary<string, object> _defaultPreVals;
|
||||
|
||||
/// <summary>
|
||||
/// Overridden because we ONLY support Date (no time) format and we don't have pre-values in the db.
|
||||
/// </summary>
|
||||
public override IDictionary<string, object> DefaultPreValues
|
||||
{
|
||||
get { return _defaultPreVals; }
|
||||
@@ -60,5 +57,16 @@ namespace Umbraco.Web.PropertyEditors
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override PreValueEditor CreatePreValueEditor()
|
||||
{
|
||||
return new DatePreValueEditor();
|
||||
}
|
||||
|
||||
internal class DatePreValueEditor : PreValueEditor
|
||||
{
|
||||
[PreValueField("format", "Date format", "textstring", Description = "If left empty then the format is YYYY-MM-DD. (see momentjs.com for supported formats)")]
|
||||
public string DefaultValue { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -106,7 +105,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
else if (_timer == null) // we don't have a timer yet
|
||||
{
|
||||
LogHelper.Debug<XmlCacheFilePersister>("Touched, was idle, start and save in {0}ms.");
|
||||
LogHelper.Debug<XmlCacheFilePersister>("Touched, was idle, start and save in {0}ms.", () => WaitMilliseconds);
|
||||
_initialTouch = DateTime.Now;
|
||||
_timer = new Timer(_ => TimerRelease());
|
||||
_timer.Change(WaitMilliseconds, 0);
|
||||
@@ -130,7 +129,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
}
|
||||
|
||||
if (runNow)
|
||||
Run();
|
||||
//Run();
|
||||
LogHelper.Warn<XmlCacheFilePersister>("Cannot write now because we are going down, changes may be lost.");
|
||||
|
||||
return ret; // this, by default, unless we created a new one
|
||||
}
|
||||
@@ -140,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)
|
||||
{
|
||||
@@ -180,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)
|
||||
{
|
||||
@@ -202,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
// exists for logging purposes
|
||||
internal class BackgroundTaskRunner
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Manages a queue of tasks of type <typeparamref name="T"/> and runs them in the background.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the managed tasks.</typeparam>
|
||||
/// <remarks>The task runner is web-aware and will ensure that it shuts down correctly when the AppDomain
|
||||
/// shuts down (ie is unloaded).</remarks>
|
||||
internal class BackgroundTaskRunner<T> : IBackgroundTaskRunner<T>
|
||||
internal class BackgroundTaskRunner<T> : BackgroundTaskRunner, IBackgroundTaskRunner<T>
|
||||
where T : class, IBackgroundTask
|
||||
{
|
||||
private readonly string _logPrefix;
|
||||
private readonly BackgroundTaskRunnerOptions _options;
|
||||
private readonly BlockingCollection<T> _tasks = new BlockingCollection<T>();
|
||||
private readonly object _locker = new object();
|
||||
private readonly ManualResetEventSlim _completedEvent = new ManualResetEventSlim(false);
|
||||
private BackgroundTaskRunnerAwaiter<T> _awaiter;
|
||||
|
||||
// that event is used to stop the pump when it is alive and waiting
|
||||
// on a latched task - so it waits on the latch, the cancellation token,
|
||||
// and the completed event
|
||||
private readonly ManualResetEventSlim _completedEvent = new ManualResetEventSlim(false);
|
||||
|
||||
// in various places we are testing these vars outside a lock, so make them volatile
|
||||
private volatile bool _isRunning; // is running
|
||||
private volatile bool _isCompleted; // does not accept tasks anymore, may still be running
|
||||
private Task _runningTask;
|
||||
|
||||
private Task _runningTask;
|
||||
private CancellationTokenSource _tokenSource;
|
||||
|
||||
private bool _terminating; // ensures we raise that event only once
|
||||
private bool _terminated; // remember we've terminated
|
||||
private TaskCompletionSource<int> _terminatedSource; // awaitable source
|
||||
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskError;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskStarting;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCompleted;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCancelled;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Completed;
|
||||
|
||||
// triggers when the runner stops (but could start again if a task is added to it)
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Stopped;
|
||||
|
||||
// triggers when the hosting environment requests that the runner terminates
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Terminating;
|
||||
|
||||
// triggers when the runner terminates (no task can be added, no task is running)
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Terminated;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class.
|
||||
/// </summary>
|
||||
public BackgroundTaskRunner()
|
||||
: this(new BackgroundTaskRunnerOptions())
|
||||
: this(typeof (T).FullName, new BackgroundTaskRunnerOptions())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the runner.</param>
|
||||
public BackgroundTaskRunner(string name)
|
||||
: this(name, new BackgroundTaskRunnerOptions())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
@@ -49,11 +76,22 @@ namespace Umbraco.Web.Scheduling
|
||||
/// </summary>
|
||||
/// <param name="options">The set of options.</param>
|
||||
public BackgroundTaskRunner(BackgroundTaskRunnerOptions options)
|
||||
: this(typeof (T).FullName, options)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class with a set of options.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the runner.</param>
|
||||
/// <param name="options">The set of options.</param>
|
||||
public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException("options");
|
||||
_options = options;
|
||||
_logPrefix = "[" + name + "] ";
|
||||
|
||||
HostingEnvironment.RegisterObject(this);
|
||||
if (options.Hosted)
|
||||
HostingEnvironment.RegisterObject(this);
|
||||
|
||||
if (options.AutoStart)
|
||||
StartUp();
|
||||
@@ -84,7 +122,7 @@ namespace Umbraco.Web.Scheduling
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaiter used to await the running Threading.Task.
|
||||
/// Gets the running task as an immutable object.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">There is no running task.</exception>
|
||||
/// <remarks>
|
||||
@@ -92,32 +130,54 @@ namespace Umbraco.Web.Scheduling
|
||||
/// a background task is added to the queue. Unless the KeepAlive option is true, there
|
||||
/// will be no running task when the queue is empty.
|
||||
/// </remarks>
|
||||
public ThreadingTaskAwaiter CurrentThreadingTask
|
||||
public ThreadingTaskImmutable CurrentThreadingTask
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_runningTask == null)
|
||||
throw new InvalidOperationException("There is no current Threading.Task.");
|
||||
return new ThreadingTaskAwaiter(_runningTask);
|
||||
lock (_locker)
|
||||
{
|
||||
if (_runningTask == null)
|
||||
throw new InvalidOperationException("There is no current Threading.Task.");
|
||||
return new ThreadingTaskImmutable(_runningTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaiter used to await the BackgroundTaskRunner running operation
|
||||
/// Gets an awaitable used to await the runner running operation.
|
||||
/// </summary>
|
||||
/// <returns>An awaiter for the BackgroundTaskRunner running operation</returns>
|
||||
/// <remarks>
|
||||
/// <para>This is used to wait until the background task runner is no longer running (IsRunning == false)
|
||||
/// </para>
|
||||
/// <para> So long as we have a method called GetAwaiter() that returns an instance of INotifyCompletion
|
||||
/// we can await anything. In this case we are awaiting with a custom BackgroundTaskRunnerAwaiter
|
||||
/// which waits for the Completed event to be raised.
|
||||
/// ref: http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public BackgroundTaskRunnerAwaiter<T> GetAwaiter()
|
||||
/// <returns>An awaitable instance.</returns>
|
||||
/// <remarks>Used to wait until the runner is no longer running (IsRunning == false),
|
||||
/// though the runner could be started again afterwards by adding tasks to it.</remarks>
|
||||
public ThreadingTaskImmutable StoppedAwaitable
|
||||
{
|
||||
return _awaiter ?? (_awaiter = new BackgroundTaskRunnerAwaiter<T>(this));
|
||||
get
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
var task = _runningTask ?? Task.FromResult(0);
|
||||
return new ThreadingTaskImmutable(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaitable used to await the runner.
|
||||
/// </summary>
|
||||
/// <returns>An awaitable instance.</returns>
|
||||
/// <remarks>Used to wait until the runner is terminated.</remarks>
|
||||
public ThreadingTaskImmutable TerminatedAwaitable
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (_terminatedSource == null && _terminated == false)
|
||||
_terminatedSource = new TaskCompletionSource<int>();
|
||||
var task = _terminatedSource == null ? Task.FromResult(0) : _terminatedSource.Task;
|
||||
return new ThreadingTaskImmutable(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,7 +193,7 @@ namespace Umbraco.Web.Scheduling
|
||||
throw new InvalidOperationException("The task runner has completed.");
|
||||
|
||||
// add task
|
||||
LogHelper.Debug<BackgroundTaskRunner<T>>("Task added {0}", task.GetType);
|
||||
LogHelper.Debug<BackgroundTaskRunner>(_logPrefix + "Task added {0}", task.GetType);
|
||||
_tasks.Add(task);
|
||||
|
||||
// start
|
||||
@@ -154,7 +214,7 @@ namespace Umbraco.Web.Scheduling
|
||||
if (_isCompleted) return false;
|
||||
|
||||
// add task
|
||||
LogHelper.Debug<BackgroundTaskRunner<T>>("Task added {0}", task.GetType);
|
||||
LogHelper.Debug<BackgroundTaskRunner>(_logPrefix + "Task added {0}", task.GetType);
|
||||
_tasks.Add(task);
|
||||
|
||||
// start
|
||||
@@ -195,7 +255,7 @@ namespace Umbraco.Web.Scheduling
|
||||
// create a new token source since this is a new process
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
_runningTask = PumpIBackgroundTasks(Task.Factory, _tokenSource.Token);
|
||||
LogHelper.Debug<BackgroundTaskRunner<T>>("Starting");
|
||||
LogHelper.Debug<BackgroundTaskRunner>(_logPrefix + "Starting");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -221,7 +281,7 @@ namespace Umbraco.Web.Scheduling
|
||||
if (force)
|
||||
{
|
||||
// we must bring everything down, now
|
||||
Thread.Sleep(100); // give time to CompleAdding()
|
||||
Thread.Sleep(100); // give time to CompleteAdding()
|
||||
lock (_locker)
|
||||
{
|
||||
// was CompleteAdding() enough?
|
||||
@@ -236,7 +296,6 @@ namespace Umbraco.Web.Scheduling
|
||||
// tasks in the queue will be executed...
|
||||
if (wait == false) return;
|
||||
_runningTask.Wait(); // wait for whatever is running to end...
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -256,27 +315,35 @@ namespace Umbraco.Web.Scheduling
|
||||
// because the pump does not lock, there's a race condition,
|
||||
// the pump may stop and then we still have tasks to process,
|
||||
// and then we must restart the pump - lock to avoid race cond
|
||||
var onStopped = false;
|
||||
lock (_locker)
|
||||
{
|
||||
if (token.IsCancellationRequested || _tasks.Count == 0)
|
||||
{
|
||||
LogHelper.Debug<BackgroundTaskRunner<T>>("_isRunning = false");
|
||||
LogHelper.Debug<BackgroundTaskRunner>(_logPrefix + "Stopping");
|
||||
|
||||
_isRunning = false; // done
|
||||
if (_options.PreserveRunningTask == false)
|
||||
_runningTask = null;
|
||||
//raise event
|
||||
OnCompleted();
|
||||
return;
|
||||
|
||||
// stopped
|
||||
_isRunning = false;
|
||||
onStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (onStopped)
|
||||
{
|
||||
OnEvent(Stopped, "Stopped");
|
||||
return;
|
||||
}
|
||||
|
||||
// if _runningTask is taskSource.Task then we must keep continuing it,
|
||||
// not starting a new taskSource, else _runningTask would complete and
|
||||
// something may be waiting on it
|
||||
//PumpIBackgroundTasks(factory, token); // restart
|
||||
// ReSharper disable once MethodSupportsCancellation // always run
|
||||
// ReSharper disable MethodSupportsCancellation // always run
|
||||
t.ContinueWithTask(_ => PumpIBackgroundTasks(factory, token)); // restart
|
||||
// ReSharper restore MethodSupportsCancellation
|
||||
});
|
||||
|
||||
Action<Task> pump = null;
|
||||
@@ -288,7 +355,7 @@ namespace Umbraco.Web.Scheduling
|
||||
if (task != null && task.IsFaulted)
|
||||
{
|
||||
var exception = task.Exception;
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("Task runner exception.", exception);
|
||||
LogHelper.Error<BackgroundTaskRunner>(_logPrefix + "Task runner exception.", exception);
|
||||
}
|
||||
|
||||
// is it ok to run?
|
||||
@@ -298,6 +365,7 @@ namespace Umbraco.Web.Scheduling
|
||||
// the blocking MoveNext will end if token is cancelled or collection is completed
|
||||
T bgTask;
|
||||
var hasBgTask = _options.KeepAlive
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
? (bgTask = enumerator.MoveNext() ? enumerator.Current : null) != null // blocking
|
||||
: _tasks.TryTake(out bgTask); // non-blocking
|
||||
|
||||
@@ -318,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;
|
||||
}
|
||||
@@ -343,7 +412,7 @@ namespace Umbraco.Web.Scheduling
|
||||
return taskSourceContinuing;
|
||||
}
|
||||
|
||||
private bool TaskSourceCanceled(TaskCompletionSource<object> taskSource, CancellationToken token)
|
||||
private static bool TaskSourceCanceled(TaskCompletionSource<object> taskSource, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
@@ -353,7 +422,7 @@ namespace Umbraco.Web.Scheduling
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TaskSourceCompleted(TaskCompletionSource<object> taskSource, CancellationToken token)
|
||||
private static void TaskSourceCompleted(TaskCompletionSource<object> taskSource, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
taskSource.SetCanceled();
|
||||
@@ -375,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.
|
||||
@@ -383,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)
|
||||
{
|
||||
@@ -394,85 +469,55 @@ namespace Umbraco.Web.Scheduling
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("Task has failed.", ex);
|
||||
LogHelper.Error<BackgroundTaskRunner>(_logPrefix + "Task has failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
private void OnEvent(TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> handler, string name)
|
||||
{
|
||||
if (handler == null) return;
|
||||
OnEvent(handler, name, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void OnEvent<TArgs>(TypedEventHandler<BackgroundTaskRunner<T>, TArgs> handler, string name, TArgs e)
|
||||
{
|
||||
if (handler == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner>(_logPrefix + name + " exception occurred", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnTaskError(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskError;
|
||||
if (handler != null) handler(this, e);
|
||||
OnEvent(TaskError, "TaskError", e);
|
||||
}
|
||||
|
||||
protected virtual void OnTaskStarting(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskStarting;
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("TaskStarting exception occurred", ex);
|
||||
}
|
||||
}
|
||||
OnEvent(TaskStarting, "TaskStarting", e);
|
||||
}
|
||||
|
||||
protected virtual void OnTaskCompleted(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskCompleted;
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("TaskCompleted exception occurred", ex);
|
||||
}
|
||||
}
|
||||
OnEvent(TaskCompleted, "TaskCompleted", e);
|
||||
}
|
||||
|
||||
protected virtual void OnTaskCancelled(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskCancelled;
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("TaskCancelled exception occurred", ex);
|
||||
}
|
||||
}
|
||||
OnEvent(TaskCancelled, "TaskCancelled", e);
|
||||
|
||||
//dispose it
|
||||
e.Task.Dispose();
|
||||
}
|
||||
|
||||
protected virtual void OnCompleted()
|
||||
{
|
||||
var handler = Completed;
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("OnCompleted exception occurred", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
@@ -482,7 +527,7 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
~BackgroundTaskRunner()
|
||||
{
|
||||
this.Dispose(false);
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -493,7 +538,7 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (this.IsDisposed || disposing == false)
|
||||
if (IsDisposed || disposing == false)
|
||||
return;
|
||||
|
||||
lock (_disposalLocker)
|
||||
@@ -525,28 +570,41 @@ namespace Umbraco.Web.Scheduling
|
||||
/// </remarks>
|
||||
public void Stop(bool immediate)
|
||||
{
|
||||
// the first time the hosting environment requests that the runner terminates,
|
||||
// raise the Terminating event - that could be used to prevent any process that
|
||||
// would expect the runner to be available from starting.
|
||||
var onTerminating = false;
|
||||
lock (_locker)
|
||||
{
|
||||
if (_terminating == false)
|
||||
{
|
||||
_terminating = true;
|
||||
LogHelper.Info<BackgroundTaskRunner>(_logPrefix + "Terminating" + (immediate ? " (immediate)" : ""));
|
||||
onTerminating = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (onTerminating)
|
||||
OnEvent(Terminating, "Terminating");
|
||||
|
||||
if (immediate == false)
|
||||
{
|
||||
// The Stop method is first called with the immediate parameter set to false. The object can either complete
|
||||
// processing, call the UnregisterObject method, and then return or it can return immediately and complete
|
||||
// processing asynchronously before calling the UnregisterObject method.
|
||||
|
||||
LogHelper.Debug<BackgroundTaskRunner<T>>("Shutting down, waiting for tasks to complete.");
|
||||
LogHelper.Info<BackgroundTaskRunner>(_logPrefix + "Waiting for tasks to complete");
|
||||
Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait
|
||||
|
||||
// raise the completed event only after the running task has completed
|
||||
// and there's no more task running
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_runningTask != null)
|
||||
_runningTask.ContinueWith(_ =>
|
||||
{
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Down, tasks completed.");
|
||||
});
|
||||
_runningTask.ContinueWith(_ => Terminate(false));
|
||||
else
|
||||
{
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Down, tasks completed.");
|
||||
}
|
||||
Terminate(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -556,13 +614,31 @@ namespace Umbraco.Web.Scheduling
|
||||
// immediate parameter is true, the registered object must call the UnregisterObject method before returning;
|
||||
// otherwise, its registration will be removed by the application manager.
|
||||
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Shutting down immediately.");
|
||||
LogHelper.Info<BackgroundTaskRunner>(_logPrefix + "Cancelling tasks");
|
||||
Shutdown(true, true); // cancel all tasks, wait for the current one to end
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Down.");
|
||||
Terminate(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Terminate(bool immediate)
|
||||
{
|
||||
// signal the environment we have terminated
|
||||
// log
|
||||
// raise the Terminated event
|
||||
// complete the awaitable completion source, if any
|
||||
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
LogHelper.Info<BackgroundTaskRunner>(_logPrefix + "Tasks " + (immediate ? "cancelled" : "completed") + ", terminated");
|
||||
OnEvent(Terminated, "Terminated");
|
||||
|
||||
TaskCompletionSource<int> terminatedSource;
|
||||
lock (_locker)
|
||||
{
|
||||
_terminated = true;
|
||||
terminatedSource = _terminatedSource;
|
||||
}
|
||||
if (terminatedSource != null)
|
||||
terminatedSource.SetResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom awaiter used to await when the BackgroundTaskRunner is completed (IsRunning == false)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <remarks>
|
||||
/// This custom awaiter simply uses a TaskCompletionSource to set the result when the Completed event of the
|
||||
/// BackgroundTaskRunner executes.
|
||||
/// A custom awaiter requires implementing INotifyCompletion as well as IsCompleted, OnCompleted and GetResult
|
||||
/// see: http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx
|
||||
/// </remarks>
|
||||
internal class BackgroundTaskRunnerAwaiter<T> : INotifyCompletion where T : class, IBackgroundTask
|
||||
{
|
||||
private readonly BackgroundTaskRunner<T> _runner;
|
||||
private readonly TaskCompletionSource<int> _tcs;
|
||||
private readonly TaskAwaiter<int> _awaiter;
|
||||
|
||||
public BackgroundTaskRunnerAwaiter(BackgroundTaskRunner<T> runner)
|
||||
{
|
||||
if (runner == null) throw new ArgumentNullException("runner");
|
||||
_runner = runner;
|
||||
|
||||
_tcs = new TaskCompletionSource<int>();
|
||||
|
||||
_awaiter = _tcs.Task.GetAwaiter();
|
||||
|
||||
if (_runner.IsRunning)
|
||||
{
|
||||
_runner.Completed += (s, e) =>
|
||||
{
|
||||
LogHelper.Debug<BackgroundTaskRunnerAwaiter<T>>("Setting result");
|
||||
|
||||
_tcs.SetResult(0);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
//not running, just set the result
|
||||
_tcs.SetResult(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BackgroundTaskRunnerAwaiter<T> GetAwaiter()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is completed when the runner is finished running
|
||||
/// </summary>
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
LogHelper.Debug<BackgroundTaskRunnerAwaiter<T>>("IsCompleted :: " + _tcs.Task.IsCompleted + ", " + (_runner.IsRunning == false));
|
||||
//Need to check if the task is completed because it might already be done on the ctor and the runner never runs
|
||||
return _tcs.Task.IsCompleted || _runner.IsRunning == false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
_awaiter.OnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void GetResult()
|
||||
{
|
||||
_awaiter.GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
public virtual bool RunsOnShutdown
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace Umbraco.Web.Scheduling
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the task is latched.
|
||||
/// </summary>
|
||||
/// <remarks>Should return false as soon as the condition is met.</remarks>
|
||||
bool IsLatched { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>();
|
||||
_tasksRunner = new BackgroundTaskRunner<IBackgroundTask>();
|
||||
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>();
|
||||
_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));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user