diff --git a/Jellyfin.Plugin.MediaCleaner/Controllers/StateController.cs b/Jellyfin.Plugin.MediaCleaner/Controllers/StateController.cs index c66c6be..5e3833b 100644 --- a/Jellyfin.Plugin.MediaCleaner/Controllers/StateController.cs +++ b/Jellyfin.Plugin.MediaCleaner/Controllers/StateController.cs @@ -1,22 +1,32 @@ using Jellyfin.Plugin.MediaCleaner.Data; +using Jellyfin.Plugin.MediaCleaner; using Jellyfin.Plugin.MediaCleaner.Models; using Microsoft.AspNetCore.Mvc; +using Jellyfin.Plugin.MediaCleaner.Configuration; namespace Jellyfin.Plugin.MediaCleaner.Controllers; [Route("mediacleaner/state")] -public class StateController : Controller +public class StateController(MediaCleanerState state) : Controller { - private readonly PluginState _state; - public StateController(PluginState state) => _state = state; + private readonly MediaCleanerState _state = state; + private static PluginConfiguration Configuration => + Plugin.Instance!.Configuration; - [HttpGet] - public IActionResult Get() => Ok(_state.GetSeriesInfo()); + [HttpGet("getSeriesInfo")] + public IActionResult GetSeriesInfo() => Ok(_state.GetSeriesInfo()); - [HttpPost("add")] - public IActionResult AddSeriesInfo([FromBody] SeriesInfo seriesInfo) - { - _state.AddSeriesInfo(seriesInfo); - return Ok(); - } + [HttpGet("getMovieInfo")] + public IActionResult GetMovieInfo() => Ok(_state.GetMovieInfo()); + + [HttpGet("updateState")] + public IActionResult GetUpdateState() => Ok(_state.UpdateState()); + + [HttpGet("getMoviesTitle")] + public IActionResult GetMoviesTitle() => + Ok($"Stale Movies (Unwatched for and created over {Configuration.StaleMediaCutoff} Days ago.)"); + + [HttpGet("getSeriesTitle")] + public IActionResult GetSeriesTitle() => + Ok($"Stale Series (Unwatched for and created over {Configuration.StaleMediaCutoff} Days ago.)"); } diff --git a/Jellyfin.Plugin.MediaCleaner/Data/MediaCleanerState.cs b/Jellyfin.Plugin.MediaCleaner/Data/MediaCleanerState.cs new file mode 100644 index 0000000..7fa61db --- /dev/null +++ b/Jellyfin.Plugin.MediaCleaner/Data/MediaCleanerState.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.ModelConfiguration; +using Jellyfin.Plugin.MediaCleaner; +using Jellyfin.Plugin.MediaCleaner.Models; +using Jellyfin.Plugin.MediaCleaner.ScheduledTasks; +using MediaBrowser.Controller.Library; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Plugin.MediaCleaner.Data; + +public class MediaCleanerState +{ + private readonly Lock _lock = new(); + private IEnumerable _mediaInfo = []; + private ILogger _logger; + private readonly StaleMediaScanner _staleMediaScanner; + + public MediaCleanerState(ILogger logger, ILibraryManager libraryManager) + { + _logger = logger; + _staleMediaScanner = new(logger, libraryManager); + } + + public async Task UpdateState() + { + _mediaInfo = await _staleMediaScanner.ScanStaleMedia().ConfigureAwait(false); + } + + public IEnumerable GetSeriesInfo() + { + lock (_lock) + { + return _mediaInfo.OfType(); + } + } + + public IEnumerable GetMovieInfo() + { + lock (_lock) + { + return _mediaInfo.OfType(); + } + } +} diff --git a/Jellyfin.Plugin.MediaCleaner/Data/PluginState.cs b/Jellyfin.Plugin.MediaCleaner/Data/PluginState.cs deleted file mode 100644 index 53f038f..0000000 --- a/Jellyfin.Plugin.MediaCleaner/Data/PluginState.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using Jellyfin.Plugin.MediaCleaner.Models; - -namespace Jellyfin.Plugin.MediaCleaner.Data; - -public class PluginState -{ - private readonly object _lock = new(); - private List _seriesInfo = new List - { - new SeriesInfo { SeriesName = "TestName", Id = System.Guid.NewGuid() } - }; - - public void AddSeriesInfo(SeriesInfo seriesInfo) - { - lock (_lock) - { - _seriesInfo.Add(seriesInfo); - } - } - - public IEnumerable GetSeriesInfo() - { - lock (_lock) - { - return _seriesInfo; - } - } -} diff --git a/Jellyfin.Plugin.MediaCleaner/Models/MediaInfo.cs b/Jellyfin.Plugin.MediaCleaner/Models/MediaInfo.cs new file mode 100644 index 0000000..7ae7881 --- /dev/null +++ b/Jellyfin.Plugin.MediaCleaner/Models/MediaInfo.cs @@ -0,0 +1,9 @@ +using System; + +namespace Jellyfin.Plugin.MediaCleaner.Models; + +public abstract class MediaInfo +{ + public required Guid Id { get; set; } + public required string Name { get; set; } +} diff --git a/Jellyfin.Plugin.MediaCleaner/Models/MovieInfo.cs b/Jellyfin.Plugin.MediaCleaner/Models/MovieInfo.cs new file mode 100644 index 0000000..c70717f --- /dev/null +++ b/Jellyfin.Plugin.MediaCleaner/Models/MovieInfo.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Jellyfin.Plugin.MediaCleaner; + +namespace Jellyfin.Plugin.MediaCleaner.Models; + +/// +/// Contains Movie information. +/// +public class MovieInfo : MediaInfo +{ + +} diff --git a/Jellyfin.Plugin.MediaCleaner/Models/SeriesInfo.cs b/Jellyfin.Plugin.MediaCleaner/Models/SeriesInfo.cs index 59ff805..1a49baa 100644 --- a/Jellyfin.Plugin.MediaCleaner/Models/SeriesInfo.cs +++ b/Jellyfin.Plugin.MediaCleaner/Models/SeriesInfo.cs @@ -1,26 +1,14 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using Jellyfin.Plugin.MediaCleaner; namespace Jellyfin.Plugin.MediaCleaner.Models; /// /// Contains series information. /// -public class SeriesInfo +public class SeriesInfo : MediaInfo { - /// - /// Gets or sets series identifier. - /// - public Guid Id { get; set; } - - /// - /// Gets or sets series name. - /// - public string SeriesName { get; set; } = string.Empty; - - /// - /// Gets or sets seasons. - /// public IEnumerable Seasons { get; set; } = []; } diff --git a/Jellyfin.Plugin.MediaCleaner/Pages/home.html b/Jellyfin.Plugin.MediaCleaner/Pages/home.html index e5971e0..3e8b22e 100644 --- a/Jellyfin.Plugin.MediaCleaner/Pages/home.html +++ b/Jellyfin.Plugin.MediaCleaner/Pages/home.html @@ -3,18 +3,29 @@

Media Cleaner

+ +

+ + + + + + +
Name
+
+
+ +

- - - - - - - + + + + + +
IDSeries Name
NameSeasons
diff --git a/Jellyfin.Plugin.MediaCleaner/Pages/media_cleaner_table.js b/Jellyfin.Plugin.MediaCleaner/Pages/media_cleaner_table.js index a56f7ba..25caa16 100644 --- a/Jellyfin.Plugin.MediaCleaner/Pages/media_cleaner_table.js +++ b/Jellyfin.Plugin.MediaCleaner/Pages/media_cleaner_table.js @@ -1,9 +1,10 @@ -var table = document.getElementById("seriesTable"); +var moviesTitle = document.getElementById("moviesTitle"); +var seriesTitle = document.getElementById("seriesTitle"); +var moviesTable = document.getElementById("moviesTable"); +var seriesTable = document.getElementById("seriesTable"); - - -const getMediaCleanerState = async () => { - const response = await fetch('/mediacleaner/state'); +const getMediaCleanerSeriesInfo = async () => { + const response = await fetch("/mediacleaner/state/getSeriesInfo"); if(!response.ok){ throw new Error(`Response status: ${response.status}`) @@ -12,16 +13,73 @@ const getMediaCleanerState = async () => { return response.json(); } -var state = await getMediaCleanerState(); +const getMediaCleanerMovieInfo = async () => { + const response = await fetch("/mediacleaner/state/getMovieInfo"); -console.log("State: ", state); + if(!response.ok){ + throw new Error(`Response status: ${response.status}`) + } -for(let i = 0; i < state.length; i++){ - var row = table.insertRow(-1); - var cell1 = row.insertCell(0); - var cell2 = row.insertCell(1); - var cell3 = row.insertCell(2); - cell1.innerHTML = state[i].Id; - cell2.innerHTML = state[i].SeriesName; - cell3.innerHTML = state[i].Seasons.length; + return response.json(); } + +const updateMediaCleanerState = async () => { + const response = await fetch("/mediacleaner/state/updateState"); + + if(!response.ok){ + throw new Error(`Response status: ${response.status}`) + } + + return response.json(); +} + +const getMediaCleanerSeriesTitle = async () => { + const response = await fetch("/mediacleaner/state/getSeriesTitle"); + + if(!response.ok){ + throw new Error(`Response status: ${response.status}`); + } + + return response.json(); +} + +const getMediaCleanerMoviesTitle = async () => { + const response = await fetch("/mediacleaner/state/getMoviesTitle"); + + if(!response.ok){ + throw new Error(`Response status: ${response.status}`); + } + + return response.json(); +} + +const populateMoviesTable = () => { + for(let i = 0; i < moviesInfo.length; i++){ + var row = moviesTable.insertRow(-1); + var cell1 = row.insertCell(0); + var cell2 = row.insertCell(1); + cell1.innerHTML = moviesInfo[i].Name; + cell2.innerHTML = ""; + } +} + +const populateSeriesTable = () => { + for(let i = 0; i < seriesInfo.length; i++){ + var row = seriesTable.insertRow(-1); + var cell1 = row.insertCell(0); + var cell2 = row.insertCell(1); + var cell3 = row.insertCell(2); + cell1.innerHTML = seriesInfo[i].Name; + cell2.innerHTML = seriesInfo[i].Seasons.map(season => season.replace("Season ", "")).join(", "); + cell3.innerHTML = ""; + } +} + +moviesTitle.innerHTML = await getMediaCleanerMoviesTitle(); +seriesTitle.innerHTML = await getMediaCleanerSeriesTitle(); + +await updateMediaCleanerState(); +var moviesInfo = await getMediaCleanerMovieInfo(); +var seriesInfo = await getMediaCleanerSeriesInfo(); +populateMoviesTable(); +populateSeriesTable(); diff --git a/Jellyfin.Plugin.MediaCleaner/PluginServiceRegistrator.cs b/Jellyfin.Plugin.MediaCleaner/PluginServiceRegistrator.cs index e5b2db9..079f689 100644 --- a/Jellyfin.Plugin.MediaCleaner/PluginServiceRegistrator.cs +++ b/Jellyfin.Plugin.MediaCleaner/PluginServiceRegistrator.cs @@ -8,6 +8,6 @@ public class PluginServiceRegistrator : IPluginServiceRegistrator { public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost) { - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); } } diff --git a/Jellyfin.Plugin.MediaCleaner/ScheduledTasks/StaleMediaTask.cs b/Jellyfin.Plugin.MediaCleaner/StaleMediaScanner.cs similarity index 83% rename from Jellyfin.Plugin.MediaCleaner/ScheduledTasks/StaleMediaTask.cs rename to Jellyfin.Plugin.MediaCleaner/StaleMediaScanner.cs index 13a8c9d..fde16f9 100644 --- a/Jellyfin.Plugin.MediaCleaner/ScheduledTasks/StaleMediaTask.cs +++ b/Jellyfin.Plugin.MediaCleaner/StaleMediaScanner.cs @@ -14,18 +14,20 @@ using Jellyfin.Database.Implementations.Entities; using Jellyfin.Database.Implementations.Entities.Libraries; using Jellyfin.Plugin.MediaCleaner.Configuration; using Jellyfin.Plugin.MediaCleaner.Helpers; +using Jellyfin.Plugin.MediaCleaner; using Jellyfin.Plugin.MediaCleaner.Models; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace Jellyfin.Plugin.MediaCleaner.ScheduledTasks; +namespace Jellyfin.Plugin.MediaCleaner; /// /// A task to scan media for stale files. /// -public sealed class StaleMediaTask : IScheduledTask +public sealed class StaleMediaScanner { private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; @@ -38,7 +40,7 @@ public sealed class StaleMediaTask : IScheduledTask /// /// Logger for StaleMediaTask. /// Accesses jellyfin's library manager for media. - public StaleMediaTask(ILogger logger, ILibraryManager libraryManager) + public StaleMediaScanner(ILogger logger, ILibraryManager libraryManager) { _logger = logger; _libraryManager = libraryManager; @@ -47,18 +49,7 @@ public sealed class StaleMediaTask : IScheduledTask _seriesHelper = new SeriesHelper(_logger); } - private static PluginConfiguration Configuration => - Plugin.Instance!.Configuration; - - string IScheduledTask.Name => "Scan Stale Media"; - - string IScheduledTask.Key => "Stale Media"; - - string IScheduledTask.Description => "Scan Stale Media"; - - string IScheduledTask.Category => "Media"; - - Task IScheduledTask.ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + public async Task> ScanStaleMedia() { _loggingHelper.LogDebugInformation("--DEBUG MODE ACTIVE--"); _loggingHelper.LogInformation("-------------------------------------------------"); @@ -98,13 +89,15 @@ public sealed class StaleMediaTask : IScheduledTask _loggingHelper.LogInformation("-------------------------------------------------"); _loggingHelper.LogInformation("Stale seasons found: {StaleSeasons}", staleSeasons.Count); + IEnumerable staleSeriesInfo = []; + if (staleSeasons.Count > 0) { - IEnumerable staleSeriesInfo = FindSeriesInfo(staleSeasons); + staleSeriesInfo = FindSeriesInfo(staleSeasons); - foreach (var seriesInfo in staleSeriesInfo) + foreach (SeriesInfo seriesInfo in staleSeriesInfo.Cast()) { - _loggingHelper.LogInformation("Series Info: ID: {Id} | Series Name: {SeriesName} | Stale Seasons: {Seasons}", [seriesInfo.Id, seriesInfo.SeriesName, string.Join(", ", seriesInfo.Seasons)]); + _loggingHelper.LogInformation("Series Info: ID: {Id} | Series Name: {SeriesName} | Stale Seasons: {Seasons}", [seriesInfo.Id, seriesInfo.Name, string.Join(", ", seriesInfo.Seasons)]); } } else @@ -115,9 +108,17 @@ public sealed class StaleMediaTask : IScheduledTask _loggingHelper.LogInformation("-------------------------------------------------"); _loggingHelper.LogInformation("Stale Movies found: {StaleMovies}", staleMovies.Count); + IEnumerable staleMoviesInfo = []; + if (staleMovies.Count > 0) { - foreach (var movieInfo in staleMovies) + staleMoviesInfo = staleMovies.Select(movie => new MovieInfo + { + Id = movie.Id, + Name = movie.Name + }); + + foreach (MovieInfo movieInfo in staleMoviesInfo.Cast()) { _loggingHelper.LogInformation("Movie Info: ID: {Id} | Movie Name: {MovieName}", [movieInfo.Id, movieInfo.Name]); } @@ -131,7 +132,9 @@ public sealed class StaleMediaTask : IScheduledTask _loggingHelper.LogInformation("Ending stale media scan..."); _loggingHelper.LogInformation("-------------------------------------------------"); - return Task.CompletedTask; + IEnumerable mediaInfo = staleSeriesInfo.Concat(staleMoviesInfo); + + return mediaInfo; } private List GetStaleMovies(List movies) @@ -198,7 +201,7 @@ public sealed class StaleMediaTask : IScheduledTask return staleSeasons; } - private IEnumerable FindSeriesInfo(IReadOnlyCollection seasons) + private IEnumerable FindSeriesInfo(IReadOnlyCollection seasons) { Guid[] seriesIds = [.. seasons.Select(season => season.ParentId).Distinct()]; @@ -212,22 +215,11 @@ public sealed class StaleMediaTask : IScheduledTask return new SeriesInfo { Id = series.Id, - SeriesName = series.Name, + Name = series.Name, Seasons = [.. seasons.Where(season => season.ParentId == series.Id).Select(season => season.Name)] }; }); return seriesInfoList; } - - IEnumerable IScheduledTask.GetDefaultTriggers() - { - // Run this task every 24 hours - // Unnecessary, and will be removed once front end page is ready. - yield return new TaskTriggerInfo - { - Type = TaskTriggerInfoType.IntervalTrigger, - IntervalTicks = TimeSpan.FromHours(24).Ticks - }; - } }