diff --git a/src/Ombi.Api.Emby/EmbyApi.cs b/src/Ombi.Api.Emby/EmbyApi.cs index 7cb702fbc..b24783e3c 100644 --- a/src/Ombi.Api.Emby/EmbyApi.cs +++ b/src/Ombi.Api.Emby/EmbyApi.cs @@ -1,9 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Internal; using Newtonsoft.Json; using Ombi.Api.Emby.Models; +using Ombi.Api.Emby.Models.Media; using Ombi.Api.Emby.Models.Media.Tv; using Ombi.Api.Emby.Models.Movie; using Ombi.Helpers; @@ -112,19 +115,30 @@ namespace Ombi.Api.Emby return await Api.Request>(request); } - public async Task> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri) + public async Task> GetLibraries(string apiKey, string baseUrl) { - return await GetAll("Movie", apiKey, userId, baseUri, true, startIndex, count); + var request = new Request("library/mediafolders", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + + var response = await Api.Request>(request); + response.Items = response.Items.Where(x => !x.CollectionType.Equals("playlists", StringComparison.InvariantCultureIgnoreCase)).ToList(); + return response; + } + + + public async Task> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri) + { + return await GetAll("Movie", apiKey, userId, baseUri, true, startIndex, count, parentIdFilder); } - public async Task> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri) + public async Task> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri) { - return await GetAll("Episode", apiKey, userId, baseUri, false, startIndex, count); + return await GetAll("Episode", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder); } - public async Task> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri) + public async Task> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri) { - return await GetAll("Series", apiKey, userId, baseUri, false, startIndex, count); + return await GetAll("Series", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder); } public async Task GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl) @@ -167,7 +181,7 @@ namespace Ombi.Api.Emby var obj = await Api.Request>(request); return obj; } - private async Task> GetAll(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count) + private async Task> GetAll(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count, string parentIdFilder = default) { var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get); @@ -176,6 +190,10 @@ namespace Ombi.Api.Emby request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds"); request.AddQueryString("startIndex", startIndex.ToString()); request.AddQueryString("limit", count.ToString()); + if (!string.IsNullOrEmpty(parentIdFilder)) + { + request.AddQueryString("ParentId", parentIdFilder); + } request.AddQueryString("IsVirtualItem", "False"); diff --git a/src/Ombi.Api.Emby/IBaseEmbyApi.cs b/src/Ombi.Api.Emby/IBaseEmbyApi.cs index 6f9a6bc7f..4de81073d 100644 --- a/src/Ombi.Api.Emby/IBaseEmbyApi.cs +++ b/src/Ombi.Api.Emby/IBaseEmbyApi.cs @@ -13,13 +13,13 @@ namespace Ombi.Api.Emby Task> GetUsers(string baseUri, string apiKey); Task LogIn(string username, string password, string apiKey, string baseUri); - Task> GetAllMovies(string apiKey, int startIndex, int count, string userId, + Task> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri); - Task> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, + Task> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri); - Task> GetAllShows(string apiKey, int startIndex, int count, string userId, + Task> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri); Task> GetCollection(string mediaId, diff --git a/src/Ombi.Api.Emby/IEmbyApi.cs b/src/Ombi.Api.Emby/IEmbyApi.cs index e7803116d..66c281de0 100644 --- a/src/Ombi.Api.Emby/IEmbyApi.cs +++ b/src/Ombi.Api.Emby/IEmbyApi.cs @@ -1,10 +1,12 @@ using System.Threading.Tasks; using Ombi.Api.Emby.Models; +using Ombi.Api.Emby.Models.Media; namespace Ombi.Api.Emby { public interface IEmbyApi : IBaseEmbyApi { Task LoginConnectUser(string username, string password); + Task> GetLibraries(string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Emby/Models/Media/MediaFolders.cs b/src/Ombi.Api.Emby/Models/Media/MediaFolders.cs new file mode 100644 index 000000000..fee589404 --- /dev/null +++ b/src/Ombi.Api.Emby/Models/Media/MediaFolders.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ombi.Api.Emby.Models.Media +{ + public class MediaFolders + { + public string Name { get; set; } + public string ServerId { get; set; } + public string Id { get; set; } + public string CollectionType { get; set; } + } +} diff --git a/src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs b/src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs index 7a9ee8a5a..4bb997cb5 100644 --- a/src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs +++ b/src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs @@ -13,13 +13,12 @@ namespace Ombi.Api.Jellyfin Task> GetUsers(string baseUri, string apiKey); Task LogIn(string username, string password, string apiKey, string baseUri); - Task> GetAllMovies(string apiKey, int startIndex, int count, string userId, - string baseUri); + Task> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri); - Task> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, + Task> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri); - Task> GetAllShows(string apiKey, int startIndex, int count, string userId, + Task> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri); Task> GetCollection(string mediaId, diff --git a/src/Ombi.Api.Jellyfin/IJellyfinApi.cs b/src/Ombi.Api.Jellyfin/IJellyfinApi.cs index 72d45877c..5dcf5741f 100644 --- a/src/Ombi.Api.Jellyfin/IJellyfinApi.cs +++ b/src/Ombi.Api.Jellyfin/IJellyfinApi.cs @@ -6,5 +6,6 @@ namespace Ombi.Api.Jellyfin public interface IJellyfinApi : IBaseJellyfinApi { Task LoginConnectUser(string username, string password); + Task> GetLibraries(string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Jellyfin/JellyfinApi.cs b/src/Ombi.Api.Jellyfin/JellyfinApi.cs index a8d94eca6..c16620710 100644 --- a/src/Ombi.Api.Jellyfin/JellyfinApi.cs +++ b/src/Ombi.Api.Jellyfin/JellyfinApi.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.Internal; using Newtonsoft.Json; using Ombi.Api.Jellyfin.Models; using Ombi.Api.Jellyfin.Models.Media.Tv; using Ombi.Api.Jellyfin.Models.Movie; -using Ombi.Helpers; namespace Ombi.Api.Jellyfin { @@ -87,19 +87,29 @@ namespace Ombi.Api.Jellyfin return await Api.Request>(request); } - public async Task> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri) + public async Task> GetLibraries(string apiKey, string baseUrl) { - return await GetAll("Movie", apiKey, userId, baseUri, true, startIndex, count); + var request = new Request("library/mediafolders", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + + var response = await Api.Request>(request); + response.Items = response.Items.Where(x => !x.CollectionType.Equals("playlists", StringComparison.InvariantCultureIgnoreCase)).ToList(); + return response; + } + + public async Task> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri) + { + return await GetAll("Movie", apiKey, userId, baseUri, true, startIndex, count, parentIdFilder); } - public async Task> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri) + public async Task> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri) { - return await GetAll("Episode", apiKey, userId, baseUri, false, startIndex, count); + return await GetAll("Episode", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder); } - public async Task> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri) + public async Task> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri) { - return await GetAll("Series", apiKey, userId, baseUri, false, startIndex, count); + return await GetAll("Series", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder); } public async Task GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl) @@ -142,15 +152,19 @@ namespace Ombi.Api.Jellyfin var obj = await Api.Request>(request); return obj; } - private async Task> GetAll(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count) + private async Task> GetAll(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count, string parentIdFilder = default) { var request = new Request($"users/{userId}/items", baseUri, HttpMethod.Get); request.AddQueryString("Recursive", true.ToString()); request.AddQueryString("IncludeItemTypes", type); - request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds"); + request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview,ParentId" : "ProviderIds,ParentId"); request.AddQueryString("startIndex", startIndex.ToString()); request.AddQueryString("limit", count.ToString()); + if(!string.IsNullOrEmpty(parentIdFilder)) + { + request.AddQueryString("ParentId", parentIdFilder); + } request.AddQueryString("IsVirtualItem", "False"); diff --git a/src/Ombi.Api.Jellyfin/Models/MediaFolders.cs b/src/Ombi.Api.Jellyfin/Models/MediaFolders.cs new file mode 100644 index 000000000..caa9b6813 --- /dev/null +++ b/src/Ombi.Api.Jellyfin/Models/MediaFolders.cs @@ -0,0 +1,10 @@ +namespace Ombi.Api.Jellyfin.Models +{ + public class MediaFolders + { + public string Name { get; set; } + public string ServerId { get; set; } + public string Id { get; set; } + public string CollectionType { get; set; } + } +} diff --git a/src/Ombi.Helpers/MediaCacheService.cs b/src/Ombi.Helpers/MediaCacheService.cs index 69ad8ec09..1cb969b60 100644 --- a/src/Ombi.Helpers/MediaCacheService.cs +++ b/src/Ombi.Helpers/MediaCacheService.cs @@ -51,6 +51,10 @@ namespace Ombi.Helpers public async Task Purge() { var keys = await _memoryCache.GetAsync>(CacheKey); + if (keys == null) + { + return; + } foreach (var key in keys) { base.Remove(key); diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs index 866216fe4..4d7e271d6 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs @@ -35,6 +35,7 @@ namespace Ombi.Schedule.Jobs.Emby private readonly IEmbyApiFactory _apiFactory; private readonly IEmbyContentRepository _repo; private readonly IHubContext _notification; + private IEmbyApi Api { get; set; } public async Task Execute(IJobExecutionContext job) @@ -78,47 +79,37 @@ namespace Ombi.Schedule.Jobs.Emby //await _repo.ExecuteSql("DELETE FROM EmbyEpisode"); //await _repo.ExecuteSql("DELETE FROM EmbyContent"); - var movies = await Api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); - var totalCount = movies.TotalRecordCount; - var processed = 1; - - var mediaToAdd = new HashSet(); - - while (processed < totalCount) + if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled)) { - foreach (var movie in movies.Items) - { - if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase)) - { - var movieInfo = - await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri); - foreach (var item in movieInfo.Items) - { - await ProcessMovies(item, mediaToAdd, server); - } + var movieLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies"); - processed++; - } - else - { - processed++; - // Regular movie - await ProcessMovies(movie, mediaToAdd, server); - } + foreach (var movieParentIdFilder in movieLibsToFilter) + { + _logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'"); + await ProcessMovies(server, movieParentIdFilder.Key); } - // Get the next batch - movies = await Api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri); - await _repo.AddRange(mediaToAdd); - mediaToAdd.Clear(); - + var tvLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows"); + foreach (var tvParentIdFilter in tvLibsToFilter) + { + _logger.LogInformation($"Scanning Lib '{tvParentIdFilter.Title}'"); + await ProcessTv(server, tvParentIdFilter.Key); + } } + else + { + await ProcessMovies(server); + await ProcessTv(server); + } + } - + private async Task ProcessTv(EmbyServers server, string parentId = default) + { // TV Time - var tv = await Api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); + var mediaToAdd = new HashSet(); + var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri); var totalTv = tv.TotalRecordCount; - processed = 1; + var processed = 1; while (processed < totalTv) { foreach (var tvShow in tv.Items) @@ -162,7 +153,7 @@ namespace Ombi.Schedule.Jobs.Emby } } // Get the next batch - tv = await Api.GetAllShows(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri); + tv = await Api.GetAllShows(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri); await _repo.AddRange(mediaToAdd); mediaToAdd.Clear(); } @@ -171,6 +162,43 @@ namespace Ombi.Schedule.Jobs.Emby await _repo.AddRange(mediaToAdd); } + private async Task ProcessMovies(EmbyServers server, string parentId = default) + { + var movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri); + var totalCount = movies.TotalRecordCount; + var processed = 1; + var mediaToAdd = new HashSet(); + while (processed < totalCount) + { + foreach (var movie in movies.Items) + { + if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase)) + { + var movieInfo = + await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri); + foreach (var item in movieInfo.Items) + { + await ProcessMovies(item, mediaToAdd, server); + } + + processed++; + } + else + { + processed++; + // Regular movie + await ProcessMovies(movie, mediaToAdd, server); + } + } + + // Get the next batch + movies = await Api.GetAllMovies(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri); + await _repo.AddRange(mediaToAdd); + mediaToAdd.Clear(); + + } + } + private async Task ProcessMovies(EmbyMovie movieInfo, ICollection content, EmbyServers server) { // Check if it exists diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs index 2cc03a16c..1a55da6c9 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs @@ -60,6 +60,7 @@ namespace Ombi.Schedule.Jobs.Emby private readonly ILogger _logger; private readonly IEmbyContentRepository _repo; private readonly IHubContext _notification; + private IEmbyApi Api { get; set; } @@ -72,7 +73,19 @@ namespace Ombi.Schedule.Jobs.Emby .SendAsync(NotificationHub.NotificationEvent, "Emby Episode Sync Started"); foreach (var server in settings.Servers) { - await CacheEpisodes(server); + if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled)) + { + var tvLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows"); + foreach (var tvParentIdFilter in tvLibsToFilter) + { + _logger.LogInformation($"Scanning Lib for episodes '{tvParentIdFilter.Title}'"); + await CacheEpisodes(server, tvParentIdFilter.Key); + } + } + else + { + await CacheEpisodes(server, string.Empty); + } } await _notification.Clients.Clients(NotificationHub.AdminConnectionIds) @@ -81,9 +94,9 @@ namespace Ombi.Schedule.Jobs.Emby await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System"); } - private async Task CacheEpisodes(EmbyServers server) + private async Task CacheEpisodes(EmbyServers server, string parentIdFilter) { - var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); + var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, 200, server.AdministratorId, server.FullUri); var total = allEpisodes.TotalRecordCount; var processed = 1; var epToAdd = new HashSet(); @@ -150,7 +163,7 @@ namespace Ombi.Schedule.Jobs.Emby await _repo.AddRange(epToAdd); epToAdd.Clear(); - allEpisodes = await Api.GetAllEpisodes(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri); + allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, processed, 200, server.AdministratorId, server.FullUri); } if (epToAdd.Any()) diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs index ff96e2130..c806a5e95 100644 --- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs @@ -35,6 +35,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin private readonly IJellyfinApiFactory _apiFactory; private readonly IJellyfinContentRepository _repo; private readonly IHubContext _notification; + private IJellyfinApi Api { get; set; } public async Task Execute(IJobExecutionContext job) @@ -52,7 +53,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin { try { - await StartServerCache(server, jellyfinSettings); + await StartServerCache(server); } catch (Exception e) { @@ -61,7 +62,6 @@ namespace Ombi.Schedule.Jobs.Jellyfin _logger.LogError(e, "Exception when caching Jellyfin for server {0}", server.Name); } } - await _notification.Clients.Clients(NotificationHub.AdminConnectionIds) .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Finished"); // Episodes @@ -70,55 +70,47 @@ namespace Ombi.Schedule.Jobs.Jellyfin } - private async Task StartServerCache(JellyfinServers server, JellyfinSettings settings) + private async Task StartServerCache(JellyfinServers server) { if (!ValidateSettings(server)) + { return; + } //await _repo.ExecuteSql("DELETE FROM JellyfinEpisode"); //await _repo.ExecuteSql("DELETE FROM JellyfinContent"); - var movies = await Api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); - var totalCount = movies.TotalRecordCount; - var processed = 1; - - var mediaToAdd = new HashSet(); - - while (processed < totalCount) + if (server.JellyfinSelectedLibraries.Any() && server.JellyfinSelectedLibraries.Any(x => x.Enabled)) { - foreach (var movie in movies.Items) - { - if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase)) - { - var movieInfo = - await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri); - foreach (var item in movieInfo.Items) - { - await ProcessMovies(item, mediaToAdd, server); - } + var movieLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies"); - processed++; - } - else - { - processed++; - // Regular movie - await ProcessMovies(movie, mediaToAdd, server); - } + foreach (var movieParentIdFilder in movieLibsToFilter) + { + _logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'"); + await ProcessMovies(server, movieParentIdFilder.Key); } - // Get the next batch - movies = await Api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri); - await _repo.AddRange(mediaToAdd); - mediaToAdd.Clear(); - + var tvLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows"); + foreach (var tvParentIdFilter in tvLibsToFilter) + { + _logger.LogInformation($"Scanning Lib '{tvParentIdFilter.Title}'"); + await ProcessTv(server, tvParentIdFilter.Key); + } } + else + { + await ProcessMovies(server); + await ProcessTv(server); + } + } - + private async Task ProcessTv(JellyfinServers server, string parentId = default) + { // TV Time - var tv = await Api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); + var mediaToAdd = new HashSet(); + var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri); var totalTv = tv.TotalRecordCount; - processed = 1; + var processed = 1; while (processed < totalTv) { foreach (var tvShow in tv.Items) @@ -162,13 +154,52 @@ namespace Ombi.Schedule.Jobs.Jellyfin } } // Get the next batch - tv = await Api.GetAllShows(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri); + tv = await Api.GetAllShows(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri); await _repo.AddRange(mediaToAdd); mediaToAdd.Clear(); } if (mediaToAdd.Any()) + { await _repo.AddRange(mediaToAdd); + } + } + + private async Task ProcessMovies(JellyfinServers server, string parentId = default) + { + var movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri); + var totalCount = movies.TotalRecordCount; + var processed = 1; + var mediaToAdd = new HashSet(); + while (processed < totalCount) + { + foreach (var movie in movies.Items) + { + if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase)) + { + var movieInfo = + await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri); + foreach (var item in movieInfo.Items) + { + await ProcessMovies(item, mediaToAdd, server); + } + + processed++; + } + else + { + processed++; + // Regular movie + await ProcessMovies(movie, mediaToAdd, server); + } + } + + // Get the next batch + movies = await Api.GetAllMovies(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri); + await _repo.AddRange(mediaToAdd); + mediaToAdd.Clear(); + + } } private async Task ProcessMovies(JellyfinMovie movieInfo, ICollection content, JellyfinServers server) diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs index 11c7ec7af..60f500d19 100644 --- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs @@ -72,7 +72,20 @@ namespace Ombi.Schedule.Jobs.Jellyfin .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Episode Sync Started"); foreach (var server in settings.Servers) { - await CacheEpisodes(server); + + if (server.JellyfinSelectedLibraries.Any() && server.JellyfinSelectedLibraries.Any(x => x.Enabled)) + { + var tvLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows"); + foreach (var tvParentIdFilter in tvLibsToFilter) + { + _logger.LogInformation($"Scanning Lib for episodes '{tvParentIdFilter.Title}'"); + await CacheEpisodes(server, tvParentIdFilter.Key); + } + } + else + { + await CacheEpisodes(server, string.Empty); + } } await _notification.Clients.Clients(NotificationHub.AdminConnectionIds) @@ -81,9 +94,9 @@ namespace Ombi.Schedule.Jobs.Jellyfin await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System"); } - private async Task CacheEpisodes(JellyfinServers server) + private async Task CacheEpisodes(JellyfinServers server, string parentIdFilter) { - var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); + var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, 200, server.AdministratorId, server.FullUri); var total = allEpisodes.TotalRecordCount; var processed = 1; var epToAdd = new HashSet(); @@ -150,7 +163,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin await _repo.AddRange(epToAdd); epToAdd.Clear(); - allEpisodes = await Api.GetAllEpisodes(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri); + allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, processed, 200, server.AdministratorId, server.FullUri); } if (epToAdd.Any()) diff --git a/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs b/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs index d1b2d3f99..46f3a7e56 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs @@ -16,21 +16,26 @@ namespace Ombi.Schedule.Jobs.Ombi public class MediaDatabaseRefresh : IMediaDatabaseRefresh { public MediaDatabaseRefresh(ISettingsService s, ILogger log, - IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo) + IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo, + ISettingsService embySettings, ISettingsService jellyfinSettings) { - _settings = s; + _plexSettings = s; _log = log; _plexRepo = plexRepo; _embyRepo = embyRepo; _jellyfinRepo = jellyfinRepo; - _settings.ClearCache(); + _embySettings = embySettings; + _jellyfinSettings = jellyfinSettings; + _plexSettings.ClearCache(); } - private readonly ISettingsService _settings; + private readonly ISettingsService _plexSettings; private readonly ILogger _log; private readonly IPlexContentRepository _plexRepo; private readonly IEmbyContentRepository _embyRepo; private readonly IJellyfinContentRepository _jellyfinRepo; + private readonly ISettingsService _embySettings; + private readonly ISettingsService _jellyfinSettings; public async Task Execute(IJobExecutionContext job) { @@ -51,7 +56,7 @@ namespace Ombi.Schedule.Jobs.Ombi { try { - var s = await _settings.GetSettingsAsync(); + var s = await _embySettings.GetSettingsAsync(); if (!s.Enable) { return; @@ -73,7 +78,7 @@ namespace Ombi.Schedule.Jobs.Ombi { try { - var s = await _settings.GetSettingsAsync(); + var s = await _jellyfinSettings.GetSettingsAsync(); if (!s.Enable) { return; @@ -95,7 +100,7 @@ namespace Ombi.Schedule.Jobs.Ombi { try { - var s = await _settings.GetSettingsAsync(); + var s = await _plexSettings.GetSettingsAsync(); if (!s.Enable) { return; diff --git a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs index 65c85b586..a24d07da0 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs @@ -30,7 +30,7 @@ namespace Ombi.Schedule.Jobs.Ombi IMovieDbApi movieApi, ISettingsService embySettings, IEmbyApiFactory embyApi, ISettingsService jellyfinSettings, IJellyfinApiFactory jellyfinApi, - IHubContext notification) + IHubContext notification, IMediaCacheService mediaCacheService) { _plexRepo = plexRepo; _embyRepo = embyRepo; @@ -44,6 +44,7 @@ namespace Ombi.Schedule.Jobs.Ombi _jellyfinSettings = jellyfinSettings; _jellyfinApiFactory = jellyfinApi; _notification = notification; + _mediaCacheService = mediaCacheService; } private readonly IPlexContentRepository _plexRepo; @@ -58,6 +59,8 @@ namespace Ombi.Schedule.Jobs.Ombi private readonly IEmbyApiFactory _embyApiFactory; private readonly IJellyfinApiFactory _jellyfinApiFactory; private readonly IHubContext _notification; + private readonly IMediaCacheService _mediaCacheService; + private IEmbyApi EmbyApi { get; set; } private IJellyfinApi JellyfinApi { get; set; } @@ -102,6 +105,8 @@ namespace Ombi.Schedule.Jobs.Ombi return; } + await _mediaCacheService.Purge(); + _log.LogInformation("Metadata refresh finished"); await _notification.Clients.Clients(NotificationHub.AdminConnectionIds) .SendAsync(NotificationHub.NotificationEvent, "Metadata Refresh Finished"); diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index 928baa0c3..dbad7ac84 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -52,8 +52,10 @@ namespace Ombi.Schedule.Jobs.Plex public class PlexContentSync : IPlexContentSync { private readonly IMovieDbApi _movieApi; + private readonly IMediaCacheService _mediaCacheService; + public PlexContentSync(ISettingsService plex, IPlexApi plexApi, ILogger logger, IPlexContentRepository repo, - IPlexEpisodeSync epsiodeSync, IHubContext hub, IMovieDbApi movieDbApi) + IPlexEpisodeSync epsiodeSync, IHubContext hub, IMovieDbApi movieDbApi, IMediaCacheService mediaCacheService) { Plex = plex; PlexApi = plexApi; @@ -62,6 +64,7 @@ namespace Ombi.Schedule.Jobs.Plex EpisodeSync = epsiodeSync; Notification = hub; _movieApi = movieDbApi; + _mediaCacheService = mediaCacheService; Plex.ClearCache(); } @@ -121,6 +124,7 @@ namespace Ombi.Schedule.Jobs.Plex { await NotifyClient("Plex Sync - Checking if any requests are now available"); Logger.LogInformation("Kicking off Plex Availability Checker"); + await _mediaCacheService.Purge(); await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex"); } var processedCont = processedContent?.Content?.Count() ?? 0; diff --git a/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs b/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs index 5bd7cea93..fe95a96d6 100644 --- a/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs @@ -17,5 +17,14 @@ namespace Ombi.Core.Settings.Models.External public string AdministratorId { get; set; } public string ServerHostname { get; set; } public bool EnableEpisodeSearching { get; set; } + public List EmbySelectedLibraries { get; set; } = new List(); + } + + public class EmbySelectedLibraries + { + public string Key { get; set; } + public string Title { get; set; } // Name is for display purposes + public string CollectionType { get; set; } + public bool Enabled { get; set; } } } diff --git a/src/Ombi.Settings/Settings/Models/External/JellyfinSettings.cs b/src/Ombi.Settings/Settings/Models/External/JellyfinSettings.cs index 3bee56848..99df8afe0 100644 --- a/src/Ombi.Settings/Settings/Models/External/JellyfinSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/JellyfinSettings.cs @@ -17,5 +17,14 @@ namespace Ombi.Core.Settings.Models.External public string AdministratorId { get; set; } public string ServerHostname { get; set; } public bool EnableEpisodeSearching { get; set; } + public List JellyfinSelectedLibraries { get; set; } = new List(); + } + + public class JellyfinSelectedLibraries + { + public string Key { get; set; } + public string Title { get; set; } // Name is for display purposes + public string CollectionType { get; set; } + public bool Enabled { get; set; } } } diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index 910f440f3..236dd12d6 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -45,6 +45,7 @@ export interface IEmbyServer extends IExternalSettings { administratorId: string; enableEpisodeSearching: boolean; serverHostname: string; + embySelectedLibraries: IEmbyLibrariesSettings[]; } export interface IPublicInfo { @@ -64,6 +65,37 @@ export interface IJellyfinServer extends IExternalSettings { administratorId: string; enableEpisodeSearching: boolean; serverHostname: string; + jellyfinSelectedLibraries: IJellyfinLibrariesSettings[]; +} +export interface IJellyfinLibrariesSettings { + key: string; + title: string; + enabled: boolean; + collectionType: string; +} +export interface IEmbyLibrariesSettings { + key: string; + title: string; + enabled: boolean; + collectionType: string; +} + +export interface IMediaServerMediaContainer { + items: T[]; + totalRecordCount: number; +} + +export interface IJellyfinLibrary { + name: string; + serverId: string; + id: string; + collectionType: string; +} +export interface IEmbyLibrary { + name: string; + serverId: string; + id: string; + collectionType: string; } export interface IPublicInfo { diff --git a/src/Ombi/ClientApp/src/app/services/applications/emby.service.ts b/src/Ombi/ClientApp/src/app/services/applications/emby.service.ts index 734568d95..6e7bdc555 100644 --- a/src/Ombi/ClientApp/src/app/services/applications/emby.service.ts +++ b/src/Ombi/ClientApp/src/app/services/applications/emby.service.ts @@ -5,7 +5,7 @@ import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; -import { IEmbyServer, IEmbySettings, IPublicInfo, IUsersModel } from "../../interfaces"; +import { IEmbyLibrary, IEmbyServer, IEmbySettings, IMediaServerMediaContainer, IPublicInfo, IUsersModel } from "../../interfaces"; @Injectable() export class EmbyService extends ServiceHelpers { @@ -20,9 +20,13 @@ export class EmbyService extends ServiceHelpers { public getUsers(): Observable { return this.http.get(`${this.url}users`, {headers: this.headers}); } - + public getPublicInfo(server: IEmbyServer): Observable { return this.http.post(`${this.url}info`, JSON.stringify(server), {headers: this.headers}); } + public getLibraries(settings: IEmbyServer): Observable> { + return this.http.post>(`${this.url}Library`, JSON.stringify(settings), {headers: this.headers}); + } + } diff --git a/src/Ombi/ClientApp/src/app/services/applications/jellyfin.service.ts b/src/Ombi/ClientApp/src/app/services/applications/jellyfin.service.ts index 1bd27ef99..19fc52883 100644 --- a/src/Ombi/ClientApp/src/app/services/applications/jellyfin.service.ts +++ b/src/Ombi/ClientApp/src/app/services/applications/jellyfin.service.ts @@ -5,7 +5,7 @@ import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; -import { IJellyfinServer, IJellyfinSettings, IPublicInfo, IUsersModel } from "../../interfaces"; +import { IEmbyServer, IMediaServerMediaContainer, IJellyfinLibrary, IJellyfinServer, IJellyfinSettings, IPublicInfo, IUsersModel } from "../../interfaces"; @Injectable() export class JellyfinService extends ServiceHelpers { @@ -20,9 +20,12 @@ export class JellyfinService extends ServiceHelpers { public getUsers(): Observable { return this.http.get(`${this.url}users`, {headers: this.headers}); } - + public getPublicInfo(server: IJellyfinServer): Observable { return this.http.post(`${this.url}info`, JSON.stringify(server), {headers: this.headers}); } + public getLibraries(settings: IJellyfinServer): Observable> { + return this.http.post>(`${this.url}Library`, JSON.stringify(settings), {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html index a079534f2..14aaada64 100644 --- a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html @@ -8,13 +8,11 @@
-
-
-
- Enable -
-
-
+
+
+ Enable + +
@@ -74,8 +72,29 @@ Current URL: "https://app.emby.media/#!/item/item.html?id=1 + +
+ Note: if nothing is selected, we will monitor all libraries +
+
+ +
+
+
+
+
+
+
+ {{lib.title}} +
+
+
+
-
@@ -87,7 +106,7 @@
-
+ diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts index c01b5636f..b50175f61 100644 --- a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit } from "@angular/core"; - -import { IEmbyServer, IEmbySettings } from "../../interfaces"; import { EmbyService, JobService, NotificationService, SettingsService, TesterService } from "../../services"; -import { MatTabChangeEvent } from "@angular/material/tabs"; +import { IEmbyLibrariesSettings, IEmbyServer, IEmbySettings } from "../../interfaces"; + import {FormControl} from '@angular/forms'; +import { MatTabChangeEvent } from "@angular/material/tabs"; @Component({ templateUrl: "./emby.component.html", @@ -100,4 +100,28 @@ export class EmbyComponent implements OnInit { } }); } + + public loadLibraries(server: IEmbyServer) { + if (server.ip == null) { + this.notificationService.error("Emby is not yet configured correctly"); + return; + } + this.embyService.getLibraries(server).subscribe(x => { + server.embySelectedLibraries = []; + if (x.totalRecordCount > 0) { + x.items.forEach((item) => { + const lib: IEmbyLibrariesSettings = { + key: item.id, + title: item.name, + enabled: false, + collectionType: item.collectionType + }; + server.embySelectedLibraries.push(lib); + }); + } else { + this.notificationService.error("Couldn't find any libraries"); + } + }, + err => { this.notificationService.error(err); }); + } } diff --git a/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.html b/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.html index ada4f5aa6..6b3ebfdbc 100644 --- a/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.html +++ b/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.html @@ -9,11 +9,11 @@
-
-
- Enable -
+
+ Enable +
+
@@ -75,7 +75,28 @@
- + +
+ Note: if nothing is selected, we will monitor all libraries +
+
+ +
+
+
+
+
+
+
+ {{lib.title}} +
+
+
+
@@ -87,10 +108,10 @@
- + - +
diff --git a/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.ts b/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.ts index fd65ba43c..346d23ac8 100644 --- a/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit } from "@angular/core"; - -import { IJellyfinServer, IJellyfinSettings } from "../../interfaces"; +import { IEmbyServer, IJellyfinLibrariesSettings, IJellyfinServer, IJellyfinSettings } from "../../interfaces"; import { JellyfinService, JobService, NotificationService, SettingsService, TesterService } from "../../services"; -import { MatTabChangeEvent } from "@angular/material/tabs"; + import {FormControl} from '@angular/forms'; +import { MatTabChangeEvent } from "@angular/material/tabs"; @Component({ templateUrl: "./jellyfin.component.html", @@ -101,4 +101,28 @@ export class JellyfinComponent implements OnInit { } }); } + + public loadLibraries(server: IJellyfinServer) { + if (server.ip == null) { + this.notificationService.error("Jellyfin is not yet configured correctly"); + return; + } + this.jellyfinService.getLibraries(server).subscribe(x => { + server.jellyfinSelectedLibraries = []; + if (x.totalRecordCount > 0) { + x.items.forEach((item) => { + const lib: IJellyfinLibrariesSettings = { + key: item.id, + title: item.name, + enabled: false, + collectionType: item.collectionType + }; + server.jellyfinSelectedLibraries.push(lib); + }); + } else { + this.notificationService.error("Couldn't find any libraries"); + } + }, + err => { this.notificationService.error(err); }); + } } diff --git a/src/Ombi/ClientApp/src/app/wizard/emby/emby.component.ts b/src/Ombi/ClientApp/src/app/wizard/emby/emby.component.ts index fb0c167a6..356a4c1e4 100644 --- a/src/Ombi/ClientApp/src/app/wizard/emby/emby.component.ts +++ b/src/Ombi/ClientApp/src/app/wizard/emby/emby.component.ts @@ -1,9 +1,8 @@ import { Component, OnInit } from "@angular/core"; import { EmbyService } from "../../services"; -import { NotificationService } from "../../services"; - import { IEmbySettings } from "../../interfaces"; +import { NotificationService } from "../../services"; @Component({ selector: "wizard-emby", @@ -35,7 +34,8 @@ export class EmbyComponent implements OnInit { ssl: false, subDir: "", serverHostname: "", - serverId: undefined + serverId: undefined, + embySelectedLibraries: [] }); } @@ -45,7 +45,7 @@ export class EmbyComponent implements OnInit { this.notificationService.error("Username or password was incorrect. Could not authenticate with Emby."); return; } - + this.notificationService.success("Done! Please press next"); }); } diff --git a/src/Ombi/ClientApp/src/app/wizard/jellyfin/jellyfin.component.ts b/src/Ombi/ClientApp/src/app/wizard/jellyfin/jellyfin.component.ts index f3779b6be..d1200360e 100644 --- a/src/Ombi/ClientApp/src/app/wizard/jellyfin/jellyfin.component.ts +++ b/src/Ombi/ClientApp/src/app/wizard/jellyfin/jellyfin.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit } from "@angular/core"; +import { IJellyfinSettings } from "../../interfaces"; import { JellyfinService } from "../../services"; import { NotificationService } from "../../services"; -import { IJellyfinSettings } from "../../interfaces"; - @Component({ selector: "wizard-jellyfin", templateUrl: "./jellyfin.component.html", @@ -35,7 +34,8 @@ export class JellyfinComponent implements OnInit { ssl: false, subDir: "", serverHostname: "", - serverId: undefined + serverId: undefined, + jellyfinSelectedLibraries: [] }); } diff --git a/src/Ombi/Controllers/V1/External/EmbyController.cs b/src/Ombi/Controllers/V1/External/EmbyController.cs index 29ad84eb2..faed4bc2b 100644 --- a/src/Ombi/Controllers/V1/External/EmbyController.cs +++ b/src/Ombi/Controllers/V1/External/EmbyController.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ombi.Api.Emby; using Ombi.Api.Emby.Models; -using Ombi.Api.Plex; +using Ombi.Api.Emby.Models.Media; using Ombi.Attributes; using Ombi.Core.Settings; using Ombi.Core.Settings.Models.External; @@ -92,5 +92,13 @@ namespace Ombi.Controllers.V1.External // Filter out any dupes return vm.DistinctBy(x => x.Id); } + + [HttpPost("Library")] + public async Task> GetLibaries([FromBody] EmbyServers server) + { + var client = await EmbyApi.CreateClient(); + var result = await client.GetLibraries(server.ApiKey, server.FullUri); + return result; + } } } diff --git a/src/Ombi/Controllers/V1/External/JellyfinController.cs b/src/Ombi/Controllers/V1/External/JellyfinController.cs index 27663a5b6..5e8b87414 100644 --- a/src/Ombi/Controllers/V1/External/JellyfinController.cs +++ b/src/Ombi/Controllers/V1/External/JellyfinController.cs @@ -66,6 +66,14 @@ namespace Ombi.Controllers.V1.External return result; } + [HttpPost("Library")] + public async Task> GetLibaries([FromBody] JellyfinServers server) + { + var client = await JellyfinApi.CreateClient(); + var result = await client.GetLibraries(server.ApiKey, server.FullUri); + return result; + } + /// /// Gets the jellyfin users. ///