Fixing tests - all green or ignored
This commit is contained in:
@@ -18,31 +18,31 @@
|
||||
<dependency id="Microsoft.AspNet.Mvc" version="[5.2.3,6.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.WebApi" version="[5.2.3,6.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.Identity.Owin" version="[2.2.1, 3.0.0)" />
|
||||
<dependency id="Microsoft.Owin.Security.Cookies" version="[3.0.1, 4.0.0)" />
|
||||
<dependency id="Microsoft.Owin.Security.OAuth" version="[3.0.1, 4.0.0)" />
|
||||
<dependency id="Microsoft.Owin.Host.SystemWeb" version="[3.0.1, 4.0.0)" />
|
||||
<dependency id="Microsoft.Owin.Security.Cookies" version="[3.1.0, 4.0.0)" />
|
||||
<dependency id="Microsoft.Owin.Security.OAuth" version="[3.1.0, 4.0.0)" />
|
||||
<dependency id="Microsoft.Owin.Host.SystemWeb" version="[3.1.0, 4.0.0)" />
|
||||
<dependency id="MiniProfiler" version="[3.2.0.157, 4.0.0)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.4.9, 2.0.0)" />
|
||||
<dependency id="Lucene.Net" version="[2.9.4.1, 4.0.0.0)" />
|
||||
<dependency id="Lucene.Net" version="[3.0.3, 4.0.0.0)" />
|
||||
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
|
||||
<dependency id="MySql.Data" version="[6.9.8, 7.0.0)" />
|
||||
<dependency id="MySql.Data" version="[6.9.9, 7.0.0)" />
|
||||
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0, 2.0.0)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0.0, 2.0.0)" />
|
||||
<dependency id="AutoMapper" version="[4.2.1, 5.0.0)" />
|
||||
<dependency id="LightInject" version="[4.0.8, 5.0.0)" />
|
||||
<dependency id="LightInject.Mvc" version="[1.0.0.4, 2.0.0)" />
|
||||
<dependency id="LightInject.Web" version="[1.0.0.7, 2.0.0)" />
|
||||
<dependency id="LightInject.WebApi" version="[1.0.0.4, 2.0.0)" />
|
||||
<dependency id="Newtonsoft.Json" version="[8.0.3, 10.0.0)" />
|
||||
<dependency id="Examine" version="[0.1.70, 3.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.4.5, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.6.6, 5.0.0)" />
|
||||
<dependency id="Newtonsoft.Json" version="[10.0.2, 10.0.0)" />
|
||||
<dependency id="Examine" version="[2.0.0-beta2, 3.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.5.4, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.8.3, 5.0.0)" />
|
||||
<dependency id="semver" version="[2.0.4, 3.0.0)" />
|
||||
<dependency id="UrlRewritingNet" version="[2.0.7, 3.0.0)" />
|
||||
<!-- Markdown can not be updated due to: https://github.com/hey-red/markdownsharp/issues/71#issuecomment-233585487 -->
|
||||
<dependency id="Markdown" version="[1.14.7, 2.0.0)" />
|
||||
<dependency id="log4net" version="[2.0.5, 3.0.0)" />
|
||||
<dependency id="log4net" version="[2.0.8, 3.0.0)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.6.0, 5.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<dependencies>
|
||||
<dependency id="UmbracoCms.Core" version="[$version$]" />
|
||||
<dependency id="Newtonsoft.Json" version="[10.0.2, 11.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[8.0.0, 9.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[8.0.0-alpha0005, 9.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.1, 3.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
|
||||
@@ -364,7 +364,8 @@ namespace Umbraco.Core.Composing
|
||||
return cache;
|
||||
}
|
||||
|
||||
private string GeTypesListFilePath()
|
||||
// internal for tests
|
||||
internal string GeTypesListFilePath()
|
||||
{
|
||||
var filename = "umbraco-types." + NetworkHelper.FileSafeMachineName + ".list";
|
||||
return Path.Combine(_tempFolder, filename);
|
||||
|
||||
@@ -785,7 +785,8 @@ WHERE (@path LIKE {5})",
|
||||
return base.GetDatabaseFieldNameForOrderBy(orderBy);
|
||||
}
|
||||
|
||||
// fixme - missing the 'include all versions' thing here - see 7.6 ProcessQuery
|
||||
// "many" corresponds to 7.6 "includeAllVersions"
|
||||
// fixme - we are not implementing the double-query thing for pagination from 7.6?
|
||||
//
|
||||
private IEnumerable<IContent> MapQueryDtos(List<DocumentDto> dtos, bool withCache = false, bool many = false)
|
||||
{
|
||||
@@ -794,16 +795,32 @@ WHERE (@path LIKE {5})",
|
||||
var contentTypes = new Dictionary<int, IContentType>();
|
||||
var templateIds = new List<int>();
|
||||
|
||||
// in case of data corruption we may have more than 1 "newest" - cleanup
|
||||
var ix = new Dictionary<int, DocumentDto>();
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
if (ix.TryGetValue(dto.NodeId, out DocumentDto ixDto) == false || ixDto.UpdateDate < dto.UpdateDate)
|
||||
ix[dto.NodeId] = dto;
|
||||
}
|
||||
dtos = ix.Values.ToList();
|
||||
|
||||
// populate published data
|
||||
if (many)
|
||||
{
|
||||
var publishedDtoIndex = Database.FetchByGroups<DocumentPublishedReadOnlyDto, int>(dtos.Select(x => x.NodeId), 2000, batch
|
||||
var roDtos = Database.FetchByGroups<DocumentPublishedReadOnlyDto, int>(dtos.Select(x => x.NodeId), 2000, batch
|
||||
=> Sql()
|
||||
.Select<DocumentPublishedReadOnlyDto>()
|
||||
.From<DocumentDto>()
|
||||
.WhereIn<DocumentDto>(x => x.NodeId, batch)
|
||||
.Where<DocumentDto>(x => x.Published))
|
||||
.ToDictionary(x => x.NodeId, x => x);
|
||||
.Where<DocumentDto>(x => x.Published));
|
||||
|
||||
// in case of data corruption we may have more than 1 "published" - cleanup
|
||||
var publishedDtoIndex = new Dictionary<int, DocumentPublishedReadOnlyDto>();
|
||||
foreach (var roDto in roDtos)
|
||||
{
|
||||
if (publishedDtoIndex.TryGetValue(roDto.NodeId, out DocumentPublishedReadOnlyDto ixDto) == false || ixDto.VersionDate < roDto.VersionDate)
|
||||
publishedDtoIndex[roDto.NodeId] = roDto;
|
||||
}
|
||||
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
|
||||
@@ -1079,103 +1079,92 @@ AND umbracoNode.id <> @id",
|
||||
// query below is not safe + pointless if array is empty
|
||||
if (contentTypeIds.Length == 0) return;
|
||||
|
||||
var sql = @"SELECT
|
||||
pt.contentTypeId as contentTypeId, pt.uniqueID as ptUniqueID, pt.id as ptId,
|
||||
pt.Alias as ptAlias, pt." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, pt.mandatory as ptMandatory,
|
||||
pt.Name as ptName, pt.sortOrder as ptSortOrder, pt.validationRegExp as ptRegExp,
|
||||
var sqlGroups = @"SELECT
|
||||
pg.contenttypeNodeId AS contentTypeId,
|
||||
pg.id AS id, pg.uniqueID AS " + sqlSyntax.GetQuotedColumnName("key") + @",
|
||||
pg.sortOrder AS sortOrder, pg." + sqlSyntax.GetQuotedColumnName("text") + @" AS text
|
||||
FROM cmsPropertyTypeGroup pg
|
||||
WHERE pg.contenttypeNodeId IN (@ids)
|
||||
ORDER BY contentTypeId, id";
|
||||
|
||||
dt.nodeId as dtId, dt.dbType as dtDbType, dt.propertyEditorAlias as dtPropEdAlias,
|
||||
|
||||
pg.id as pgId, pg.uniqueID as pgKey, pg.sortorder as pgSortOrder, pg." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText
|
||||
|
||||
FROM cmsPropertyType as pt
|
||||
var sqlProps = @"SELECT
|
||||
pt.contentTypeId AS contentTypeId,
|
||||
pt.id AS id, pt.uniqueID AS " + sqlSyntax.GetQuotedColumnName("key") + @",
|
||||
pt.propertyTypeGroupId AS groupId,
|
||||
pt.Alias AS alias, pt." + sqlSyntax.GetQuotedColumnName("Description") + @" AS " + sqlSyntax.GetQuotedColumnName("desc") + @", pt.mandatory AS mandatory,
|
||||
pt.Name AS name, pt.sortOrder AS sortOrder, pt.validationRegExp AS regexp,
|
||||
dt.nodeId as dataTypeId, dt.dbType as dbType, dt.propertyEditorAlias as editorAlias
|
||||
FROM cmsPropertyType pt
|
||||
INNER JOIN cmsDataType as dt ON pt.dataTypeId = dt.nodeId
|
||||
LEFT JOIN cmsPropertyTypeGroup as pg ON pg.id = pt.propertyTypeGroupId
|
||||
WHERE pt.contentTypeId IN (@ids)
|
||||
ORDER BY contentTypeId, groupId, id";
|
||||
|
||||
WHERE pt.contentTypeId IN (@contentTypeIds)
|
||||
|
||||
ORDER BY pgId
|
||||
";
|
||||
|
||||
//NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count!
|
||||
// Since there are 2 groups of params, it will be half!
|
||||
if (((contentTypeIds.Length / 2) - 1) > 2000)
|
||||
if (contentTypeIds.Length > 2000)
|
||||
throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters");
|
||||
|
||||
var result = db.Fetch<dynamic>(sql, new { contentTypeIds = contentTypeIds });
|
||||
var groups = db.Fetch<dynamic>(sqlGroups, new { ids = contentTypeIds });
|
||||
var groupsEnumerator = groups.GetEnumerator();
|
||||
var group = groupsEnumerator.MoveNext() ? groupsEnumerator.Current : null;
|
||||
|
||||
var props = db.Fetch<dynamic>(sqlProps, new { ids = contentTypeIds });
|
||||
var propsEnumerator = props.GetEnumerator();
|
||||
var prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null;
|
||||
|
||||
// groups are ordered by content type, group id
|
||||
// props are ordered by content type, group id, prop id
|
||||
|
||||
foreach (var contentTypeId in contentTypeIds)
|
||||
{
|
||||
//from this we need to make :
|
||||
// * PropertyGroupCollection - Contains all property groups along with all property types associated with a group
|
||||
// * PropertyTypeCollection - Contains all property types that do not belong to a group
|
||||
var propertyTypeCollection = allPropertyTypeCollection[contentTypeId] = new PropertyTypeCollection();
|
||||
var propertyGroupCollection = allPropertyGroupCollection[contentTypeId] = new PropertyGroupCollection();
|
||||
|
||||
//create the property group collection first, this means all groups (even empty ones) and all groups with properties
|
||||
while (prop != null && prop.contentTypeId == contentTypeId && prop.groupId == null)
|
||||
{
|
||||
AddPropertyType(propertyTypeCollection, prop);
|
||||
prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null;
|
||||
}
|
||||
|
||||
int currId = contentTypeId;
|
||||
|
||||
var propertyGroupCollection = new PropertyGroupCollection(result
|
||||
//get all rows that have a group id
|
||||
.Where(x => x.pgId != null)
|
||||
//filter based on the current content type
|
||||
.Where(x => x.contentTypeId == currId)
|
||||
//turn that into a custom object containing only the group info
|
||||
.Select(x => new { GroupId = x.pgId, SortOrder = x.pgSortOrder, Text = x.pgText, Key = x.pgKey })
|
||||
//get distinct data by id
|
||||
.DistinctBy(x => (int)x.GroupId)
|
||||
//for each of these groups, create a group object with it's associated properties
|
||||
.Select(group => new PropertyGroup(new PropertyTypeCollection(
|
||||
result
|
||||
.Where(row => row.pgId == group.GroupId && row.ptId != null)
|
||||
.Select(row => new PropertyType(row.dtPropEdAlias, Enum<DataTypeDatabaseType>.Parse(row.dtDbType), row.ptAlias)
|
||||
{
|
||||
//fill in the rest of the property type properties
|
||||
Description = row.ptDesc,
|
||||
DataTypeDefinitionId = row.dtId,
|
||||
Id = row.ptId,
|
||||
Key = row.ptUniqueID,
|
||||
Mandatory = Convert.ToBoolean(row.ptMandatory),
|
||||
Name = row.ptName,
|
||||
PropertyGroupId = new Lazy<int>(() => group.GroupId, false),
|
||||
SortOrder = row.ptSortOrder,
|
||||
ValidationRegExp = row.ptRegExp
|
||||
})))
|
||||
while (group != null && group.contentTypeId == contentTypeId)
|
||||
{
|
||||
var propertyGroup = new PropertyGroup(new PropertyTypeCollection())
|
||||
{
|
||||
//fill in the rest of the group properties
|
||||
Id = group.GroupId,
|
||||
Name = group.Text,
|
||||
SortOrder = group.SortOrder,
|
||||
Key = group.Key
|
||||
}).ToArray());
|
||||
Id = group.id,
|
||||
Name = group.text,
|
||||
SortOrder = group.sortOrder,
|
||||
Key = group.key
|
||||
};
|
||||
propertyGroupCollection.Add(propertyGroup);
|
||||
|
||||
allPropertyGroupCollection[currId] = propertyGroupCollection;
|
||||
|
||||
//Create the property type collection now (that don't have groups)
|
||||
|
||||
var propertyTypeCollection = new PropertyTypeCollection(result
|
||||
.Where(x => x.pgId == null)
|
||||
//filter based on the current content type
|
||||
.Where(x => x.contentTypeId == currId)
|
||||
.Select(row => new PropertyType(row.dtPropEdAlias, Enum<DataTypeDatabaseType>.Parse(row.dtDbType), row.ptAlias)
|
||||
while (prop != null && prop.groupId == group.id)
|
||||
{
|
||||
//fill in the rest of the property type properties
|
||||
Description = row.ptDesc,
|
||||
DataTypeDefinitionId = row.dtId,
|
||||
Id = row.ptId,
|
||||
Key = row.ptUniqueID,
|
||||
Mandatory = Convert.ToBoolean(row.ptMandatory),
|
||||
Name = row.ptName,
|
||||
PropertyGroupId = null,
|
||||
SortOrder = row.ptSortOrder,
|
||||
ValidationRegExp = row.ptRegExp
|
||||
}).ToArray());
|
||||
AddPropertyType(propertyGroup.PropertyTypes, prop, propertyGroup);
|
||||
prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null;
|
||||
}
|
||||
|
||||
allPropertyTypeCollection[currId] = propertyTypeCollection;
|
||||
group = groupsEnumerator.MoveNext() ? groupsEnumerator.Current : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
propsEnumerator.Dispose();
|
||||
groupsEnumerator.Dispose();
|
||||
}
|
||||
|
||||
private static void AddPropertyType(PropertyTypeCollection propertyTypes, dynamic prop, PropertyGroup propertyGroup = null)
|
||||
{
|
||||
var propertyType = new PropertyType(prop.editorAlias, Enum<DataTypeDatabaseType>.Parse(prop.dbType), prop.alias)
|
||||
{
|
||||
Description = prop.desc,
|
||||
DataTypeDefinitionId = prop.dataTypeId,
|
||||
Id = prop.id,
|
||||
Key = prop.key,
|
||||
Mandatory = Convert.ToBoolean(prop.mandatory),
|
||||
Name = prop.name,
|
||||
PropertyGroupId = propertyGroup == null ? null : new Lazy<int>(() => propertyGroup.Id),
|
||||
SortOrder = prop.sortOrder,
|
||||
ValidationRegExp = prop.regexp
|
||||
};
|
||||
propertyTypes.Add(propertyType);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract TEntity PerformGet(Guid id);
|
||||
|
||||
@@ -14,14 +14,13 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
// creates a unit of work
|
||||
// redefine the method to indicate it returns an IScopeUnitOfWork and
|
||||
// not anymore only an IDatabaseUnitOfWork as IDatabaseUnitOfWorkProvider does
|
||||
// fixme - merge IScope... and IDatabase...
|
||||
new IScopeUnitOfWork CreateUnitOfWork();
|
||||
|
||||
// creates a unit of work
|
||||
// support specifying an isolation level
|
||||
// support auto-commit - but beware! it will be committed, whatever happens
|
||||
// fixme in v8 this should all be merged as one single method with optional args
|
||||
IScopeUnitOfWork CreateUnitOfWork(bool readOnly);
|
||||
IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel, bool readOnly = false);
|
||||
IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel = IsolationLevel.Unspecified, bool readOnly = false, bool immediate = false);
|
||||
|
||||
// fixme explain
|
||||
IDatabaseContext DatabaseContext { get; }
|
||||
|
||||
@@ -31,11 +31,12 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
/// <param name="repositoryFactory"></param>
|
||||
/// <param name="isolationLevel"></param>
|
||||
/// <param name="readOnly"></param>
|
||||
/// <param name="immediate"></param>
|
||||
/// <remarks>
|
||||
/// This should normally not be used directly and should be created with the UnitOfWorkProvider
|
||||
/// </remarks>
|
||||
internal ScopeUnitOfWork(IScopeProvider scopeProvider, RepositoryFactory repositoryFactory, IsolationLevel isolationLevel = IsolationLevel.Unspecified, bool readOnly = false)
|
||||
: base(repositoryFactory, readOnly)
|
||||
internal ScopeUnitOfWork(IScopeProvider scopeProvider, RepositoryFactory repositoryFactory, IsolationLevel isolationLevel = IsolationLevel.Unspecified, bool readOnly = false, bool immediate = false)
|
||||
: base(repositoryFactory, readOnly, immediate)
|
||||
{
|
||||
_scopeProvider = scopeProvider;
|
||||
_isolationLevel = isolationLevel;
|
||||
|
||||
@@ -37,21 +37,9 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel)
|
||||
public IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel = IsolationLevel.Unspecified, bool readOnly = false, bool immediate = false)
|
||||
{
|
||||
return new ScopeUnitOfWork(ScopeProvider, _repositoryFactory, isolationLevel);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IScopeUnitOfWork CreateUnitOfWork(bool readOnly)
|
||||
{
|
||||
return new ScopeUnitOfWork(ScopeProvider, _repositoryFactory, readOnly: readOnly);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel, bool readOnly)
|
||||
{
|
||||
return new ScopeUnitOfWork(ScopeProvider, _repositoryFactory, isolationLevel, readOnly: readOnly);
|
||||
return new ScopeUnitOfWork(ScopeProvider, _repositoryFactory, isolationLevel, readOnly, immediate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,20 +7,28 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
{
|
||||
public abstract class UnitOfWorkBase : DisposableObject, IUnitOfWork
|
||||
{
|
||||
private readonly Queue<Operation> _operations = new Queue<Operation>();
|
||||
private Queue<Operation> _operations;
|
||||
|
||||
// fixme - explain readonly
|
||||
// it means that the unit of work *will* complete no matter what
|
||||
// but if an exception is thrown from within the 'using' block
|
||||
// the calling code still can deal with it and cancel everything
|
||||
//
|
||||
// fixme - explain immediate
|
||||
// do not queue things - save allocations + in some complex operations
|
||||
// that are transactional queueing makes no sense (or we keep flushing)
|
||||
|
||||
protected UnitOfWorkBase(RepositoryFactory repositoryFactory, bool readOnly = false)
|
||||
protected UnitOfWorkBase(RepositoryFactory repositoryFactory, bool readOnly = false, bool immediate = false)
|
||||
{
|
||||
RepositoryFactory = repositoryFactory;
|
||||
ReadOnly = readOnly;
|
||||
Immediate = immediate;
|
||||
}
|
||||
|
||||
private Queue<Operation> Operations => _operations ?? (_operations = new Queue<Operation>());
|
||||
|
||||
protected bool ReadOnly { get; }
|
||||
protected bool Immediate { get; }
|
||||
|
||||
protected RepositoryFactory RepositoryFactory { get; }
|
||||
|
||||
@@ -39,12 +47,15 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
|
||||
Completed = false;
|
||||
|
||||
_operations.Enqueue(new Operation
|
||||
{
|
||||
Entity = entity,
|
||||
Repository = repository,
|
||||
Type = OperationType.Insert
|
||||
});
|
||||
if (Immediate)
|
||||
repository.PersistNewItem(entity);
|
||||
else
|
||||
Operations.Enqueue(new Operation
|
||||
{
|
||||
Entity = entity,
|
||||
Repository = repository,
|
||||
Type = OperationType.Insert
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -59,12 +70,15 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
|
||||
Completed = false;
|
||||
|
||||
_operations.Enqueue(new Operation
|
||||
{
|
||||
Entity = entity,
|
||||
Repository = repository,
|
||||
Type = OperationType.Update
|
||||
});
|
||||
if (Immediate)
|
||||
repository.PersistUpdatedItem(entity);
|
||||
else
|
||||
Operations.Enqueue(new Operation
|
||||
{
|
||||
Entity = entity,
|
||||
Repository = repository,
|
||||
Type = OperationType.Update
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -79,12 +93,15 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
|
||||
Completed = false;
|
||||
|
||||
_operations.Enqueue(new Operation
|
||||
{
|
||||
Entity = entity,
|
||||
Repository = repository,
|
||||
Type = OperationType.Delete
|
||||
});
|
||||
if (Immediate)
|
||||
repository.PersistDeletedItem(entity);
|
||||
else
|
||||
Operations.Enqueue(new Operation
|
||||
{
|
||||
Entity = entity,
|
||||
Repository = repository,
|
||||
Type = OperationType.Delete
|
||||
});
|
||||
}
|
||||
|
||||
// fixme - we don't need Begin, really, or do we?
|
||||
@@ -98,6 +115,9 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
|
||||
Begin();
|
||||
|
||||
if (_operations == null)
|
||||
return;
|
||||
|
||||
while (_operations.Count > 0)
|
||||
{
|
||||
var operation = _operations.Dequeue();
|
||||
@@ -137,7 +157,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
{
|
||||
// whatever hasn't been commited is lost
|
||||
// not sure we need this as we are being disposed...
|
||||
_operations.Clear();
|
||||
_operations?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -367,7 +367,7 @@ namespace Umbraco.Core.Scoping
|
||||
{
|
||||
// figure out completed
|
||||
var completed = _completed.HasValue && _completed.Value;
|
||||
|
||||
|
||||
// deal with database
|
||||
var databaseException = false;
|
||||
if (_database != null)
|
||||
|
||||
@@ -1536,19 +1536,20 @@ namespace Umbraco.Core.Services
|
||||
|
||||
moves.Add(Tuple.Create(content, content.Path)); // capture original path
|
||||
|
||||
// get before moving, in case uow is immediate
|
||||
var descendants = GetDescendants(content);
|
||||
|
||||
// these will be updated by the repo because we changed parentId
|
||||
//content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id;
|
||||
//content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId);
|
||||
//content.Level += levelDelta;
|
||||
PerformMoveContentLocked(repository, content, userId, trash);
|
||||
|
||||
// BUT content.Path will be updated only when the UOW commits, and
|
||||
// because we want it now, we have to calculate it by ourselves
|
||||
// if uow is not immediate, content.Path will be updated only when the UOW commits,
|
||||
// and because we want it now, we have to calculate it by ourselves
|
||||
//paths[content.Id] = content.Path;
|
||||
paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : "-1") : parent.Path) + "," + content.Id;
|
||||
Console.WriteLine("path " + content.Id + " = " + paths[content.Id]);
|
||||
|
||||
var descendants = GetDescendants(content);
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path
|
||||
@@ -2505,7 +2506,11 @@ namespace Umbraco.Core.Services
|
||||
var moves = new List<Tuple<IContent, string>>();
|
||||
var contentTypeIdsA = contentTypeIds.ToArray();
|
||||
|
||||
using (var uow = UowProvider.CreateUnitOfWork())
|
||||
// using an immediate uow here because we keep making changes with
|
||||
// PerformMoveLocked and DeleteLocked that must be applied immediately,
|
||||
// no point queuing operations
|
||||
//
|
||||
using (var uow = UowProvider.CreateUnitOfWork(immediate: true))
|
||||
{
|
||||
uow.WriteLock(Constants.Locks.ContentTree);
|
||||
var repository = uow.CreateRepository<IContentRepository>();
|
||||
@@ -2531,9 +2536,9 @@ namespace Umbraco.Core.Services
|
||||
|
||||
// if current content has children, move them to trash
|
||||
var c = content;
|
||||
var childQuery = repository.QueryT.Where(x => x.Path.StartsWith(c.Path));
|
||||
var childQuery = repository.QueryT.Where(x => x.ParentId == c.Id);
|
||||
var children = repository.GetByQuery(childQuery);
|
||||
foreach (var child in children.Where(x => contentTypeIdsA.Contains(x.ContentTypeId) == false))
|
||||
foreach (var child in children)
|
||||
{
|
||||
// see MoveToRecycleBin
|
||||
PerformMoveLocked(repository, child, Constants.System.RecycleBinContent, null, userId, moves, true);
|
||||
@@ -2550,7 +2555,7 @@ namespace Umbraco.Core.Services
|
||||
.Select(x => new MoveEventInfo<IContent>(x.Item1, x.Item2, x.Item1.ParentId))
|
||||
.ToArray();
|
||||
if (moveInfos.Length > 0)
|
||||
uow.Events.Dispatch(Trashed, this, new MoveEventArgs<IContent>(false, moveInfos));
|
||||
uow.Events.Dispatch(Trashed, this, new MoveEventArgs<IContent>(false, moveInfos), "Trashed");
|
||||
uow.Events.Dispatch(TreeChanged, this, changes.ToEventArgs());
|
||||
|
||||
Audit(uow, AuditType.Delete, $"Delete Content of Type {string.Join(",", contentTypeIdsA)} performed by user", userId, Constants.System.Root);
|
||||
|
||||
@@ -351,6 +351,11 @@ namespace Umbraco.Core.Services
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name))
|
||||
{
|
||||
throw new ArgumentException("Cannot save datatype with empty name.");
|
||||
}
|
||||
|
||||
var repository = uow.CreateRepository<IDataTypeDefinitionRepository>();
|
||||
repository.AddOrUpdate(dataTypeDefinition);
|
||||
|
||||
|
||||
@@ -114,7 +114,11 @@ namespace Umbraco.Core.Services
|
||||
throw new ArgumentException("No media with that id.", nameof(parentId));
|
||||
|
||||
var media = new Models.Media(name, parentId, mediaType);
|
||||
CreateMedia(null, media, parent, userId, false);
|
||||
using (var uow = UowProvider.CreateUnitOfWork())
|
||||
{
|
||||
CreateMedia(uow, media, parent, userId, false);
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
return media;
|
||||
}
|
||||
@@ -139,7 +143,11 @@ namespace Umbraco.Core.Services
|
||||
throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias));
|
||||
|
||||
var media = new Models.Media(name, -1, mediaType);
|
||||
CreateMedia(null, media, null, userId, false);
|
||||
using (var uow = UowProvider.CreateUnitOfWork())
|
||||
{
|
||||
CreateMedia(uow, media, null, userId, false);
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
return media;
|
||||
}
|
||||
@@ -1122,18 +1130,20 @@ namespace Umbraco.Core.Services
|
||||
|
||||
moves.Add(Tuple.Create(media, media.Path)); // capture original path
|
||||
|
||||
// get before moving, in case uow is immediate
|
||||
var descendants = GetDescendants(media);
|
||||
|
||||
// these will be updated by the repo because we changed parentId
|
||||
//media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id;
|
||||
//media.SortOrder = ((MediaRepository) repository).NextChildSortOrder(parentId);
|
||||
//media.Level += levelDelta;
|
||||
PerformMoveMediaLocked(repository, media, userId, trash);
|
||||
|
||||
// BUT media.Path will be updated only when the UOW commits, and
|
||||
// because we want it now, we have to calculate it by ourselves
|
||||
// if uow is not immediate, content.Path will be updated only when the UOW commits,
|
||||
// and because we want it now, we have to calculate it by ourselves
|
||||
//paths[media.Id] = media.Path;
|
||||
paths[media.Id] = (parent == null ? (parentId == Constants.System.RecycleBinMedia ? "-1,-21" : "-1") : parent.Path) + "," + media.Id;
|
||||
|
||||
var descendants = GetDescendants(media);
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path
|
||||
@@ -1407,7 +1417,11 @@ namespace Umbraco.Core.Services
|
||||
var moves = new List<Tuple<IMedia, string>>();
|
||||
var mediaTypeIdsA = mediaTypeIds.ToArray();
|
||||
|
||||
using (var uow = UowProvider.CreateUnitOfWork())
|
||||
// using an immediate uow here because we keep making changes with
|
||||
// PerformMoveLocked and DeleteLocked that must be applied immediately,
|
||||
// no point queuing operations
|
||||
//
|
||||
using (var uow = UowProvider.CreateUnitOfWork(immediate: true))
|
||||
{
|
||||
uow.WriteLock(Constants.Locks.MediaTree);
|
||||
var repository = uow.CreateRepository<IMediaRepository>();
|
||||
@@ -1446,7 +1460,7 @@ namespace Umbraco.Core.Services
|
||||
.Select(x => new MoveEventInfo<IMedia>(x.Item1, x.Item2, x.Item1.ParentId))
|
||||
.ToArray();
|
||||
if (moveInfos.Length > 0)
|
||||
uow.Events.Dispatch(Trashed, this, new MoveEventArgs<IMedia>(false, moveInfos));
|
||||
uow.Events.Dispatch(Trashed, this, new MoveEventArgs<IMedia>(false, moveInfos), "Trashed");
|
||||
uow.Events.Dispatch(TreeChanged, this, changes.ToEventArgs());
|
||||
|
||||
Audit(uow, AuditType.Delete, $"Delete Media of types {string.Join(",", mediaTypeIdsA)} performed by user", userId, Constants.System.Root);
|
||||
|
||||
@@ -122,7 +122,11 @@ namespace Umbraco.Core.Services
|
||||
throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias));
|
||||
|
||||
var member = new Member(name, email.ToLower().Trim(), username, memberType);
|
||||
CreateMember(null, member, 0, false);
|
||||
using (var uow = UowProvider.CreateUnitOfWork())
|
||||
{
|
||||
CreateMember(uow, member, 0, false);
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
@@ -143,7 +147,11 @@ namespace Umbraco.Core.Services
|
||||
if (memberType == null) throw new ArgumentNullException(nameof(memberType));
|
||||
|
||||
var member = new Member(name, email.ToLower().Trim(), username, memberType);
|
||||
CreateMember(null, member, 0, false);
|
||||
using (var uow = UowProvider.CreateUnitOfWork())
|
||||
{
|
||||
CreateMember(uow, member, 0, false);
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
@@ -348,7 +356,7 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
// fixme get rid of string filter, ignored here = bad!
|
||||
// fixme get rid of string filter?
|
||||
|
||||
public IEnumerable<IMember> GetAll(long pageIndex, int pageSize, out long totalRecords,
|
||||
string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "")
|
||||
@@ -363,8 +371,9 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
uow.ReadLock(Constants.Locks.MemberTree);
|
||||
var repository = uow.CreateRepository<IMemberRepository>();
|
||||
var query = memberTypeAlias == null ? null : repository.QueryT.Where(x => x.ContentTypeAlias == memberTypeAlias);
|
||||
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField /*, filter*/);
|
||||
var query1 = memberTypeAlias == null ? null : repository.QueryT.Where(x => x.ContentTypeAlias == memberTypeAlias);
|
||||
var query2 = filter == null ? null : repository.QueryT.Where(x => x.Name.Contains(filter) || x.Username.Contains(filter));
|
||||
return repository.GetPagedResultsByQuery(query1, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, query2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,6 +841,11 @@ namespace Umbraco.Core.Services
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(member.Name))
|
||||
{
|
||||
throw new ArgumentException("Cannot save member with empty name.");
|
||||
}
|
||||
|
||||
uow.WriteLock(Constants.Locks.MemberTree);
|
||||
var repository = uow.CreateRepository<IMemberRepository>();
|
||||
|
||||
|
||||
@@ -146,10 +146,9 @@ namespace Umbraco.Tests.Plugins
|
||||
[Test]
|
||||
public void Detect_Legacy_Plugin_File_List()
|
||||
{
|
||||
var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
var filePath= Path.Combine(tempFolder, string.Format("umbraco-plugins.{0}.list", NetworkHelper.FileSafeMachineName));
|
||||
var filePath = _manager.GeTypesListFilePath();
|
||||
var fileDir = Path.GetDirectoryName(filePath);
|
||||
Directory.CreateDirectory(fileDir);
|
||||
|
||||
File.WriteAllText(filePath, @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<plugins>
|
||||
|
||||
@@ -198,8 +198,7 @@ DetermineRouteById(id):
|
||||
|
||||
SettingsForTests.HideTopLevelNodeFromPath = hide;
|
||||
|
||||
const bool preview = true; // make sure we don't cache
|
||||
var route = cache.GetRouteById(preview, id);
|
||||
var route = cache.GetRouteById(false, id);
|
||||
Assert.AreEqual(expected, route);
|
||||
}
|
||||
|
||||
@@ -222,8 +221,7 @@ DetermineRouteById(id):
|
||||
|
||||
SettingsForTests.HideTopLevelNodeFromPath = hide;
|
||||
|
||||
const bool preview = true; // make sure we don't cache
|
||||
var route = cache.GetRouteById(preview, id);
|
||||
var route = cache.GetRouteById(false, id);
|
||||
Assert.AreEqual(expected, route);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,274 +1,303 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Tests.Cache.DistributedCache;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Web.Cache;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.PublishedCache.XmlPublishedCache;
|
||||
|
||||
namespace Umbraco.Tests.Scoping
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, FacadeServiceRepositoryEvents = true)]
|
||||
public class ScopedXmlTests : TestWithDatabaseBase
|
||||
{
|
||||
[Test]
|
||||
public void Fail()
|
||||
{
|
||||
// fixme
|
||||
// need to rewrite these tests entirely using the XmlStore and such
|
||||
// need to create an equivalent test for NuCache once we understand it
|
||||
private CacheRefresherComponent _cacheRefresher;
|
||||
|
||||
Assert.Fail("need to rewrite these tests entirely for XmlStore");
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
// the cache refresher component needs to trigger to refresh caches
|
||||
// but then, it requires a lot of plumbing ;(
|
||||
// fixme - and we cannot inject a DistributedCache yet
|
||||
// so doing all this mess
|
||||
Container.RegisterSingleton<IServerMessenger, LocalServerMessenger>();
|
||||
Container.RegisterSingleton(f => Mock.Of<IServerRegistrar>());
|
||||
Container.RegisterCollectionBuilder<CacheRefresherCollectionBuilder>()
|
||||
.Add(f => f.TryGetInstance<TypeLoader>().GetCacheRefreshers());
|
||||
}
|
||||
|
||||
//private CacheRefresherComponent _cacheRefresher;
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
_cacheRefresher?.Unbind();
|
||||
_cacheRefresher = null;
|
||||
|
||||
//// fixme ?
|
||||
////protected override void FreezeResolution()
|
||||
////{
|
||||
//// ServerRegistrarResolver.Current = new ServerRegistrarResolver(
|
||||
//// new DistributedCacheTests.TestServerRegistrar());
|
||||
//// ServerMessengerResolver.Current = new ServerMessengerResolver(
|
||||
//// new DatabaseServerMessenger(ApplicationContext, false, new DatabaseServerMessengerOptions()));
|
||||
//// CacheRefreshersResolver.Current = new CacheRefreshersResolver(
|
||||
//// new ActivatorServiceProvider(), Mock.Of<ILogger>(), () => new[]
|
||||
//// {
|
||||
//// typeof(PageCacheRefresher),
|
||||
//// typeof(UnpublishedPageCacheRefresher),
|
||||
_onPublishedAssertAction = null;
|
||||
ContentService.Published -= OnPublishedAssert;
|
||||
SafeXmlReaderWriter.Cloning = null;
|
||||
}
|
||||
|
||||
//// typeof(DomainCacheRefresher),
|
||||
//// typeof(MacroCacheRefresher)
|
||||
//// });
|
||||
private void OnPublishedAssert(IContentService sender, PublishEventArgs<IContent> args)
|
||||
{
|
||||
_onPublishedAssertAction?.Invoke();
|
||||
}
|
||||
|
||||
//// base.FreezeResolution();
|
||||
////}
|
||||
private Action _onPublishedAssertAction;
|
||||
|
||||
//[TearDown]
|
||||
//public void Teardown()
|
||||
//{
|
||||
// _cacheRefresher?.Unbind();
|
||||
// _cacheRefresher = null;
|
||||
// in 7.6, content.Instance
|
||||
// .XmlContent - comes from .XmlContentInternal and is cached in context items for current request
|
||||
// .XmlContentInternal - the actual main xml document
|
||||
// become in 8
|
||||
// xmlStore.Xml - the actual main xml document
|
||||
// publishedContentCache.GetXml() - the captured xml
|
||||
|
||||
// _onPublishedAssertAction = null;
|
||||
// content.HttpContextItemsGetter = null;
|
||||
// ContentService.Published -= OnPublishedAssert;
|
||||
// SafeXmlReaderWriter.Cloning = null;
|
||||
private static XmlStore XmlStore => (Current.Container.GetInstance<IFacadeService>() as FacadeService).XmlStore;
|
||||
private static XmlDocument XmlMaster => XmlStore.Xml;
|
||||
private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.ContentCache).GetXml(false);
|
||||
|
||||
// ServerRegistrarResolver.Reset();
|
||||
// ServerMessengerResolver.Reset();
|
||||
// CacheRefreshersResolver.Reset();
|
||||
//}
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestScope(bool complete)
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext("http://example.com/", setSingleton: true);
|
||||
|
||||
//private void OnPublishedAssert(IContentService sender, PublishEventArgs<IContent> args)
|
||||
//{
|
||||
// _onPublishedAssertAction?.Invoke();
|
||||
//}
|
||||
// sanity checks
|
||||
Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext);
|
||||
Assert.AreSame(XmlStore, ((PublishedContentCache) umbracoContext.ContentCache).XmlStore);
|
||||
|
||||
//private Action _onPublishedAssertAction;
|
||||
// settings
|
||||
var settings = SettingsForTests.GenerateMockSettings();
|
||||
var contentMock = Mock.Get(settings.Content);
|
||||
contentMock.Setup(x => x.XmlCacheEnabled).Returns(false);
|
||||
SettingsForTests.ConfigureSettings(settings);
|
||||
|
||||
//[TestCase(true)]
|
||||
//[TestCase(false)]
|
||||
//public void TestScope(bool complete)
|
||||
//{
|
||||
// content.TestingUpdateSitemapProvider = false;
|
||||
// create document type, document
|
||||
var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" };
|
||||
Current.Services.ContentTypeService.Save(contentType);
|
||||
var item = new Content("name", -1, contentType);
|
||||
|
||||
// var httpContextItems = new Hashtable();
|
||||
// content.HttpContextItemsGetter = () => httpContextItems;
|
||||
// wire cache refresher
|
||||
_cacheRefresher = new CacheRefresherComponent(true);
|
||||
_cacheRefresher.Initialize();
|
||||
|
||||
// var settings = SettingsForTests.GenerateMockSettings();
|
||||
// var contentMock = Mock.Get(settings.Content);
|
||||
// contentMock.Setup(x => x.XmlCacheEnabled).Returns(false);
|
||||
// SettingsForTests.ConfigureSettings(settings);
|
||||
// check xml in context = "before"
|
||||
var xml = XmlInContext;
|
||||
var beforeXml = xml;
|
||||
var beforeOuterXml = beforeXml.OuterXml;
|
||||
Console.WriteLine("Xml Before:");
|
||||
Console.WriteLine(xml.OuterXml);
|
||||
|
||||
// var contentType = new ContentType(-1) { Alias = "contenttype", Name = "test"};
|
||||
// Current.Services.ContentTypeService.Save(contentType);
|
||||
// event handler
|
||||
var evented = 0;
|
||||
_onPublishedAssertAction = () =>
|
||||
{
|
||||
evented++;
|
||||
|
||||
// _cacheRefresher = new CacheRefresherComponent(true);
|
||||
// _cacheRefresher.Initialize();
|
||||
// should see the changes in context, not in master
|
||||
xml = XmlInContext;
|
||||
Assert.AreNotSame(beforeXml, xml);
|
||||
Console.WriteLine("Xml Event:");
|
||||
Console.WriteLine(xml.OuterXml);
|
||||
var node = xml.GetElementById(item.Id.ToString());
|
||||
Assert.IsNotNull(node);
|
||||
|
||||
// var xml = content.Instance.XmlContent;
|
||||
// Assert.IsNotNull(xml);
|
||||
// Assert.AreSame(xml, httpContextItems[content.XmlContextContentItemKey]);
|
||||
// var beforeXml = xml;
|
||||
// var beforeOuterXml = beforeXml.OuterXml;
|
||||
// var item = new Content("name", -1, contentType);
|
||||
xml = XmlMaster;
|
||||
Assert.AreSame(beforeXml, xml);
|
||||
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
|
||||
};
|
||||
|
||||
// Console.WriteLine("Xml Before:");
|
||||
// Console.WriteLine(xml.OuterXml);
|
||||
ContentService.Published += OnPublishedAssert;
|
||||
|
||||
// var evented = 0;
|
||||
// _onPublishedAssertAction = () =>
|
||||
// {
|
||||
// evented++;
|
||||
// xml = content.Instance.XmlContent;
|
||||
// Assert.IsNotNull(xml);
|
||||
// Assert.AreNotSame(beforeXml, xml);
|
||||
// Console.WriteLine("Xml Event:");
|
||||
// Console.WriteLine(xml.OuterXml);
|
||||
// var node = xml.GetElementById(item.Id.ToString());
|
||||
// Assert.IsNotNull(node);
|
||||
// };
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Current.Services.ContentService.SaveAndPublishWithStatus(item); // should create an xml clone
|
||||
item.Name = "changed";
|
||||
Current.Services.ContentService.SaveAndPublishWithStatus(item); // should re-use the xml clone
|
||||
|
||||
// ContentService.Published += OnPublishedAssert;
|
||||
// this should never change
|
||||
Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
|
||||
|
||||
// using (var scope = ScopeProvider.CreateScope())
|
||||
// {
|
||||
// Current.Services.ContentService.SaveAndPublishWithStatus(item); // should create an xml clone
|
||||
// item.Name = "changed";
|
||||
// Current.Services.ContentService.SaveAndPublishWithStatus(item); // should re-use the xml clone
|
||||
// this does not change, other thread don't see the changes
|
||||
xml = XmlMaster;
|
||||
Assert.AreSame(beforeXml, xml);
|
||||
Console.WriteLine("XmlInternal During:");
|
||||
Console.WriteLine(xml.OuterXml);
|
||||
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
|
||||
|
||||
// // this should never change
|
||||
// Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
|
||||
// this does not change during the scope (only in events)
|
||||
// because it is the events that trigger the changes
|
||||
xml = XmlInContext;
|
||||
Assert.IsNotNull(xml);
|
||||
Assert.AreSame(beforeXml, xml);
|
||||
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
|
||||
|
||||
// // this does not change, other thread don't see the changes
|
||||
// xml = content.Instance.XmlContentInternal;
|
||||
// Assert.IsNotNull(xml);
|
||||
// Assert.AreSame(beforeXml, xml);
|
||||
// Console.WriteLine("XmlInternal During:");
|
||||
// Console.WriteLine(xml.OuterXml);
|
||||
// Assert.AreEqual(beforeOuterXml, xml.OuterXml);
|
||||
// note
|
||||
// this means that, as long as ppl don't create scopes, they'll see their
|
||||
// changes right after SaveAndPublishWithStatus, but if they create scopes,
|
||||
// they will have to wail until the scope is completed, ie wait until the
|
||||
// events trigger, to use eg GetUrl etc
|
||||
|
||||
// // this does not change during the scope (only in events)
|
||||
// // because it is the events that trigger the changes
|
||||
// xml = content.Instance.XmlContent;
|
||||
// Assert.IsNotNull(xml);
|
||||
// Assert.AreSame(beforeXml, xml);
|
||||
|
||||
// // note
|
||||
// // this means that, as long as ppl don't create scopes, they'll see their
|
||||
// // changes right after SaveAndPublishWithStatus, but if they create scopes,
|
||||
// // they will have to wail until the scope is completed, ie wait until the
|
||||
// // events trigger, to use eg GetUrl etc
|
||||
|
||||
// if (complete)
|
||||
// scope.Complete();
|
||||
// }
|
||||
if (complete)
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
//The reason why there is only 1 event occuring is because we are publishing twice for the same event for the same
|
||||
//object and the scope deduplicates the events (uses the latest)
|
||||
// Assert.AreEqual(complete ? 1 : 0, evented);
|
||||
//object and the scope deduplicates the events(uses the latest)
|
||||
Assert.AreEqual(complete? 1 : 0, evented);
|
||||
|
||||
// // this should never change
|
||||
// Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
|
||||
// this should never change
|
||||
Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
|
||||
|
||||
// xml = content.Instance.XmlContent;
|
||||
// Assert.IsNotNull(xml);
|
||||
// Console.WriteLine("Xml After:");
|
||||
// Console.WriteLine(xml.OuterXml);
|
||||
// if (complete)
|
||||
// {
|
||||
// var node = xml.GetElementById(item.Id.ToString());
|
||||
// Assert.IsNotNull(node);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Assert.AreSame(beforeXml, xml);
|
||||
// Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed!
|
||||
// }
|
||||
//}
|
||||
xml = XmlInContext;
|
||||
Console.WriteLine("Xml After:");
|
||||
Console.WriteLine(xml.OuterXml);
|
||||
if (complete)
|
||||
{
|
||||
var node = xml.GetElementById(item.Id.ToString());
|
||||
Assert.IsNotNull(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreSame(beforeXml, xml);
|
||||
Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed!
|
||||
}
|
||||
|
||||
//[TestCase(true)]
|
||||
//[TestCase(false)]
|
||||
//public void TestScopeMany(bool complete)
|
||||
//{
|
||||
// content.TestingUpdateSitemapProvider = false;
|
||||
xml = XmlMaster;
|
||||
if (complete)
|
||||
{
|
||||
var node = xml.GetElementById(item.Id.ToString());
|
||||
Assert.IsNotNull(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreSame(beforeXml, xml);
|
||||
Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed!
|
||||
}
|
||||
}
|
||||
|
||||
// var settings = SettingsForTests.GenerateMockSettings();
|
||||
// var contentMock = Mock.Get(settings.Content);
|
||||
// contentMock.Setup(x => x.XmlCacheEnabled).Returns(false);
|
||||
// SettingsForTests.ConfigureSettings(settings);
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestScopeMany(bool complete)
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext("http://example.com/", setSingleton: true);
|
||||
|
||||
// var contentType = new ContentType(-1) { Alias = "contenttype", Name = "test" };
|
||||
// Current.Services.ContentTypeService.Save(contentType);
|
||||
// sanity checks
|
||||
Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext);
|
||||
Assert.AreSame(XmlStore, ((PublishedContentCache)umbracoContext.ContentCache).XmlStore);
|
||||
|
||||
// _cacheRefresher = new CacheRefresherComponent(true);
|
||||
// _cacheRefresher.Initialize();
|
||||
// settings
|
||||
var settings = SettingsForTests.GenerateMockSettings();
|
||||
var contentMock = Mock.Get(settings.Content);
|
||||
contentMock.Setup(x => x.XmlCacheEnabled).Returns(false);
|
||||
SettingsForTests.ConfigureSettings(settings);
|
||||
|
||||
// var xml = content.Instance.XmlContent;
|
||||
// Assert.IsNotNull(xml);
|
||||
// var beforeXml = xml;
|
||||
// var beforeOuterXml = beforeXml.OuterXml;
|
||||
// var item = new Content("name", -1, contentType);
|
||||
// const int count = 10;
|
||||
// var ids = new int[count];
|
||||
// var clones = 0;
|
||||
// create document type
|
||||
var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" };
|
||||
Current.Services.ContentTypeService.Save(contentType);
|
||||
|
||||
// SafeXmlReaderWriter.Cloning = () => { clones++; };
|
||||
// wire cache refresher
|
||||
_cacheRefresher = new CacheRefresherComponent(true);
|
||||
_cacheRefresher.Initialize();
|
||||
|
||||
// Console.WriteLine("Xml Before:");
|
||||
// Console.WriteLine(xml.OuterXml);
|
||||
// check xml in context = "before"
|
||||
var xml = XmlInContext;
|
||||
var beforeXml = xml;
|
||||
var beforeOuterXml = beforeXml.OuterXml;
|
||||
var item = new Content("name", -1, contentType);
|
||||
const int count = 10;
|
||||
var ids = new int[count];
|
||||
var clones = 0;
|
||||
|
||||
// using (var scope = ScopeProvider.CreateScope())
|
||||
// {
|
||||
// Current.Services.ContentService.SaveAndPublishWithStatus(item);
|
||||
SafeXmlReaderWriter.Cloning = () => { clones++; };
|
||||
|
||||
// for (var i = 0; i < count; i++)
|
||||
// {
|
||||
// var temp = new Content("content_" + i, -1, contentType);
|
||||
// Current.Services.ContentService.SaveAndPublishWithStatus(temp);
|
||||
// ids[i] = temp.Id;
|
||||
// }
|
||||
Console.WriteLine("Xml Before:");
|
||||
Console.WriteLine(xml.OuterXml);
|
||||
|
||||
// // this should never change
|
||||
// Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Current.Services.ContentService.SaveAndPublishWithStatus(item);
|
||||
|
||||
// xml = content.Instance.XmlContent;
|
||||
// Assert.IsNotNull(xml);
|
||||
// Console.WriteLine("Xml InScope (before complete):");
|
||||
// Console.WriteLine(xml.OuterXml);
|
||||
// Assert.AreEqual(beforeOuterXml, xml.OuterXml);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var temp = new Content("content_" + i, -1, contentType);
|
||||
Current.Services.ContentService.SaveAndPublishWithStatus(temp);
|
||||
ids[i] = temp.Id;
|
||||
}
|
||||
|
||||
// if (complete)
|
||||
// scope.Complete();
|
||||
// this should never change
|
||||
Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
|
||||
|
||||
// xml = content.Instance.XmlContent;
|
||||
// Assert.IsNotNull(xml);
|
||||
// Console.WriteLine("Xml InScope (after complete):");
|
||||
// Console.WriteLine(xml.OuterXml);
|
||||
// Assert.AreEqual(beforeOuterXml, xml.OuterXml);
|
||||
// }
|
||||
xml = XmlMaster;
|
||||
Assert.IsNotNull(xml);
|
||||
Console.WriteLine("Xml InScope (before complete):");
|
||||
Console.WriteLine(xml.OuterXml);
|
||||
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
|
||||
|
||||
// var scopeProvider = ScopeProvider;
|
||||
// Assert.IsNotNull(scopeProvider);
|
||||
// // ambient scope may be null, or maybe not, depending on whether the code that
|
||||
// // was called did proper scoped work, or some direct (NoScope) use of the database
|
||||
// Assert.IsNull(scopeProvider.AmbientContext);
|
||||
if (complete)
|
||||
scope.Complete();
|
||||
|
||||
// // limited number of clones!
|
||||
// Assert.AreEqual(complete ? 1 : 0, clones);
|
||||
xml = XmlMaster;
|
||||
Assert.IsNotNull(xml);
|
||||
Console.WriteLine("Xml InScope (after complete):");
|
||||
Console.WriteLine(xml.OuterXml);
|
||||
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
|
||||
}
|
||||
|
||||
// // this should never change
|
||||
// Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
|
||||
var scopeProvider = ScopeProvider;
|
||||
Assert.IsNotNull(scopeProvider);
|
||||
// ambient scope may be null, or maybe not, depending on whether the code that
|
||||
// was called did proper scoped work, or some direct (NoScope) use of the database
|
||||
Assert.IsNull(scopeProvider.AmbientContext);
|
||||
|
||||
// xml = content.Instance.XmlContent;
|
||||
// Assert.IsNotNull(xml);
|
||||
// Console.WriteLine("Xml After:");
|
||||
// Console.WriteLine(xml.OuterXml);
|
||||
// if (complete)
|
||||
// {
|
||||
// var node = xml.GetElementById(item.Id.ToString());
|
||||
// Assert.IsNotNull(node);
|
||||
// limited number of clones!
|
||||
Assert.AreEqual(complete ? 1 : 0, clones);
|
||||
|
||||
// for (var i = 0; i < 10; i++)
|
||||
// {
|
||||
// node = xml.GetElementById(ids[i].ToString());
|
||||
// Assert.IsNotNull(node);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed!
|
||||
// }
|
||||
//}
|
||||
// this should never change
|
||||
Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
|
||||
|
||||
xml = XmlMaster;
|
||||
Assert.IsNotNull(xml);
|
||||
Console.WriteLine("Xml After:");
|
||||
Console.WriteLine(xml.OuterXml);
|
||||
if (complete)
|
||||
{
|
||||
var node = xml.GetElementById(item.Id.ToString());
|
||||
Assert.IsNotNull(node);
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
node = xml.GetElementById(ids[i].ToString());
|
||||
Assert.IsNotNull(node);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed!
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalServerMessenger : ServerMessengerBase
|
||||
{
|
||||
public LocalServerMessenger()
|
||||
: base(false)
|
||||
{ }
|
||||
|
||||
protected override void DeliverRemote(IEnumerable<IServerAddress> servers, ICacheRefresher refresher, MessageType messageType, IEnumerable<object> ids = null, string json = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,8 @@ namespace Umbraco.Tests.TestHelpers
|
||||
Current.Logger);
|
||||
|
||||
// testing=true so XmlStore will not use the file nor the database
|
||||
var facadeAccessor = new TestFacadeAccessor();
|
||||
//var facadeAccessor = new TestFacadeAccessor();
|
||||
var facadeAccessor = new UmbracoContextFacadeAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
|
||||
var service = new FacadeService(
|
||||
Current.Services,
|
||||
(ScopeProvider) Current.ScopeProvider,
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace Umbraco.Tests.Web
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
Current.Reset();
|
||||
|
||||
// fixme - now UrlProvider depends on EntityService for GetUrl(guid) - this is bad
|
||||
// should not depend on more than IdkMap maybe - fix this!
|
||||
var entityService = new Mock<IEntityService>();
|
||||
|
||||
@@ -498,9 +498,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
return previewXml ?? _xml;
|
||||
}
|
||||
|
||||
internal void Resync()
|
||||
internal void Resync(XmlDocument xml)
|
||||
{
|
||||
_xml = _xmlStore.Xml; // re-capture
|
||||
_xml = xml; // re-capture
|
||||
|
||||
// note: we're not resyncing "preview" because that would mean re-building the whole
|
||||
// preview set which is costly, so basically when previewing, there will be no resync.
|
||||
|
||||
@@ -301,7 +301,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
/// </remarks>
|
||||
public Func<XmlDocument> GetXmlDocument { get; set; }
|
||||
|
||||
private readonly XmlDocument _xmlDocument; // supplied xml document (for tests)
|
||||
private XmlDocument _xmlDocument; // supplied xml document (for tests)
|
||||
private volatile XmlDocument _xml; // master xml document
|
||||
private readonly AsyncLock _xmlLock = new AsyncLock(); // protects _xml
|
||||
|
||||
@@ -311,10 +311,18 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_xml != null)
|
||||
return _xml;
|
||||
|
||||
if (_xmlDocument != null)
|
||||
return _xmlDocument;
|
||||
{
|
||||
_xml = _xmlDocument;
|
||||
_xmlDocument = null;
|
||||
return _xml;
|
||||
}
|
||||
|
||||
if (GetXmlDocument != null)
|
||||
return GetXmlDocument();
|
||||
return _xml = GetXmlDocument();
|
||||
|
||||
LazyInitializeContent();
|
||||
ReloadXmlFromFileIfChanged();
|
||||
@@ -590,24 +598,24 @@ AND (umbracoNode.id=@id)";
|
||||
// gets a locked safe read access to the main xml
|
||||
private SafeXmlReaderWriter GetSafeXmlReader()
|
||||
{
|
||||
return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xmlDocument,
|
||||
_ => ResyncCurrentFacade(),
|
||||
return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xml,
|
||||
ResyncCurrentFacade,
|
||||
(xml, registerXmlChange) =>
|
||||
{
|
||||
SetXmlLocked(xml, registerXmlChange);
|
||||
ResyncCurrentFacade();
|
||||
ResyncCurrentFacade(xml);
|
||||
}, false);
|
||||
}
|
||||
|
||||
// gets a locked safe write access to the main xml (cloned)
|
||||
private SafeXmlReaderWriter GetSafeXmlWriter()
|
||||
{
|
||||
return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xmlDocument,
|
||||
_ => ResyncCurrentFacade(),
|
||||
return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xml,
|
||||
ResyncCurrentFacade,
|
||||
(xml, registerXmlChange) =>
|
||||
{
|
||||
SetXmlLocked(xml, registerXmlChange);
|
||||
ResyncCurrentFacade();
|
||||
ResyncCurrentFacade(xml);
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -1184,10 +1192,7 @@ ORDER BY umbracoNode.level, umbracoNode.sortOrder";
|
||||
}
|
||||
|
||||
if (publishedChanged)
|
||||
{
|
||||
safeXml.AcceptChanges();
|
||||
ResyncCurrentFacade();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1216,8 +1221,6 @@ ORDER BY umbracoNode.level, umbracoNode.sortOrder";
|
||||
RefreshContentTypes(ids);
|
||||
|
||||
// ignore media and member types - we're not caching them
|
||||
|
||||
ResyncCurrentFacade();
|
||||
}
|
||||
|
||||
public void Notify(DataTypeCacheRefresher.JsonPayload[] payloads)
|
||||
@@ -1236,15 +1239,13 @@ ORDER BY umbracoNode.level, umbracoNode.sortOrder";
|
||||
// that's all we need to do as the changes have NO impact whatsoever on the Xml content
|
||||
|
||||
// ignore media and member types - we're not caching them
|
||||
|
||||
ResyncCurrentFacade();
|
||||
}
|
||||
|
||||
private void ResyncCurrentFacade()
|
||||
private void ResyncCurrentFacade(XmlDocument xml)
|
||||
{
|
||||
var facade = (Facade) _facadeAccessor.Facade;
|
||||
if (facade == null) return;
|
||||
((PublishedContentCache) facade.ContentCache).Resync();
|
||||
((PublishedContentCache) facade.ContentCache).Resync(xml);
|
||||
((PublishedMediaCache) facade.MediaCache).Resync();
|
||||
|
||||
// not trying to resync members or domains, which are not cached really
|
||||
|
||||
Reference in New Issue
Block a user