Skip to content

Commit

Permalink
Implemented ContentVersionCleanup scheduled task.
Browse files Browse the repository at this point in the history
Note: adding ref to Microsoft.NET.Test.Sdk fixes AutoFixture AutoDataAttribute (and sub classes)
  • Loading branch information
Paul Johnson committed Oct 26, 2021
1 parent a1ac730 commit bba089c
Show file tree
Hide file tree
Showing 30 changed files with 1,416 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static Composition ComposeRepositories(this Composition composition)
composition.RegisterUnique<IContentTypeCommonRepository, ContentTypeCommonRepository>();
composition.RegisterUnique<IInstallationRepository, InstallationRepository>();
composition.RegisterUnique<IUpgradeCheckRepository, UpgradeCheckRepository>();
composition.RegisterUnique<IDocumentVersionRepository, DocumentVersionRepository>();

return composition;
}
Expand Down
7 changes: 6 additions & 1 deletion src/Umbraco.Core/Composing/CompositionExtensions/Services.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ public static Composition ComposeServices(this Composition composition)
composition.RegisterUnique<IDomainService, DomainService>();
composition.RegisterUnique<IAuditService, AuditService>();
composition.RegisterUnique<ITagService, TagService>();
composition.RegisterUnique<IContentService, ContentService>();

composition.RegisterUnique<ContentService>();
composition.RegisterUnique<IContentService>(factory => factory.GetInstance<ContentService>());
composition.RegisterUnique<IContentVersionCleanupService>(factory => factory.GetInstance<ContentService>());
composition.RegisterUnique<IContentVersionCleanupPolicy, DefaultContentVersionCleanupPolicy>();

composition.RegisterUnique<IUserService, UserService>();
composition.RegisterUnique<IMemberService, MemberService>();
composition.RegisterUnique<IMediaService, MediaService>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ internal class ContentElement : UmbracoConfigurationElement, IContentSection
[ConfigurationProperty("notifications", IsRequired = true)]
internal NotificationsElement Notifications => (NotificationsElement) base["notifications"];

[ConfigurationProperty("contentVersionCleanupPolicy", IsRequired = false)]
internal ContentVersionCleanupPolicyElement ContentVersionCleanupPolicy => (ContentVersionCleanupPolicyElement) this["contentVersionCleanupPolicy"];
[ConfigurationProperty("contentVersionCleanupPolicyGlobalSettings", IsRequired = false)]
internal ContentVersionCleanupPolicyGlobalSettingsElement ContentVersionCleanupPolicyGlobalSettingsElement => (ContentVersionCleanupPolicyGlobalSettingsElement) this["contentVersionCleanupPolicyGlobalSettings"];

[ConfigurationProperty("PreviewBadge")]
internal InnerTextConfigurationElement<string> PreviewBadge => GetOptionalTextElement("PreviewBadge", DefaultPreviewBadge);
Expand Down Expand Up @@ -64,7 +64,7 @@ internal class ContentElement : UmbracoConfigurationElement, IContentSection

IEnumerable<string> IContentSection.AllowedUploadFiles => AllowedUploadFiles;

IContentVersionCleanupPolicySettings IContentSection.ContentVersionCleanupPolicySettings => ContentVersionCleanupPolicy;
IContentVersionCleanupPolicyGlobalSettings IContentSection.ContentVersionCleanupPolicyGlobalSettings => ContentVersionCleanupPolicyGlobalSettingsElement;

bool IContentSection.ShowDeprecatedPropertyEditors => ShowDeprecatedPropertyEditors;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class ContentVersionCleanupPolicyElement : UmbracoConfigurationElement, IContentVersionCleanupPolicySettings
internal class ContentVersionCleanupPolicyGlobalSettingsElement : UmbracoConfigurationElement, IContentVersionCleanupPolicyGlobalSettings
{
[ConfigurationProperty("enable", DefaultValue = false)]
public bool EnableCleanup => (bool)this["enable"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public interface IContentSection : IUmbracoConfigurationSection

IEnumerable<string> AllowedUploadFiles { get; }

IContentVersionCleanupPolicySettings ContentVersionCleanupPolicySettings { get; }
IContentVersionCleanupPolicyGlobalSettings ContentVersionCleanupPolicyGlobalSettings { get; }

/// <summary>
/// Gets a value indicating whether to show deprecated property editors in
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IContentVersionCleanupPolicySettings
public interface IContentVersionCleanupPolicyGlobalSettings
{
bool EnableCleanup { get; }
int KeepAllVersionsNewerThanDays { get; }
Expand Down
13 changes: 13 additions & 0 deletions src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace Umbraco.Core.Models
{
public class ContentVersionCleanupPolicySettings
{
public int ContentTypeId { get; set; }
public int? KeepAllVersionsNewerThanDays { get; set; }
public int? KeepLatestVersionPerDayForDays { get; set; }
public bool PreventCleanup { get; set; }
public DateTime Updated { get; set; }
}
}
24 changes: 24 additions & 0 deletions src/Umbraco.Core/Models/HistoricContentVersionMeta.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace Umbraco.Core.Models
{
public class HistoricContentVersionMeta
{
public int ContentId { get; }
public int ContentTypeId { get; }
public int VersionId { get; }
public DateTime VersionDate { get; }

public HistoricContentVersionMeta() { }

public HistoricContentVersionMeta(int contentId, int contentTypeId, int versionId, DateTime versionDate)
{
ContentId = contentId;
ContentTypeId = contentTypeId;
VersionId = versionId;
VersionDate = versionDate;
}

public override string ToString() => $"HistoricContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@
namespace Umbraco.Core.Persistence.Dtos
{
[TableName(TableName)]
[PrimaryKey("contentTypeId")]
[PrimaryKey("contentTypeId", AutoIncrement = false)]
[ExplicitColumns]
internal class ContentVersionCleanupPolicyDto
{
public const string TableName = Constants.DatabaseSchema.Tables.ContentVersionCleanupPolicy;

[Column("contentTypeId")]
[PrimaryKeyColumn(AutoIncrement = false)]
[ForeignKey(typeof(ContentTypeDto), Column = "nodeId", OnDelete = Rule.Cascade)]
public int ContentTypeId { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using Umbraco.Core.Models;

namespace Umbraco.Core.Persistence.Repositories
{
public interface IDocumentVersionRepository : IRepository
{
/// <summary>
/// Gets a list of all historic content versions.
/// </summary>
public IReadOnlyCollection<HistoricContentVersionMeta> GetDocumentVersionsEligibleForCleanup();

/// <summary>
/// Gets cleanup policy override settings per content type.
/// </summary>
public IReadOnlyCollection<ContentVersionCleanupPolicySettings> GetCleanupPolicies();

/// <summary>
/// Deletes multiple content versions by ID.
/// </summary>
void DeleteVersions(IEnumerable<int> versionIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Scoping;

namespace Umbraco.Core.Persistence.Repositories.Implement
{
internal class DocumentVersionRepository : IDocumentVersionRepository
{
private readonly IScopeAccessor _scopeAccessor;

public DocumentVersionRepository(IScopeAccessor scopeAccessor)
{
_scopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor));
}

/// <inheritdoc />
/// <remarks>
/// Never includes current draft version. <br/>
/// Never includes current published version.<br/>
/// Never includes versions marked as "preventCleanup".<br/>
/// </remarks>
public IReadOnlyCollection<HistoricContentVersionMeta> GetDocumentVersionsEligibleForCleanup()
{
var query = _scopeAccessor.AmbientScope.SqlContext.Sql();

query.Select(@"umbracoDocument.nodeId as contentId,
umbracoContent.contentTypeId as contentTypeId,
umbracoContentVersion.id as versionId,
umbracoContentVersion.versionDate as versionDate")
.From<DocumentDto>()
.InnerJoin<ContentDto>()
.On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentVersionDto>()
.On<ContentDto, ContentVersionDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<DocumentVersionDto>()
.On<ContentVersionDto, DocumentVersionDto>(left => left.Id, right => right.Id)
.Where<ContentVersionDto>(x => !x.Current) // Never delete current draft version
.Where<ContentVersionDto>(x => !x.PreventCleanup) // Never delete "pinned" versions
.Where<DocumentVersionDto>(x => !x.Published); // Never delete published version

return _scopeAccessor.AmbientScope.Database.Fetch<HistoricContentVersionMeta>(query);
}

/// <inheritdoc />
public IReadOnlyCollection<ContentVersionCleanupPolicySettings> GetCleanupPolicies()
{
var query = _scopeAccessor.AmbientScope.SqlContext.Sql();

query.Select<ContentVersionCleanupPolicyDto>()
.From<ContentVersionCleanupPolicyDto>();

return _scopeAccessor.AmbientScope.Database.Fetch<ContentVersionCleanupPolicySettings>(query);
}

/// <inheritdoc />
/// <remarks>
/// Deletes in batches of <see cref="Constants.Sql.MaxParameterCount"/>
/// </remarks>
public void DeleteVersions(IEnumerable<int> versionIds)
{
foreach (var group in versionIds.InGroupsOf(Constants.Sql.MaxParameterCount))
{
var groupedVersionIds = group.ToList();

// Note: We had discussed doing this in a single SQL Command.
// If you can work out how to make that work with SQL CE, let me know!
// Can use test PerformContentVersionCleanup_WithNoKeepPeriods_DeletesEverythingExceptActive to try things out.

var query = _scopeAccessor.AmbientScope.SqlContext.Sql()
.Delete<PropertyDataDto>()
.WhereIn<PropertyDataDto>(x => x.VersionId, groupedVersionIds);
_scopeAccessor.AmbientScope.Database.Execute(query);

query = _scopeAccessor.AmbientScope.SqlContext.Sql()
.Delete<ContentVersionCultureVariationDto>()
.WhereIn<ContentVersionCultureVariationDto>(x => x.VersionId, groupedVersionIds);
_scopeAccessor.AmbientScope.Database.Execute(query);

query = _scopeAccessor.AmbientScope.SqlContext.Sql()
.Delete<DocumentVersionDto>()
.WhereIn<DocumentVersionDto>(x => x.Id, groupedVersionIds);
_scopeAccessor.AmbientScope.Database.Execute(query);

query = _scopeAccessor.AmbientScope.SqlContext.Sql()
.Delete<ContentVersionDto>()
.WhereIn<ContentVersionDto>(x => x.Id, groupedVersionIds);
_scopeAccessor.AmbientScope.Database.Execute(query);
}
}
}
}
17 changes: 17 additions & 0 deletions src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;

namespace Umbraco.Core.Services
{
/// <summary>
/// Used to filter historic content versions for cleanup.
/// </summary>
public interface IContentVersionCleanupPolicy
{
/// <summary>
/// Filters a set of candidates historic content versions for cleanup according to policy settings.
/// </summary>
IEnumerable<HistoricContentVersionMeta> Apply(DateTime asAtDate, IEnumerable<HistoricContentVersionMeta> items);
}
}
14 changes: 14 additions & 0 deletions src/Umbraco.Core/Services/IContentVersionCleanupService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;

namespace Umbraco.Core.Services
{
public interface IContentVersionCleanupService
{
/// <summary>
/// Removes historic content versions according to a policy.
/// </summary>
IReadOnlyCollection<HistoricContentVersionMeta> PerformContentVersionCleanup(DateTime asAtDate);
}
}
Loading

0 comments on commit bba089c

Please sign in to comment.