From d3ad2aec60e3b4894fc9ea7d03f674febaf66690 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Tue, 10 Jun 2025 02:14:27 +0300 Subject: Feature/persistent watch data (#14262) --- .../ScheduledTasks/Tasks/CleanupUserDataTask.cs | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs (limited to 'Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs') diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs new file mode 100644 index 000000000..74069d0ee --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs @@ -0,0 +1,77 @@ +#pragma warning disable RS0030 // Do not use banned APIs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.Implementations.Item; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// +/// Task to clean up any detached userdata from the database. +/// +public class CleanupUserDataTask : IScheduledTask +{ + private readonly ILocalizationManager _localization; + private readonly IDbContextFactory _dbProvider; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The localisation Provider. + /// The DB context factory. + /// A logger. + public CleanupUserDataTask(ILocalizationManager localization, IDbContextFactory dbProvider, ILogger logger) + { + _localization = localization; + _dbProvider = dbProvider; + _logger = logger; + } + + /// + public string Name => _localization.GetLocalizedString("CleanupUserDataTask"); + + /// + public string Description => _localization.GetLocalizedString("CleanupUserDataTaskDescription"); + + /// + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// + public string Key => nameof(CleanupUserDataTask); + + /// + public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + { + const int LimitDays = 90; + var userDataDate = DateTimeOffset.UtcNow.AddDays(LimitDays * -1); + var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var detachedUserData = dbContext.UserData.Where(e => e.ItemId == BaseItemRepository.PlaceholderId); + _logger.LogInformation("There are {NoDetached} detached UserData entires.", detachedUserData.Count()); + + detachedUserData = detachedUserData.Where(e => e.RetentionDate < userDataDate); + + _logger.LogInformation("{NoDetached} are older then {Limit} days.", detachedUserData.Count(), LimitDays); + + await detachedUserData.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + } + + progress.Report(100); + } + + /// + public IEnumerable GetDefaultTriggers() + { + yield break; + } +} -- cgit v1.2.3 From 7037121bd0876e834906441fc9e22e14dfbd9d73 Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Mon, 9 Jun 2025 19:58:25 -0500 Subject: chore/typo s/entires/entries --- Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs') diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs index 74069d0ee..044c5f5cd 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs @@ -57,7 +57,7 @@ public class CleanupUserDataTask : IScheduledTask await using (dbContext.ConfigureAwait(false)) { var detachedUserData = dbContext.UserData.Where(e => e.ItemId == BaseItemRepository.PlaceholderId); - _logger.LogInformation("There are {NoDetached} detached UserData entires.", detachedUserData.Count()); + _logger.LogInformation("There are {NoDetached} detached UserData entries.", detachedUserData.Count()); detachedUserData = detachedUserData.Where(e => e.RetentionDate < userDataDate); -- cgit v1.2.3 From 1c4c9cf733ce84a2b7a70a186f4412198951a271 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 12 Jun 2025 02:30:57 +0300 Subject: Fix UserData cleanup task and queries (#14280) --- .../ScheduledTasks/Tasks/CleanupUserDataTask.cs | 2 +- Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | 4 ++-- .../Jellyfin.Database.Implementations/Entities/UserData.cs | 2 +- .../Migrations/JellyfinDbModelSnapshot.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs') diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs index 044c5f5cd..4156050eb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs @@ -52,7 +52,7 @@ public class CleanupUserDataTask : IScheduledTask public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { const int LimitDays = 90; - var userDataDate = DateTimeOffset.UtcNow.AddDays(LimitDays * -1); + var userDataDate = DateTime.UtcNow.AddDays(LimitDays * -1); var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); await using (dbContext.ConfigureAwait(false)) { diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index f6f2d5577..23a2a98d0 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -108,7 +108,7 @@ public sealed class BaseItemRepository using var context = _dbProvider.CreateDbContext(); using var transaction = context.Database.BeginTransaction(); - var date = (DateTimeOffset?)DateTimeOffset.Now; + var date = (DateTime?)DateTime.UtcNow; // Detach all user watch data context.UserData.Where(e => e.ItemId == id) .ExecuteUpdate(e => e @@ -529,7 +529,7 @@ public sealed class BaseItemRepository { // reattach old userData entries var userKeys = item.UserDataKey.ToArray(); - var retentionDate = (DateTimeOffset?)null; + var retentionDate = (DateTime?)null; context.UserData .Where(e => e.ItemId == PlaceholderId) .Where(e => userKeys.Contains(e.CustomDataKey)) diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/UserData.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/UserData.cs index d7aa79dc9..3d8b01c2b 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/UserData.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/UserData.cs @@ -71,7 +71,7 @@ public class UserData /// /// Gets or Sets the date the referenced has been deleted. /// - public DateTimeOffset? RetentionDate { get; set; } + public DateTime? RetentionDate { get; set; } /// /// Gets or sets the key. diff --git a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/JellyfinDbModelSnapshot.cs b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/JellyfinDbModelSnapshot.cs index 01ba3dfd8..706215eef 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/JellyfinDbModelSnapshot.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/JellyfinDbModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "9.0.5"); + modelBuilder.HasAnnotation("ProductVersion", "9.0.6"); modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.AccessSchedule", b => { @@ -1388,7 +1388,7 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Rating") .HasColumnType("REAL"); - b.Property("RetentionDate") + b.Property("RetentionDate") .HasColumnType("TEXT"); b.Property("SubtitleStreamIndex") -- cgit v1.2.3