2017-07-20 11:21:28 +02:00
|
|
|
|
using System;
|
2017-05-12 14:49:44 +02:00
|
|
|
|
using System.Xml;
|
2021-02-09 10:22:42 +01:00
|
|
|
|
using Umbraco.Cms.Core;
|
2021-02-15 11:41:12 +01:00
|
|
|
|
using Umbraco.Cms.Core.Scoping;
|
2017-05-12 14:49:44 +02:00
|
|
|
|
|
2019-01-30 17:50:13 +11:00
|
|
|
|
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
2017-05-12 14:49:44 +02:00
|
|
|
|
{
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: should be a ScopeContextualBase
|
2017-05-12 14:49:44 +02:00
|
|
|
|
internal class SafeXmlReaderWriter : IDisposable
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly bool _scoped;
|
|
|
|
|
|
private readonly Action<XmlDocument> _refresh;
|
|
|
|
|
|
private readonly Action<XmlDocument, bool> _apply;
|
|
|
|
|
|
private IDisposable _releaser;
|
|
|
|
|
|
private bool _applyChanges;
|
|
|
|
|
|
private XmlDocument _xml, _origXml;
|
|
|
|
|
|
private bool _using;
|
|
|
|
|
|
private bool _registerXmlChange;
|
|
|
|
|
|
|
2018-03-27 10:51:41 +02:00
|
|
|
|
// the default enlist priority is 100
|
|
|
|
|
|
// enlist with a lower priority to ensure that anything "default" has a clean xml
|
|
|
|
|
|
private const int EnlistPriority = 60;
|
|
|
|
|
|
private const string EnlistKey = "safeXmlReaderWriter";
|
|
|
|
|
|
|
2017-05-12 14:49:44 +02:00
|
|
|
|
private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action<XmlDocument> refresh, Action<XmlDocument, bool> apply, bool isWriter, bool scoped)
|
|
|
|
|
|
{
|
|
|
|
|
|
_releaser = releaser;
|
|
|
|
|
|
_refresh = refresh;
|
|
|
|
|
|
_apply = apply;
|
|
|
|
|
|
_scoped = scoped;
|
|
|
|
|
|
|
|
|
|
|
|
IsWriter = isWriter;
|
|
|
|
|
|
|
|
|
|
|
|
_xml = IsWriter ? Clone(xml) : xml;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-03-27 10:51:41 +02:00
|
|
|
|
public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider)
|
|
|
|
|
|
{
|
2018-03-28 11:28:32 +02:00
|
|
|
|
return scopeProvider?.Context?.GetEnlisted<SafeXmlReaderWriter>(EnlistKey);
|
2018-03-27 10:51:41 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-10-18 10:19:11 +11:00
|
|
|
|
public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, SystemLock xmlLock, XmlDocument xml, Action<XmlDocument> refresh, Action<XmlDocument, bool> apply, bool writer)
|
2017-05-12 14:49:44 +02:00
|
|
|
|
{
|
|
|
|
|
|
var scopeContext = scopeProvider.Context;
|
|
|
|
|
|
|
|
|
|
|
|
// no real scope = just create a reader/writer instance
|
|
|
|
|
|
if (scopeContext == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// obtain exclusive access to xml and create reader/writer
|
|
|
|
|
|
var releaser = xmlLock.Lock();
|
|
|
|
|
|
return new SafeXmlReaderWriter(releaser, xml, refresh, apply, writer, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// get or create an enlisted reader/writer
|
2018-03-27 10:51:41 +02:00
|
|
|
|
var rw = scopeContext.Enlist(EnlistKey,
|
2017-05-12 14:49:44 +02:00
|
|
|
|
() => // creator
|
|
|
|
|
|
{
|
|
|
|
|
|
// obtain exclusive access to xml and create reader/writer
|
|
|
|
|
|
var releaser = xmlLock.Lock();
|
|
|
|
|
|
return new SafeXmlReaderWriter(releaser, xml, refresh, apply, writer, true);
|
|
|
|
|
|
},
|
|
|
|
|
|
(completed, item) => // action
|
|
|
|
|
|
{
|
|
|
|
|
|
item.DisposeForReal(completed);
|
2018-03-27 10:51:41 +02:00
|
|
|
|
}, EnlistPriority);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
|
|
|
|
|
|
// ensure it's not already in-use - should never happen, just being super safe
|
|
|
|
|
|
if (rw._using)
|
|
|
|
|
|
throw new InvalidOperationException("panic: used.");
|
|
|
|
|
|
rw._using = true;
|
|
|
|
|
|
|
|
|
|
|
|
return rw;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsWriter { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
public void UpgradeToWriter(bool auto)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsWriter)
|
|
|
|
|
|
throw new InvalidOperationException("Already a writer.");
|
|
|
|
|
|
IsWriter = true;
|
|
|
|
|
|
|
|
|
|
|
|
_xml = Clone(_xml);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// for tests
|
|
|
|
|
|
internal static Action Cloning { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
private XmlDocument Clone(XmlDocument xml)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cloning?.Invoke();
|
|
|
|
|
|
if (_origXml != null)
|
|
|
|
|
|
throw new Exception("panic.");
|
|
|
|
|
|
_origXml = xml;
|
|
|
|
|
|
return (XmlDocument) xml?.CloneNode(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public XmlDocument Xml
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _xml;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsWriter == false)
|
|
|
|
|
|
throw new InvalidOperationException("Not a writer.");
|
|
|
|
|
|
_xml = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// registerXmlChange indicates whether to do what should be done when Xml changes,
|
|
|
|
|
|
// that is, to request that the file be written to disk - something we don't want
|
|
|
|
|
|
// to do if we're committing Xml precisely after we've read from disk!
|
|
|
|
|
|
public void AcceptChanges(bool registerXmlChange = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsWriter == false)
|
|
|
|
|
|
throw new InvalidOperationException("Not a writer.");
|
|
|
|
|
|
|
|
|
|
|
|
_applyChanges = true;
|
|
|
|
|
|
_registerXmlChange |= registerXmlChange;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void DisposeForReal(bool completed)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsWriter)
|
|
|
|
|
|
{
|
|
|
|
|
|
// apply changes, or restore the original xml for the current request
|
|
|
|
|
|
if (_applyChanges && completed)
|
|
|
|
|
|
_apply(_xml, _registerXmlChange);
|
|
|
|
|
|
else
|
|
|
|
|
|
_refresh(_origXml);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// release the lock
|
|
|
|
|
|
_releaser.Dispose();
|
|
|
|
|
|
_releaser = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
_using = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (_scoped == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
// really dispose
|
|
|
|
|
|
DisposeForReal(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// don't really dispose,
|
|
|
|
|
|
// just apply the changes for the current request
|
|
|
|
|
|
_refresh(_xml);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-07-20 11:21:28 +02:00
|
|
|
|
}
|