Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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.6
|
||||
+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.6")]
|
||||
[assembly: AssemblyInformationalVersion("7.2.6")]
|
||||
@@ -165,6 +165,8 @@ namespace Umbraco.Core
|
||||
/// </remarks>
|
||||
internal string OriginalRequestUrl { get; set; }
|
||||
|
||||
private bool _versionsDifferenceReported;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the version configured matches the assembly version
|
||||
/// </summary>
|
||||
@@ -174,17 +176,19 @@ namespace Umbraco.Core
|
||||
{
|
||||
try
|
||||
{
|
||||
string configStatus = ConfigurationStatus;
|
||||
string currentVersion = UmbracoVersion.Current.ToString(3);
|
||||
var configStatus = ConfigurationStatus;
|
||||
var currentVersion = UmbracoVersion.Current.ToString(3);
|
||||
var ok = configStatus == currentVersion;
|
||||
|
||||
|
||||
if (currentVersion != configStatus)
|
||||
if (ok == false && _versionsDifferenceReported == false)
|
||||
{
|
||||
LogHelper.Info<ApplicationContext>("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'");
|
||||
// remember it's been reported so we don't flood the log
|
||||
// no thread-safety so there may be a few log entries, doesn't matter
|
||||
_versionsDifferenceReported = true;
|
||||
LogHelper.Info<ApplicationContext>("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'");
|
||||
}
|
||||
|
||||
|
||||
return (configStatus == currentVersion);
|
||||
return ok;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -105,15 +105,22 @@ 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);
|
||||
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
|
||||
|
||||
@@ -129,7 +129,7 @@ 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);
|
||||
|
||||
@@ -138,10 +138,17 @@ namespace Umbraco.Core.Cache
|
||||
}
|
||||
}
|
||||
|
||||
// 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,7 +180,7 @@ 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)
|
||||
|
||||
|
||||
@@ -19,29 +19,11 @@ namespace Umbraco.Core.Cache
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#region Clear
|
||||
|
||||
public virtual void ClearAllCache()
|
||||
@@ -72,7 +54,7 @@ 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);
|
||||
var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
return value == null || value.GetType().ToString().InvariantEquals(typeName);
|
||||
})
|
||||
.Select(x => x.Key)
|
||||
@@ -92,7 +74,7 @@ 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);
|
||||
var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
return value == null || value.GetType() == typeOfT;
|
||||
})
|
||||
.Select(x => x.Key)
|
||||
@@ -112,7 +94,7 @@ 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);
|
||||
var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
if (value == null) return true;
|
||||
return value.GetType() == typeOfT
|
||||
&& predicate(x.Key, (T) value);
|
||||
@@ -161,7 +143,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 +158,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 +170,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)
|
||||
@@ -205,9 +187,9 @@ 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();
|
||||
@@ -215,7 +197,12 @@ namespace Umbraco.Core.Cache
|
||||
}
|
||||
}
|
||||
|
||||
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,7 +214,7 @@ 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)
|
||||
|
||||
|
||||
@@ -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.6");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Umbraco.Core
|
||||
|
||||
InitializeProfilerResolver();
|
||||
|
||||
_timer = DisposableTimer.DebugDuration<CoreBootManager>("Umbraco application starting", "Umbraco application startup complete");
|
||||
_timer = DisposableTimer.TraceDuration<CoreBootManager>("Umbraco application starting", "Umbraco application startup complete");
|
||||
|
||||
CreateApplicationCache();
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace Umbraco.Core.Logging
|
||||
/// <returns></returns>
|
||||
private static string PrefixThreadId(string generateMessageFormat)
|
||||
{
|
||||
return "[Thread " + Thread.CurrentThread.ManagedThreadId + "] " + generateMessageFormat;
|
||||
}
|
||||
return "[T" + Thread.CurrentThread.ManagedThreadId + "/D" + AppDomain.CurrentDomain.Id + "] " + generateMessageFormat;
|
||||
}
|
||||
|
||||
#region Error
|
||||
/// <summary>
|
||||
|
||||
@@ -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 | RegexOptions.Singleline);
|
||||
|
||||
/// <summary>
|
||||
/// This formats the persisted string to something useful for the rte so that the macro renders properly since we
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,29 @@ 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_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()
|
||||
{
|
||||
@@ -422,7 +613,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 +622,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 +631,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()
|
||||
{
|
||||
@@ -478,6 +669,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
|
||||
waitHandle.WaitOne();
|
||||
Assert.GreaterOrEqual(runCount, 4);
|
||||
Assert.IsTrue(task.HasRun);
|
||||
|
||||
// stops recurring
|
||||
runner.Shutdown(false, false);
|
||||
@@ -492,10 +684,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 +718,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 +747,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,13 +776,17 @@ 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>
|
||||
{
|
||||
public bool HasRun { get; private set; }
|
||||
|
||||
public MyDelayedRecurringTask(IBackgroundTaskRunner<MyDelayedRecurringTask> runner, int delayMilliseconds, int periodMilliseconds)
|
||||
: base(runner, delayMilliseconds, periodMilliseconds)
|
||||
{ }
|
||||
@@ -566,7 +802,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
|
||||
public override void PerformRun()
|
||||
{
|
||||
// nothing to do at the moment
|
||||
HasRun = true;
|
||||
}
|
||||
|
||||
public override Task PerformRunAsync()
|
||||
@@ -583,30 +819,28 @@ namespace Umbraco.Tests.Scheduling
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -180,7 +180,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 +412,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.
|
||||
|
||||
+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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -37,11 +37,6 @@
|
||||
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;
|
||||
@@ -153,27 +148,27 @@
|
||||
/* 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;}
|
||||
}
|
||||
|
||||
.umb-tab-buttons{padding-left: 240px;}
|
||||
.umb-tab-buttons{
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
@media (min-width: 1101px) {
|
||||
.umb-bottom-bar {
|
||||
left: 460px;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-tab-pane{padding-bottom: 90px}
|
||||
|
||||
.tab-content{overflow: visible; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
+18
-3
@@ -18,9 +18,16 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
|
||||
//map the user config
|
||||
$scope.model.config = angular.extend(config, $scope.model.config);
|
||||
|
||||
$scope.datetimePickerValue = $scope.model.value;
|
||||
|
||||
//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);
|
||||
|
||||
@@ -67,8 +74,16 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
|
||||
.datetimepicker($scope.model.config)
|
||||
.on("dp.change", applyDate);
|
||||
|
||||
//manually assign the date to the plugin
|
||||
$element.find("div:first").datetimepicker("setValue", $scope.model.value ? $scope.model.value : null);
|
||||
//manually assign the date to the plugin
|
||||
if (!$scope.model.config.format) {
|
||||
$element.find("div:first").datetimepicker("setValue", $scope.model.value ? $scope.model.value : null);
|
||||
}
|
||||
else {
|
||||
$element.find("div:first").datetimepicker("setValue", $scope.model.value ? new Date($scope.model.value) : null);
|
||||
if ($scope.model.value && $scope.model.config.format) {
|
||||
$scope.datetimePickerValue = moment($scope.model.value).format($scope.model.config.format);
|
||||
}
|
||||
}
|
||||
|
||||
//Ensure to remove the event handler when this instance is destroyted
|
||||
$scope.$on('$destroy', function () {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<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-model="datetimePickerValue"
|
||||
ng-required="model.validation.mandatory"
|
||||
val-server="value" />
|
||||
<span class="add-on">
|
||||
|
||||
@@ -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' />");
|
||||
|
||||
@@ -2540,9 +2540,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\"
|
||||
<WebProjectProperties>
|
||||
<UseIIS>True</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>7250</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>7260</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:7250</IISUrl>
|
||||
<IISUrl>http://localhost:7260</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>False</UseCustomServer>
|
||||
<CustomServerUrl>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,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 +130,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
|
||||
}
|
||||
|
||||
@@ -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,9 +76,19 @@ 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);
|
||||
|
||||
@@ -84,7 +121,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 +129,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 +192,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 +213,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 +254,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 +280,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 +295,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 +314,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 +354,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 +364,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
|
||||
|
||||
@@ -343,7 +410,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 +420,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();
|
||||
@@ -394,85 +461,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 +519,7 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
~BackgroundTaskRunner()
|
||||
{
|
||||
this.Dispose(false);
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -493,7 +530,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 +562,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 +606,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
public bool IsLatched
|
||||
{
|
||||
get { return _latch != null; }
|
||||
get { return _latch != null && _latch.IsSet == false; }
|
||||
}
|
||||
|
||||
public virtual bool RunsOnShutdown
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -48,9 +48,9 @@ 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>();
|
||||
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPublishing");
|
||||
_tasksRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledTasks");
|
||||
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>("LogScrubber");
|
||||
|
||||
var settings = UmbracoConfig.For.UmbracoSettings();
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// This is used to return an awaitable instance from a Task without actually returning the
|
||||
/// underlying Task instance since it shouldn't be mutable.
|
||||
/// </summary>
|
||||
internal class ThreadingTaskAwaiter
|
||||
{
|
||||
private readonly Task _task;
|
||||
|
||||
public ThreadingTaskAwaiter(Task task)
|
||||
{
|
||||
if (task == null) throw new ArgumentNullException("task");
|
||||
_task = task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// With a GetAwaiter declared it means that this instance can be awaited on with the await keyword
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public TaskAwaiter GetAwaiter()
|
||||
{
|
||||
return _task.GetAwaiter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the running task.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">There is no running task.</exception>
|
||||
/// <remarks>Unless the AutoStart option is true, there will be no running task until
|
||||
/// 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 TaskStatus Status
|
||||
{
|
||||
get { return _task.Status; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a Task within an object that gives access to its GetAwaiter method and Status
|
||||
/// property while ensuring that it cannot be modified in any way.
|
||||
/// </summary>
|
||||
internal class ThreadingTaskImmutable
|
||||
{
|
||||
private readonly Task _task;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ThreadingTaskImmutable"/> class with a Task.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
public ThreadingTaskImmutable(Task task)
|
||||
{
|
||||
if (task == null)
|
||||
throw new ArgumentNullException("task");
|
||||
_task = task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaiter used to await the task.
|
||||
/// </summary>
|
||||
/// <returns>An awaiter instance.</returns>
|
||||
public TaskAwaiter GetAwaiter()
|
||||
{
|
||||
return _task.GetAwaiter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TaskStatus of the task.
|
||||
/// </summary>
|
||||
/// <returns>The current TaskStatus of the task.</returns>
|
||||
public TaskStatus Status
|
||||
{
|
||||
get { return _task.Status; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,7 +274,7 @@
|
||||
<Compile Include="Mvc\DisableClientCacheAttribute.cs" />
|
||||
<Compile Include="Mvc\MvcVersionCheck.cs" />
|
||||
<Compile Include="Mvc\ReflectedFixedRazorViewEngine.cs" />
|
||||
<Compile Include="Scheduling\ThreadingTaskAwaiter.cs" />
|
||||
<Compile Include="Scheduling\ThreadingTaskImmutable.cs" />
|
||||
<Compile Include="Scheduling\BackgroundTaskRunner.cs" />
|
||||
<Compile Include="BatchedServerMessenger.cs" />
|
||||
<Compile Include="CacheHelperExtensions.cs" />
|
||||
@@ -499,7 +499,6 @@
|
||||
<Compile Include="Mvc\UmbracoVirtualNodeRouteHandler.cs" />
|
||||
<Compile Include="Routing\CustomRouteUrlProvider.cs" />
|
||||
<Compile Include="Routing\UrlProviderExtensions.cs" />
|
||||
<Compile Include="Scheduling\BackgroundTaskRunnerAwaiter.cs" />
|
||||
<Compile Include="Scheduling\BackgroundTaskRunnerOptions.cs" />
|
||||
<Compile Include="Scheduling\DelayedRecurringTaskBase.cs" />
|
||||
<Compile Include="Scheduling\IBackgroundTaskRunner.cs" />
|
||||
|
||||
@@ -409,15 +409,23 @@ namespace Umbraco.Web
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool _notConfiguredReported;
|
||||
|
||||
// ensures Umbraco is configured
|
||||
// if not, redirect to install and return false
|
||||
// if yes, return true
|
||||
private static bool EnsureIsConfigured(HttpContextBase httpContext, Uri uri)
|
||||
private bool EnsureIsConfigured(HttpContextBase httpContext, Uri uri)
|
||||
{
|
||||
if (ApplicationContext.Current.IsConfigured)
|
||||
return true;
|
||||
|
||||
LogHelper.Warn<UmbracoModule>("Umbraco is not configured");
|
||||
if (_notConfiguredReported)
|
||||
{
|
||||
// remember it's been reported so we don't flood the log
|
||||
// no thread-safety so there may be a few log entries, doesn't matter
|
||||
_notConfiguredReported = true;
|
||||
LogHelper.Warn<UmbracoModule>("Umbraco is not configured");
|
||||
}
|
||||
|
||||
var installPath = UriUtility.ToAbsolute(SystemDirectories.Install);
|
||||
var installUrl = string.Format("{0}/?redir=true&url={1}", installPath, HttpUtility.UrlEncode(uri.ToString()));
|
||||
@@ -588,6 +596,19 @@ namespace Umbraco.Web
|
||||
var httpContext = ((HttpApplication)sender).Context;
|
||||
LogHelper.Debug<UmbracoModule>("Begin request: {0}.", () => httpContext.Request.Url);
|
||||
BeginRequest(new HttpContextWrapper(httpContext));
|
||||
|
||||
//disable asp.net headers (security)
|
||||
try
|
||||
{
|
||||
httpContext.Response.Headers.Remove("Server");
|
||||
//this doesn't normally work since IIS sets it but we'll keep it here anyways.
|
||||
httpContext.Response.Headers.Remove("X-Powered-By");
|
||||
}
|
||||
catch (PlatformNotSupportedException ex)
|
||||
{
|
||||
// can't remove headers this way on IIS6 or cassini.
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
app.AuthenticateRequest += AuthenticateRequest;
|
||||
@@ -612,21 +633,6 @@ namespace Umbraco.Web
|
||||
DisposeHttpContextItems(httpContext);
|
||||
};
|
||||
|
||||
//disable asp.net headers (security)
|
||||
app.PreSendRequestHeaders += (sender, args) =>
|
||||
{
|
||||
var httpContext = ((HttpApplication)sender).Context;
|
||||
try
|
||||
{
|
||||
httpContext.Response.Headers.Remove("Server");
|
||||
//this doesn't normally work since IIS sets it but we'll keep it here anyways.
|
||||
httpContext.Response.Headers.Remove("X-Powered-By");
|
||||
}
|
||||
catch (PlatformNotSupportedException ex)
|
||||
{
|
||||
// can't remove headers this way on IIS6 or cassini.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -40,26 +40,66 @@ namespace umbraco
|
||||
|
||||
private content()
|
||||
{
|
||||
if (SyncToXmlFile == false) return;
|
||||
|
||||
// there's always be one task keeping a ref to the runner
|
||||
// so it's safe to just create it as a local var here
|
||||
var runner = new BackgroundTaskRunner<XmlCacheFilePersister>(new BackgroundTaskRunnerOptions
|
||||
if (SyncToXmlFile)
|
||||
{
|
||||
LongRunning = true,
|
||||
KeepAlive = true
|
||||
});
|
||||
// if we write to file, prepare the lock
|
||||
// (if we don't use the file, or just read from it, no need to lock)
|
||||
InitializeFileLock();
|
||||
|
||||
// create (and add to runner)
|
||||
_persisterTask = new XmlCacheFilePersister(runner, this);
|
||||
// and prepare the persister task
|
||||
// there's always be one task keeping a ref to the runner
|
||||
// so it's safe to just create it as a local var here
|
||||
var runner = new BackgroundTaskRunner<XmlCacheFilePersister>("XmlCacheFilePersister", new BackgroundTaskRunnerOptions
|
||||
{
|
||||
LongRunning = true,
|
||||
KeepAlive = true
|
||||
});
|
||||
|
||||
InitializeFileLock();
|
||||
// when the runner is terminating we need to ensure that no modifications
|
||||
// to content are possible anymore, as they would not be written out to
|
||||
// the xml file - unfortunately that is not possible in 7.x because we
|
||||
// cannot lock the content service... and so we do nothing...
|
||||
//runner.Terminating += (sender, args) =>
|
||||
//{
|
||||
//};
|
||||
|
||||
// when the runner has terminated we know we will not be writing to the file
|
||||
// anymore, so we can release the lock now - no need to wait for the AppDomain
|
||||
// unload - which means any "last minute" saves will be lost - but waiting for
|
||||
// the AppDomain to unload has issues...
|
||||
runner.Terminated += (sender, args) =>
|
||||
{
|
||||
if (_fileLock == null) return; // not locking (testing?)
|
||||
if (_fileLocked == null) return; // not locked
|
||||
|
||||
// thread-safety
|
||||
// lock something that's readonly and not null..
|
||||
lock (_xmlFileName)
|
||||
{
|
||||
// double-check
|
||||
if (_fileLocked == null) return;
|
||||
|
||||
LogHelper.Debug<content>("Release file lock.");
|
||||
_fileLocked.Dispose();
|
||||
_fileLocked = null;
|
||||
_fileLock = null; // ensure we don't lock again
|
||||
}
|
||||
};
|
||||
|
||||
// create (and add to runner)
|
||||
_persisterTask = new XmlCacheFilePersister(runner, this);
|
||||
}
|
||||
|
||||
// initialize content - populate the cache
|
||||
using (var safeXml = GetSafeXmlWriter(false))
|
||||
{
|
||||
bool registerXmlChange;
|
||||
|
||||
// if we don't use the file then LoadXmlLocked will not even
|
||||
// read from the file and will go straight to database
|
||||
LoadXmlLocked(safeXml, out registerXmlChange);
|
||||
// if we use the file and registerXmlChange is true this will
|
||||
// write to file, else it will not
|
||||
safeXml.Commit(registerXmlChange);
|
||||
}
|
||||
}
|
||||
@@ -1007,8 +1047,8 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
private void OnDomainUnloadReleaseFileLock(object sender, EventArgs args)
|
||||
{
|
||||
// the unload event triggers AFTER all hosted objects (eg the file persister
|
||||
// background task runner) have been stopped, so we should NOT be accessing
|
||||
// the file from now one - release the lock
|
||||
// background task runner) have been stopped, so we should have released the
|
||||
// lock already - this is for safety - might be possible to get rid of it
|
||||
|
||||
// NOTE
|
||||
// trying to write to the log via LogHelper at that point is a BAD idea
|
||||
@@ -1056,12 +1096,14 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
internal void SaveXmlToFile()
|
||||
{
|
||||
LogHelper.Info<content>("Save Xml to file...");
|
||||
EnsureFileLock();
|
||||
|
||||
var xml = _xmlContent; // capture (atomic + volatile), immutable anyway
|
||||
|
||||
try
|
||||
{
|
||||
var xml = _xmlContent; // capture (atomic + volatile), immutable anyway
|
||||
if (xml == null) return;
|
||||
|
||||
EnsureFileLock();
|
||||
|
||||
// delete existing file, if any
|
||||
DeleteXmlFile();
|
||||
|
||||
@@ -1079,7 +1121,7 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
fs.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
LogHelper.Debug<content>("Saved Xml to file.");
|
||||
LogHelper.Info<content>("Saved Xml to file.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -1094,12 +1136,14 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
internal async System.Threading.Tasks.Task SaveXmlToFileAsync()
|
||||
{
|
||||
LogHelper.Info<content>("Save Xml to file...");
|
||||
EnsureFileLock();
|
||||
|
||||
var xml = _xmlContent; // capture (atomic + volatile), immutable anyway
|
||||
|
||||
try
|
||||
{
|
||||
var xml = _xmlContent; // capture (atomic + volatile), immutable anyway
|
||||
if (xml == null) return;
|
||||
|
||||
EnsureFileLock();
|
||||
|
||||
// delete existing file, if any
|
||||
DeleteXmlFile();
|
||||
|
||||
@@ -1117,7 +1161,7 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
await fs.WriteAsync(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
LogHelper.Debug<content>("Saved Xml to file.");
|
||||
LogHelper.Info<content>("Saved Xml to file.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -1160,19 +1204,27 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
private XmlDocument LoadXmlFromFile()
|
||||
{
|
||||
LogHelper.Info<content>("Load Xml from file...");
|
||||
EnsureFileLock();
|
||||
|
||||
try
|
||||
{
|
||||
// if we're not writing back to the file, no need to lock
|
||||
if (SyncToXmlFile)
|
||||
EnsureFileLock();
|
||||
|
||||
var xml = new XmlDocument();
|
||||
using (var fs = new FileStream(_xmlFileName, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
xml.Load(fs);
|
||||
}
|
||||
_lastFileRead = DateTime.UtcNow;
|
||||
LogHelper.Info<content>("Successfully loaded Xml from file.");
|
||||
LogHelper.Info<content>("Loaded Xml from file.");
|
||||
return xml;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
LogHelper.Warn<content>("Failed to load Xml, file does not exist.");
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<content>("Failed to load Xml from file.", e);
|
||||
|
||||
@@ -8,6 +8,7 @@ using umbraco.cms.businesslogic.language;
|
||||
using umbraco.cms.businesslogic.property;
|
||||
using umbraco.cms.businesslogic.task;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace umbraco.cms.businesslogic.translation
|
||||
@@ -95,10 +96,14 @@ namespace umbraco.cms.businesslogic.translation
|
||||
var props = d.GenericProperties;
|
||||
foreach (Property p in props)
|
||||
{
|
||||
if (p.Value.GetType() == "".GetType())
|
||||
var asString = p.Value as string;
|
||||
if (asString != null)
|
||||
{
|
||||
if (p.Value.ToString().Trim() != "")
|
||||
words += CountWordsInString(p.Value.ToString());
|
||||
var trimmed = asString.Trim();
|
||||
if (trimmed.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
words += CountWordsInString(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using Umbraco.Core.Cache;
|
||||
@@ -121,27 +122,29 @@ namespace umbraco.cms.businesslogic.web
|
||||
|
||||
using (var re = File.OpenText(IOHelper.MapPath(String.Format("{0}/{1}.css", SystemDirectories.Css, this.Text))))
|
||||
{
|
||||
string input = null;
|
||||
_content = string.Empty;
|
||||
// NH: Updates the reader to support properties
|
||||
var readingProperties = false;
|
||||
// stop concatenating strings, use string builders!
|
||||
var contentBuilder = new StringBuilder();
|
||||
var propertiesBuilder = new StringBuilder();
|
||||
|
||||
while ((input = re.ReadLine()) != null && true)
|
||||
string input;
|
||||
while ((input = re.ReadLine()) != null)
|
||||
{
|
||||
if (input.Contains("EDITOR PROPERTIES"))
|
||||
{
|
||||
readingProperties = true;
|
||||
}
|
||||
else
|
||||
if (readingProperties)
|
||||
{
|
||||
propertiesContent += input.Replace("\n", "") + "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
_content += input.Replace("\n", "") + "\n";
|
||||
}
|
||||
{
|
||||
var builder = readingProperties ? propertiesBuilder : contentBuilder;
|
||||
builder.Append(input.Replace("\n", ""));
|
||||
builder.Append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
_content = contentBuilder.ToString();
|
||||
propertiesContent = propertiesBuilder.ToString();
|
||||
}
|
||||
|
||||
// update properties
|
||||
|
||||
Reference in New Issue
Block a user