From fc7df0e11b912afdf4532db7804d5731d2d30f90 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 9 Jul 2021 15:16:57 +0100 Subject: [PATCH] Added the ability to select certain libraries in jellyfin --- src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs | 7 +- src/Ombi.Api.Jellyfin/JellyfinApi.cs | 20 ++-- .../Jobs/Jellyfin/JellyfinContentSync.cs | 103 ++++++++++++------ .../Jobs/Jellyfin/JellyfinEpisodeSync.cs | 21 +++- .../Settings/Models/External/EmbySettings.cs | 8 ++ .../Models/External/JellyfinSettings.cs | 9 ++ .../ClientApp/src/app/interfaces/ISettings.ts | 19 ++++ .../services/applications/jellyfin.service.ts | 7 +- .../settings/jellyfin/jellyfin.component.html | 35 ++++-- .../settings/jellyfin/jellyfin.component.ts | 30 ++++- .../app/wizard/jellyfin/jellyfin.component.ts | 6 +- 11 files changed, 198 insertions(+), 67 deletions(-) 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/JellyfinApi.cs b/src/Ombi.Api.Jellyfin/JellyfinApi.cs index 9ec6c8241..c16620710 100644 --- a/src/Ombi.Api.Jellyfin/JellyfinApi.cs +++ b/src/Ombi.Api.Jellyfin/JellyfinApi.cs @@ -97,19 +97,19 @@ namespace Ombi.Api.Jellyfin return response; } - public async Task> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri) + 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); + 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) @@ -152,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.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs index ff96e2130..0920eed42 100644 --- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs @@ -52,7 +52,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin { try { - await StartServerCache(server, jellyfinSettings); + await StartServerCache(server); } catch (Exception e) { @@ -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()) { - 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..4cbe7dca9 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()) + { + 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.Settings/Settings/Models/External/EmbySettings.cs b/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs index 5bd7cea93..2b165daf8 100644 --- a/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs @@ -17,5 +17,13 @@ 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 int Key { get; set; } + public string Title { get; set; } // Name is for display purposes + 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..95b5f9dab 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -64,6 +64,25 @@ 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 IJellyfinContainer { + items: T[]; + totalRecordCount: number; +} + +export interface IJellyfinLibrary { + name: string; + serverId: string; + id: string; + collectionType: string; } export interface IPublicInfo { 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..25ff15fde 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, IJellyfinContainer, 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/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/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: [] }); }