From 4d7e7d6331243bd339464bd6010569c6c308088b Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 3 Oct 2020 21:14:25 -0400 Subject: Rewrite activity log backend to use a query class. --- .../Activity/ActivityManager.cs | 37 ++++++++++------------ 1 file changed, 16 insertions(+), 21 deletions(-) (limited to 'Jellyfin.Server.Implementations/Activity/ActivityManager.cs') diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index abdd290d45..695e2fbd84 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -3,8 +3,10 @@ using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Querying; +using Microsoft.EntityFrameworkCore; namespace Jellyfin.Server.Implementations.Activity { @@ -39,41 +41,34 @@ namespace Jellyfin.Server.Implementations.Activity } /// - public QueryResult GetPagedResult( - Func, IQueryable> func, - int? startIndex, - int? limit) + public async Task> GetPagedResultAsync(ActivityLogQuery query) { - using var dbContext = _provider.CreateContext(); + await using var dbContext = _provider.CreateContext(); - var query = func(dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated)); + IQueryable entries = dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated); - if (startIndex.HasValue) + if (query.MinDate.HasValue) { - query = query.Skip(startIndex.Value); + entries = entries.Where(entry => entry.DateCreated >= query.MinDate); } - if (limit.HasValue) + if (query.HasUserId.HasValue) { - query = query.Take(limit.Value); + entries = entries.Where(entry => entry.UserId != Guid.Empty == query.HasUserId.Value ); } - // This converts the objects from the new database model to the old for compatibility with the existing API. - var list = query.Select(ConvertToOldModel).ToList(); - return new QueryResult { - Items = list, - TotalRecordCount = func(dbContext.ActivityLogs).Count() + Items = await entries.Skip(query.StartIndex ?? 0) + .Take(query.Limit ?? 100) + .Select(ConvertToOldModel) + .AsQueryable() + .ToListAsync() + .ConfigureAwait(false), + TotalRecordCount = await entries.CountAsync().ConfigureAwait(false) }; } - /// - public QueryResult GetPagedResult(int? startIndex, int? limit) - { - return GetPagedResult(logs => logs, startIndex, limit); - } - private static ActivityLogEntry ConvertToOldModel(ActivityLog entry) { return new ActivityLogEntry -- cgit v1.2.3 From d4a492ef93f6b663fd4a4f7710613f06863f401f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 5 Oct 2020 22:51:52 -0400 Subject: Fix activity log query. --- Jellyfin.Server.Implementations/Activity/ActivityManager.cs | 9 ++++++--- .../Jellyfin.Server.Implementations.csproj | 1 + .../Users/DisplayPreferencesManager.cs | 1 + Jellyfin.Server.Implementations/Users/UserManager.cs | 11 ++++++----- 4 files changed, 14 insertions(+), 8 deletions(-) (limited to 'Jellyfin.Server.Implementations/Activity/ActivityManager.cs') diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 695e2fbd84..5926abfe0d 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -45,7 +45,9 @@ namespace Jellyfin.Server.Implementations.Activity { await using var dbContext = _provider.CreateContext(); - IQueryable entries = dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated); + IQueryable entries = dbContext.ActivityLogs + .AsQueryable() + .OrderByDescending(entry => entry.DateCreated); if (query.MinDate.HasValue) { @@ -59,10 +61,11 @@ namespace Jellyfin.Server.Implementations.Activity return new QueryResult { - Items = await entries.Skip(query.StartIndex ?? 0) + Items = await entries + .Skip(query.StartIndex ?? 0) .Take(query.Limit ?? 100) + .AsAsyncEnumerable() .Select(ConvertToOldModel) - .AsQueryable() .ToListAsync() .ConfigureAwait(false), TotalRecordCount = await entries.CountAsync().ConfigureAwait(false) diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 4e79dd8d6c..17ba092588 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -24,6 +24,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index 46f1c618f2..76f9433854 100644 --- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -61,6 +61,7 @@ namespace Jellyfin.Server.Implementations.Users public IList ListItemDisplayPreferences(Guid userId, string client) { return _dbContext.ItemDisplayPreferences + .AsQueryable() .Where(prefs => prefs.UserId == userId && prefs.ItemId != Guid.Empty && string.Equals(prefs.Client, client)) .ToList(); } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 8f04baa089..8f6a0496ab 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -108,6 +108,7 @@ namespace Jellyfin.Server.Implementations.Users { using var dbContext = _dbProvider.CreateContext(); return dbContext.Users + .AsQueryable() .Select(user => user.Id) .ToList(); } @@ -200,8 +201,8 @@ namespace Jellyfin.Server.Implementations.Users internal async Task CreateUserInternalAsync(string name, JellyfinDb dbContext) { // TODO: Remove after user item data is migrated. - var max = await dbContext.Users.AnyAsync().ConfigureAwait(false) - ? await dbContext.Users.Select(u => u.InternalId).MaxAsync().ConfigureAwait(false) + var max = await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false) + ? await dbContext.Users.AsQueryable().Select(u => u.InternalId).MaxAsync().ConfigureAwait(false) : 0; return new User( @@ -221,7 +222,7 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); } - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); var newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false); @@ -587,9 +588,9 @@ namespace Jellyfin.Server.Implementations.Users public async Task InitializeAsync() { // TODO: Refactor the startup wizard so that it doesn't require a user to already exist. - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); - if (await dbContext.Users.AnyAsync().ConfigureAwait(false)) + if (await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false)) { return; } -- cgit v1.2.3 From 15a7f88e083a78a4219cbd004e6041b49c490b05 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 14 Oct 2020 11:44:11 -0600 Subject: Automatically clean activity log database --- .../Localization/Core/en-US.json | 2 + .../ScheduledTasks/Tasks/CleanActivityLogTask.cs | 68 ++++++++++++++++++++++ .../Activity/ActivityManager.cs | 12 ++++ MediaBrowser.Model/Activity/IActivityManager.cs | 7 +++ 4 files changed, 89 insertions(+) create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs (limited to 'Jellyfin.Server.Implementations/Activity/ActivityManager.cs') diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index 92c54fb0e6..bc973c9737 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -95,6 +95,8 @@ "TasksLibraryCategory": "Library", "TasksApplicationCategory": "Application", "TasksChannelsCategory": "Internet Channels", + "TaskCleanActivityLog": "Clean Activity Log", + "TaskCleanActivityLogDescription": "Deletes activity log entries older then the configured age.", "TaskCleanCache": "Clean Cache Directory", "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.", "TaskRefreshChapterImages": "Extract Chapter Images", diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs new file mode 100644 index 0000000000..50bc091c83 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks +{ + /// + /// Deletes old activity log entries. + /// + public class CleanActivityLogTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly ILocalizationManager _localization; + private readonly IActivityManager _activityManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public CleanActivityLogTask( + ILocalizationManager localization, + IActivityManager activityManager) + { + _localization = localization; + _activityManager = activityManager; + } + + /// + public string Name => _localization.GetLocalizedString("TaskCleanActivityLog"); + + /// + public string Key => "CleanActivityLog"; + + /// + public string Description => _localization.GetLocalizedString("TaskCleanActivityLogDescription"); + + /// + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// + public bool IsHidden => false; + + /// + public bool IsEnabled => true; + + /// + public bool IsLogged => true; + + /// + public Task Execute(CancellationToken cancellationToken, IProgress progress) + { + // TODO allow configure + var startDate = DateTime.UtcNow.AddDays(-30); + return _activityManager.CleanAsync(startDate); + } + + /// + public IEnumerable GetDefaultTriggers() + { + return Enumerable.Empty(); + } + } +} \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 5926abfe0d..7bde4f35be 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -72,6 +72,18 @@ namespace Jellyfin.Server.Implementations.Activity }; } + /// + public async Task CleanAsync(DateTime startDate) + { + await using var dbContext = _provider.CreateContext(); + var entries = dbContext.ActivityLogs + .AsQueryable() + .Where(entry => entry.DateCreated <= startDate); + + dbContext.RemoveRange(entries); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + private static ActivityLogEntry ConvertToOldModel(ActivityLog entry) { return new ActivityLogEntry diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs index 3e4ea208ed..28073fb8d7 100644 --- a/MediaBrowser.Model/Activity/IActivityManager.cs +++ b/MediaBrowser.Model/Activity/IActivityManager.cs @@ -16,5 +16,12 @@ namespace MediaBrowser.Model.Activity Task CreateAsync(ActivityLog entry); Task> GetPagedResultAsync(ActivityLogQuery query); + + /// + /// Remove all activity logs before the specified date. + /// + /// Activity log start date. + /// A representing the asynchronous operation. + Task CleanAsync(DateTime startDate); } } -- cgit v1.2.3