Merge pull request #4252 from Ombi-app/feature/jellyfin-libs

Added the ability to monitor certain libs in Jellyfin and Emby
pull/4273/head v4.0.1446
Jamie 4 years ago committed by GitHub
commit 046bad66d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,9 +1,12 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Internal;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ombi.Api.Emby.Models; using Ombi.Api.Emby.Models;
using Ombi.Api.Emby.Models.Media;
using Ombi.Api.Emby.Models.Media.Tv; using Ombi.Api.Emby.Models.Media.Tv;
using Ombi.Api.Emby.Models.Movie; using Ombi.Api.Emby.Models.Movie;
using Ombi.Helpers; using Ombi.Helpers;
@ -112,19 +115,30 @@ namespace Ombi.Api.Emby
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request); return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
} }
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri) public async Task<EmbyItemContainer<MediaFolders>> GetLibraries(string apiKey, string baseUrl)
{ {
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count); var request = new Request("library/mediafolders", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
var response = await Api.Request<EmbyItemContainer<MediaFolders>>(request);
response.Items = response.Items.Where(x => !x.CollectionType.Equals("playlists", StringComparison.InvariantCultureIgnoreCase)).ToList();
return response;
}
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count, parentIdFilder);
} }
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri) public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{ {
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count); return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
} }
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri) public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{ {
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count); return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
} }
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl) public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
@ -167,7 +181,7 @@ namespace Ombi.Api.Emby
var obj = await Api.Request<EmbyItemContainer<T>>(request); var obj = await Api.Request<EmbyItemContainer<T>>(request);
return obj; return obj;
} }
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count) private async Task<EmbyItemContainer<T>> GetAll<T>(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); 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("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("startIndex", startIndex.ToString()); request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString()); request.AddQueryString("limit", count.ToString());
if (!string.IsNullOrEmpty(parentIdFilder))
{
request.AddQueryString("ParentId", parentIdFilder);
}
request.AddQueryString("IsVirtualItem", "False"); request.AddQueryString("IsVirtualItem", "False");

@ -13,13 +13,13 @@ namespace Ombi.Api.Emby
Task<List<EmbyUser>> GetUsers(string baseUri, string apiKey); Task<List<EmbyUser>> GetUsers(string baseUri, string apiKey);
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri); Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
string baseUri); string baseUri);
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
string baseUri); string baseUri);
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
string baseUri); string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId, Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId,

@ -1,10 +1,12 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Api.Emby.Models; using Ombi.Api.Emby.Models;
using Ombi.Api.Emby.Models.Media;
namespace Ombi.Api.Emby namespace Ombi.Api.Emby
{ {
public interface IEmbyApi : IBaseEmbyApi public interface IEmbyApi : IBaseEmbyApi
{ {
Task<EmbyConnectUser> LoginConnectUser(string username, string password); Task<EmbyConnectUser> LoginConnectUser(string username, string password);
Task<EmbyItemContainer<MediaFolders>> GetLibraries(string apiKey, string baseUrl);
} }
} }

@ -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; }
}
}

@ -13,13 +13,12 @@ namespace Ombi.Api.Jellyfin
Task<List<JellyfinUser>> GetUsers(string baseUri, string apiKey); Task<List<JellyfinUser>> GetUsers(string baseUri, string apiKey);
Task<JellyfinUser> LogIn(string username, string password, string apiKey, string baseUri); Task<JellyfinUser> LogIn(string username, string password, string apiKey, string baseUri);
Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
string baseUri);
Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
string baseUri); string baseUri);
Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
string baseUri); string baseUri);
Task<JellyfinItemContainer<JellyfinMovie>> GetCollection(string mediaId, Task<JellyfinItemContainer<JellyfinMovie>> GetCollection(string mediaId,

@ -6,5 +6,6 @@ namespace Ombi.Api.Jellyfin
public interface IJellyfinApi : IBaseJellyfinApi public interface IJellyfinApi : IBaseJellyfinApi
{ {
Task<JellyfinConnectUser> LoginConnectUser(string username, string password); Task<JellyfinConnectUser> LoginConnectUser(string username, string password);
Task<JellyfinItemContainer<MediaFolders>> GetLibraries(string apiKey, string baseUrl);
} }
} }

@ -1,12 +1,12 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Internal;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ombi.Api.Jellyfin.Models; using Ombi.Api.Jellyfin.Models;
using Ombi.Api.Jellyfin.Models.Media.Tv; using Ombi.Api.Jellyfin.Models.Media.Tv;
using Ombi.Api.Jellyfin.Models.Movie; using Ombi.Api.Jellyfin.Models.Movie;
using Ombi.Helpers;
namespace Ombi.Api.Jellyfin namespace Ombi.Api.Jellyfin
{ {
@ -87,19 +87,29 @@ namespace Ombi.Api.Jellyfin
return await Api.Request<JellyfinItemContainer<JellyfinMovie>>(request); return await Api.Request<JellyfinItemContainer<JellyfinMovie>>(request);
} }
public async Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri) public async Task<JellyfinItemContainer<MediaFolders>> GetLibraries(string apiKey, string baseUrl)
{ {
return await GetAll<JellyfinMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count); var request = new Request("library/mediafolders", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
var response = await Api.Request<JellyfinItemContainer<MediaFolders>>(request);
response.Items = response.Items.Where(x => !x.CollectionType.Equals("playlists", StringComparison.InvariantCultureIgnoreCase)).ToList();
return response;
}
public async Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<JellyfinMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count, parentIdFilder);
} }
public async Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri) public async Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{ {
return await GetAll<JellyfinEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count); return await GetAll<JellyfinEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
} }
public async Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri) public async Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{ {
return await GetAll<JellyfinSeries>("Series", apiKey, userId, baseUri, false, startIndex, count); return await GetAll<JellyfinSeries>("Series", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
} }
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl) public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
@ -142,15 +152,19 @@ namespace Ombi.Api.Jellyfin
var obj = await Api.Request<JellyfinItemContainer<T>>(request); var obj = await Api.Request<JellyfinItemContainer<T>>(request);
return obj; return obj;
} }
private async Task<JellyfinItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count) private async Task<JellyfinItemContainer<T>> GetAll<T>(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); var request = new Request($"users/{userId}/items", baseUri, HttpMethod.Get);
request.AddQueryString("Recursive", true.ToString()); request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type); 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("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString()); request.AddQueryString("limit", count.ToString());
if(!string.IsNullOrEmpty(parentIdFilder))
{
request.AddQueryString("ParentId", parentIdFilder);
}
request.AddQueryString("IsVirtualItem", "False"); request.AddQueryString("IsVirtualItem", "False");

@ -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; }
}
}

@ -51,6 +51,10 @@ namespace Ombi.Helpers
public async Task Purge() public async Task Purge()
{ {
var keys = await _memoryCache.GetAsync<List<string>>(CacheKey); var keys = await _memoryCache.GetAsync<List<string>>(CacheKey);
if (keys == null)
{
return;
}
foreach (var key in keys) foreach (var key in keys)
{ {
base.Remove(key); base.Remove(key);

@ -35,6 +35,7 @@ namespace Ombi.Schedule.Jobs.Emby
private readonly IEmbyApiFactory _apiFactory; private readonly IEmbyApiFactory _apiFactory;
private readonly IEmbyContentRepository _repo; private readonly IEmbyContentRepository _repo;
private readonly IHubContext<NotificationHub> _notification; private readonly IHubContext<NotificationHub> _notification;
private IEmbyApi Api { get; set; } private IEmbyApi Api { get; set; }
public async Task Execute(IJobExecutionContext job) 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 EmbyEpisode");
//await _repo.ExecuteSql("DELETE FROM EmbyContent"); //await _repo.ExecuteSql("DELETE FROM EmbyContent");
var movies = await Api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled))
var totalCount = movies.TotalRecordCount;
var processed = 1;
var mediaToAdd = new HashSet<EmbyContent>();
while (processed < totalCount)
{
foreach (var movie in movies.Items)
{ {
if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase)) var movieLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");
{
var movieInfo = foreach (var movieParentIdFilder in movieLibsToFilter)
await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
foreach (var item in movieInfo.Items)
{ {
await ProcessMovies(item, mediaToAdd, server); _logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'");
await ProcessMovies(server, movieParentIdFilder.Key);
} }
processed++; 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 else
{ {
processed++; await ProcessMovies(server);
// Regular movie await ProcessTv(server);
await ProcessMovies(movie, mediaToAdd, server);
}
} }
// Get the next batch
movies = await Api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
} }
private async Task ProcessTv(EmbyServers server, string parentId = default)
{
// TV Time // TV Time
var tv = await Api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); var mediaToAdd = new HashSet<EmbyContent>();
var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
var totalTv = tv.TotalRecordCount; var totalTv = tv.TotalRecordCount;
processed = 1; var processed = 1;
while (processed < totalTv) while (processed < totalTv)
{ {
foreach (var tvShow in tv.Items) foreach (var tvShow in tv.Items)
@ -162,7 +153,7 @@ namespace Ombi.Schedule.Jobs.Emby
} }
} }
// Get the next batch // 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); await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear(); mediaToAdd.Clear();
} }
@ -171,6 +162,43 @@ namespace Ombi.Schedule.Jobs.Emby
await _repo.AddRange(mediaToAdd); 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<EmbyContent>();
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<EmbyContent> content, EmbyServers server) private async Task ProcessMovies(EmbyMovie movieInfo, ICollection<EmbyContent> content, EmbyServers server)
{ {
// Check if it exists // Check if it exists

@ -60,6 +60,7 @@ namespace Ombi.Schedule.Jobs.Emby
private readonly ILogger<EmbyEpisodeSync> _logger; private readonly ILogger<EmbyEpisodeSync> _logger;
private readonly IEmbyContentRepository _repo; private readonly IEmbyContentRepository _repo;
private readonly IHubContext<NotificationHub> _notification; private readonly IHubContext<NotificationHub> _notification;
private IEmbyApi Api { get; set; } private IEmbyApi Api { get; set; }
@ -72,7 +73,19 @@ namespace Ombi.Schedule.Jobs.Emby
.SendAsync(NotificationHub.NotificationEvent, "Emby Episode Sync Started"); .SendAsync(NotificationHub.NotificationEvent, "Emby Episode Sync Started");
foreach (var server in settings.Servers) 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) await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
@ -81,9 +94,9 @@ namespace Ombi.Schedule.Jobs.Emby
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System"); 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 total = allEpisodes.TotalRecordCount;
var processed = 1; var processed = 1;
var epToAdd = new HashSet<EmbyEpisode>(); var epToAdd = new HashSet<EmbyEpisode>();
@ -150,7 +163,7 @@ namespace Ombi.Schedule.Jobs.Emby
await _repo.AddRange(epToAdd); await _repo.AddRange(epToAdd);
epToAdd.Clear(); 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()) if (epToAdd.Any())

@ -35,6 +35,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
private readonly IJellyfinApiFactory _apiFactory; private readonly IJellyfinApiFactory _apiFactory;
private readonly IJellyfinContentRepository _repo; private readonly IJellyfinContentRepository _repo;
private readonly IHubContext<NotificationHub> _notification; private readonly IHubContext<NotificationHub> _notification;
private IJellyfinApi Api { get; set; } private IJellyfinApi Api { get; set; }
public async Task Execute(IJobExecutionContext job) public async Task Execute(IJobExecutionContext job)
@ -52,7 +53,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
{ {
try try
{ {
await StartServerCache(server, jellyfinSettings); await StartServerCache(server);
} }
catch (Exception e) catch (Exception e)
{ {
@ -61,7 +62,6 @@ namespace Ombi.Schedule.Jobs.Jellyfin
_logger.LogError(e, "Exception when caching Jellyfin for server {0}", server.Name); _logger.LogError(e, "Exception when caching Jellyfin for server {0}", server.Name);
} }
} }
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds) await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Finished"); .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Finished");
// Episodes // 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)) if (!ValidateSettings(server))
{
return; return;
}
//await _repo.ExecuteSql("DELETE FROM JellyfinEpisode"); //await _repo.ExecuteSql("DELETE FROM JellyfinEpisode");
//await _repo.ExecuteSql("DELETE FROM JellyfinContent"); //await _repo.ExecuteSql("DELETE FROM JellyfinContent");
var movies = await Api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); if (server.JellyfinSelectedLibraries.Any() && server.JellyfinSelectedLibraries.Any(x => x.Enabled))
var totalCount = movies.TotalRecordCount;
var processed = 1;
var mediaToAdd = new HashSet<JellyfinContent>();
while (processed < totalCount)
{
foreach (var movie in movies.Items)
{
if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase))
{ {
var movieInfo = var movieLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");
await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
foreach (var item in movieInfo.Items) foreach (var movieParentIdFilder in movieLibsToFilter)
{ {
await ProcessMovies(item, mediaToAdd, server); _logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'");
await ProcessMovies(server, movieParentIdFilder.Key);
} }
processed++; 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 else
{ {
processed++; await ProcessMovies(server);
// Regular movie await ProcessTv(server);
await ProcessMovies(movie, mediaToAdd, server);
} }
} }
// Get the next batch private async Task ProcessTv(JellyfinServers server, string parentId = default)
movies = await Api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri); {
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
// TV Time // TV Time
var tv = await Api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri); var mediaToAdd = new HashSet<JellyfinContent>();
var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
var totalTv = tv.TotalRecordCount; var totalTv = tv.TotalRecordCount;
processed = 1; var processed = 1;
while (processed < totalTv) while (processed < totalTv)
{ {
foreach (var tvShow in tv.Items) foreach (var tvShow in tv.Items)
@ -162,14 +154,53 @@ namespace Ombi.Schedule.Jobs.Jellyfin
} }
} }
// Get the next batch // 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); await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear(); mediaToAdd.Clear();
} }
if (mediaToAdd.Any()) if (mediaToAdd.Any())
{
await _repo.AddRange(mediaToAdd); 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<JellyfinContent>();
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<JellyfinContent> content, JellyfinServers server) private async Task ProcessMovies(JellyfinMovie movieInfo, ICollection<JellyfinContent> content, JellyfinServers server)
{ {

@ -72,7 +72,20 @@ namespace Ombi.Schedule.Jobs.Jellyfin
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Episode Sync Started"); .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Episode Sync Started");
foreach (var server in settings.Servers) 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) await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
@ -81,9 +94,9 @@ namespace Ombi.Schedule.Jobs.Jellyfin
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System"); 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 total = allEpisodes.TotalRecordCount;
var processed = 1; var processed = 1;
var epToAdd = new HashSet<JellyfinEpisode>(); var epToAdd = new HashSet<JellyfinEpisode>();
@ -150,7 +163,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
await _repo.AddRange(epToAdd); await _repo.AddRange(epToAdd);
epToAdd.Clear(); 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()) if (epToAdd.Any())

@ -16,21 +16,26 @@ namespace Ombi.Schedule.Jobs.Ombi
public class MediaDatabaseRefresh : IMediaDatabaseRefresh public class MediaDatabaseRefresh : IMediaDatabaseRefresh
{ {
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log, public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log,
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo) IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo,
ISettingsService<EmbySettings> embySettings, ISettingsService<JellyfinSettings> jellyfinSettings)
{ {
_settings = s; _plexSettings = s;
_log = log; _log = log;
_plexRepo = plexRepo; _plexRepo = plexRepo;
_embyRepo = embyRepo; _embyRepo = embyRepo;
_jellyfinRepo = jellyfinRepo; _jellyfinRepo = jellyfinRepo;
_settings.ClearCache(); _embySettings = embySettings;
_jellyfinSettings = jellyfinSettings;
_plexSettings.ClearCache();
} }
private readonly ISettingsService<PlexSettings> _settings; private readonly ISettingsService<PlexSettings> _plexSettings;
private readonly ILogger _log; private readonly ILogger _log;
private readonly IPlexContentRepository _plexRepo; private readonly IPlexContentRepository _plexRepo;
private readonly IEmbyContentRepository _embyRepo; private readonly IEmbyContentRepository _embyRepo;
private readonly IJellyfinContentRepository _jellyfinRepo; private readonly IJellyfinContentRepository _jellyfinRepo;
private readonly ISettingsService<EmbySettings> _embySettings;
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
public async Task Execute(IJobExecutionContext job) public async Task Execute(IJobExecutionContext job)
{ {
@ -51,7 +56,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
try try
{ {
var s = await _settings.GetSettingsAsync(); var s = await _embySettings.GetSettingsAsync();
if (!s.Enable) if (!s.Enable)
{ {
return; return;
@ -73,7 +78,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
try try
{ {
var s = await _settings.GetSettingsAsync(); var s = await _jellyfinSettings.GetSettingsAsync();
if (!s.Enable) if (!s.Enable)
{ {
return; return;
@ -95,7 +100,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
try try
{ {
var s = await _settings.GetSettingsAsync(); var s = await _plexSettings.GetSettingsAsync();
if (!s.Enable) if (!s.Enable)
{ {
return; return;

@ -30,7 +30,7 @@ namespace Ombi.Schedule.Jobs.Ombi
IMovieDbApi movieApi, IMovieDbApi movieApi,
ISettingsService<EmbySettings> embySettings, IEmbyApiFactory embyApi, ISettingsService<EmbySettings> embySettings, IEmbyApiFactory embyApi,
ISettingsService<JellyfinSettings> jellyfinSettings, IJellyfinApiFactory jellyfinApi, ISettingsService<JellyfinSettings> jellyfinSettings, IJellyfinApiFactory jellyfinApi,
IHubContext<NotificationHub> notification) IHubContext<NotificationHub> notification, IMediaCacheService mediaCacheService)
{ {
_plexRepo = plexRepo; _plexRepo = plexRepo;
_embyRepo = embyRepo; _embyRepo = embyRepo;
@ -44,6 +44,7 @@ namespace Ombi.Schedule.Jobs.Ombi
_jellyfinSettings = jellyfinSettings; _jellyfinSettings = jellyfinSettings;
_jellyfinApiFactory = jellyfinApi; _jellyfinApiFactory = jellyfinApi;
_notification = notification; _notification = notification;
_mediaCacheService = mediaCacheService;
} }
private readonly IPlexContentRepository _plexRepo; private readonly IPlexContentRepository _plexRepo;
@ -58,6 +59,8 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly IEmbyApiFactory _embyApiFactory; private readonly IEmbyApiFactory _embyApiFactory;
private readonly IJellyfinApiFactory _jellyfinApiFactory; private readonly IJellyfinApiFactory _jellyfinApiFactory;
private readonly IHubContext<NotificationHub> _notification; private readonly IHubContext<NotificationHub> _notification;
private readonly IMediaCacheService _mediaCacheService;
private IEmbyApi EmbyApi { get; set; } private IEmbyApi EmbyApi { get; set; }
private IJellyfinApi JellyfinApi { get; set; } private IJellyfinApi JellyfinApi { get; set; }
@ -102,6 +105,8 @@ namespace Ombi.Schedule.Jobs.Ombi
return; return;
} }
await _mediaCacheService.Purge();
_log.LogInformation("Metadata refresh finished"); _log.LogInformation("Metadata refresh finished");
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds) await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Metadata Refresh Finished"); .SendAsync(NotificationHub.NotificationEvent, "Metadata Refresh Finished");

@ -52,8 +52,10 @@ namespace Ombi.Schedule.Jobs.Plex
public class PlexContentSync : IPlexContentSync public class PlexContentSync : IPlexContentSync
{ {
private readonly IMovieDbApi _movieApi; private readonly IMovieDbApi _movieApi;
private readonly IMediaCacheService _mediaCacheService;
public PlexContentSync(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentSync> logger, IPlexContentRepository repo, public PlexContentSync(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentSync> logger, IPlexContentRepository repo,
IPlexEpisodeSync epsiodeSync, IHubContext<NotificationHub> hub, IMovieDbApi movieDbApi) IPlexEpisodeSync epsiodeSync, IHubContext<NotificationHub> hub, IMovieDbApi movieDbApi, IMediaCacheService mediaCacheService)
{ {
Plex = plex; Plex = plex;
PlexApi = plexApi; PlexApi = plexApi;
@ -62,6 +64,7 @@ namespace Ombi.Schedule.Jobs.Plex
EpisodeSync = epsiodeSync; EpisodeSync = epsiodeSync;
Notification = hub; Notification = hub;
_movieApi = movieDbApi; _movieApi = movieDbApi;
_mediaCacheService = mediaCacheService;
Plex.ClearCache(); Plex.ClearCache();
} }
@ -121,6 +124,7 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
await NotifyClient("Plex Sync - Checking if any requests are now available"); await NotifyClient("Plex Sync - Checking if any requests are now available");
Logger.LogInformation("Kicking off Plex Availability Checker"); Logger.LogInformation("Kicking off Plex Availability Checker");
await _mediaCacheService.Purge();
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex"); await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
} }
var processedCont = processedContent?.Content?.Count() ?? 0; var processedCont = processedContent?.Content?.Count() ?? 0;

@ -17,5 +17,14 @@ namespace Ombi.Core.Settings.Models.External
public string AdministratorId { get; set; } public string AdministratorId { get; set; }
public string ServerHostname { get; set; } public string ServerHostname { get; set; }
public bool EnableEpisodeSearching { get; set; } public bool EnableEpisodeSearching { get; set; }
public List<EmbySelectedLibraries> EmbySelectedLibraries { get; set; } = new List<EmbySelectedLibraries>();
}
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; }
} }
} }

@ -17,5 +17,14 @@ namespace Ombi.Core.Settings.Models.External
public string AdministratorId { get; set; } public string AdministratorId { get; set; }
public string ServerHostname { get; set; } public string ServerHostname { get; set; }
public bool EnableEpisodeSearching { get; set; } public bool EnableEpisodeSearching { get; set; }
public List<JellyfinSelectedLibraries> JellyfinSelectedLibraries { get; set; } = new List<JellyfinSelectedLibraries>();
}
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; }
} }
} }

@ -45,6 +45,7 @@ export interface IEmbyServer extends IExternalSettings {
administratorId: string; administratorId: string;
enableEpisodeSearching: boolean; enableEpisodeSearching: boolean;
serverHostname: string; serverHostname: string;
embySelectedLibraries: IEmbyLibrariesSettings[];
} }
export interface IPublicInfo { export interface IPublicInfo {
@ -64,6 +65,37 @@ export interface IJellyfinServer extends IExternalSettings {
administratorId: string; administratorId: string;
enableEpisodeSearching: boolean; enableEpisodeSearching: boolean;
serverHostname: string; 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<T> {
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 { export interface IPublicInfo {

@ -5,7 +5,7 @@ import { Observable } from "rxjs";
import { ServiceHelpers } from "../service.helpers"; import { ServiceHelpers } from "../service.helpers";
import { IEmbyServer, IEmbySettings, IPublicInfo, IUsersModel } from "../../interfaces"; import { IEmbyLibrary, IEmbyServer, IEmbySettings, IMediaServerMediaContainer, IPublicInfo, IUsersModel } from "../../interfaces";
@Injectable() @Injectable()
export class EmbyService extends ServiceHelpers { export class EmbyService extends ServiceHelpers {
@ -25,4 +25,8 @@ export class EmbyService extends ServiceHelpers {
return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers}); return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers});
} }
public getLibraries(settings: IEmbyServer): Observable<IMediaServerMediaContainer<IEmbyLibrary>> {
return this.http.post<IMediaServerMediaContainer<IEmbyLibrary>>(`${this.url}Library`, JSON.stringify(settings), {headers: this.headers});
}
} }

@ -5,7 +5,7 @@ import { Observable } from "rxjs";
import { ServiceHelpers } from "../service.helpers"; import { ServiceHelpers } from "../service.helpers";
import { IJellyfinServer, IJellyfinSettings, IPublicInfo, IUsersModel } from "../../interfaces"; import { IEmbyServer, IMediaServerMediaContainer, IJellyfinLibrary, IJellyfinServer, IJellyfinSettings, IPublicInfo, IUsersModel } from "../../interfaces";
@Injectable() @Injectable()
export class JellyfinService extends ServiceHelpers { export class JellyfinService extends ServiceHelpers {
@ -25,4 +25,7 @@ export class JellyfinService extends ServiceHelpers {
return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers}); return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers});
} }
public getLibraries(settings: IJellyfinServer): Observable<IMediaServerMediaContainer<IJellyfinLibrary>> {
return this.http.post<IMediaServerMediaContainer<IJellyfinLibrary>>(`${this.url}Library`, JSON.stringify(settings), {headers: this.headers});
}
} }

@ -9,12 +9,10 @@
<div class="row"> <div class="row">
<div class="col-md-6 col-6 col-sm-6"> <div class="col-md-6 col-6 col-sm-6">
<div style="float:right;text-align:left;">
<div class="md-form-field"> <div class="md-form-field">
<mat-slide-toggle [(ngModel)]="settings.enable" (change)="toggle()" [checked]="settings.enable">Enable</mat-slide-toggle> <mat-slide-toggle [(ngModel)]="settings.enable" [checked]="settings.enable">Enable
</div> </mat-slide-toggle>
</div> </div> </div>
</div>
</div> </div>
<mat-tab-group #tabGroup [selectedIndex]="selected.value" (selectedTabChange)="addTab($event)" (selectedIndexChange)="selected.setValue($event)" animationDuration="0ms" style="display:block;"> <mat-tab-group #tabGroup [selectedIndex]="selected.value" (selectedTabChange)="addTab($event)" (selectedIndexChange)="selected.setValue($event)" animationDuration="0ms" style="display:block;">
<mat-tab *ngFor="let server of settings.servers" [label]="server.name"> <mat-tab *ngFor="let server of settings.servers" [label]="server.name">
@ -74,7 +72,28 @@
<span *ngIf="!server.serverHostname">Current URL: "https://app.emby.media/#!/item/item.html?id=1</span> <span *ngIf="!server.serverHostname">Current URL: "https://app.emby.media/#!/item/item.html?id=1</span>
</small> </small>
</div> </div>
<label>Please select the libraries you want Ombi to look in for content</label>
<br />
<small>Note: if nothing is selected, we will monitor all libraries</small>
<div class="md-form-field">
<div>
<button mat-raised-button (click)="loadLibraries(server)"
class="mat-focus-indicator mat-stroked-button mat-button-base">Load Libraries
<i class="fas fa-film"></i>
</button>
</div>
</div>
<br />
<div *ngIf="server.embySelectedLibraries">
<div *ngFor="let lib of server.embySelectedLibraries">
<div class="md-form-field">
<div class="checkbox">
<mat-slide-toggle [(ngModel)]="lib.enabled" [checked]="lib.enabled"
for="{{lib.title}}">{{lib.title}}</mat-slide-toggle>
</div>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>

@ -1,9 +1,9 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { IEmbyServer, IEmbySettings } from "../../interfaces";
import { EmbyService, JobService, NotificationService, SettingsService, TesterService } from "../../services"; 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 {FormControl} from '@angular/forms';
import { MatTabChangeEvent } from "@angular/material/tabs";
@Component({ @Component({
templateUrl: "./emby.component.html", 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); });
}
} }

@ -9,11 +9,11 @@
<div class="row"> <div class="row">
<div class="col-md-6 col-6 col-sm-6"> <div class="col-md-6 col-6 col-sm-6">
<div style="float:right;text-align:left;">
<div class="md-form-field"> <div class="md-form-field">
<mat-slide-toggle [(ngModel)]="settings.enable" (change)="toggle()" [checked]="settings.enable">Enable</mat-slide-toggle> <mat-slide-toggle [(ngModel)]="settings.enable" [checked]="settings.enable">Enable
</div> </mat-slide-toggle>
</div> </div>
</div> </div>
</div> </div>
<mat-tab-group #tabGroup [selectedIndex]="selected.value" (selectedTabChange)="addTab($event)" (selectedIndexChange)="selected.setValue($event)" animationDuration="0ms" style="display:block;"> <mat-tab-group #tabGroup [selectedIndex]="selected.value" (selectedTabChange)="addTab($event)" (selectedIndexChange)="selected.setValue($event)" animationDuration="0ms" style="display:block;">
@ -75,7 +75,28 @@
</small> </small>
</div> </div>
<label>Please select the libraries you want Ombi to look in for content</label>
<br />
<small>Note: if nothing is selected, we will monitor all libraries</small>
<div class="md-form-field">
<div>
<button mat-raised-button (click)="loadLibraries(server)"
class="mat-focus-indicator mat-stroked-button mat-button-base">Load Libraries
<i class="fas fa-film"></i>
</button>
</div>
</div>
<br />
<div *ngIf="server.jellyfinSelectedLibraries">
<div *ngFor="let lib of server.jellyfinSelectedLibraries">
<div class="md-form-field">
<div class="checkbox">
<mat-slide-toggle [(ngModel)]="lib.enabled" [checked]="lib.enabled"
for="{{lib.title}}">{{lib.title}}</mat-slide-toggle>
</div>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button mat-raised-button id="testJellyfin" type="button" (click)="test(server)" class="mat-focus-indicator mat-stroked-button mat-button-base">Test Connectivity <div id="spinner"></div></button> <button mat-raised-button id="testJellyfin" type="button" (click)="test(server)" class="mat-focus-indicator mat-stroked-button mat-button-base">Test Connectivity <div id="spinner"></div></button>

@ -1,9 +1,9 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { IEmbyServer, IJellyfinLibrariesSettings, IJellyfinServer, IJellyfinSettings } from "../../interfaces";
import { IJellyfinServer, IJellyfinSettings } from "../../interfaces";
import { JellyfinService, JobService, NotificationService, SettingsService, TesterService } from "../../services"; import { JellyfinService, JobService, NotificationService, SettingsService, TesterService } from "../../services";
import { MatTabChangeEvent } from "@angular/material/tabs";
import {FormControl} from '@angular/forms'; import {FormControl} from '@angular/forms';
import { MatTabChangeEvent } from "@angular/material/tabs";
@Component({ @Component({
templateUrl: "./jellyfin.component.html", 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); });
}
} }

@ -1,9 +1,8 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { EmbyService } from "../../services"; import { EmbyService } from "../../services";
import { NotificationService } from "../../services";
import { IEmbySettings } from "../../interfaces"; import { IEmbySettings } from "../../interfaces";
import { NotificationService } from "../../services";
@Component({ @Component({
selector: "wizard-emby", selector: "wizard-emby",
@ -35,7 +34,8 @@ export class EmbyComponent implements OnInit {
ssl: false, ssl: false,
subDir: "", subDir: "",
serverHostname: "", serverHostname: "",
serverId: undefined serverId: undefined,
embySelectedLibraries: []
}); });
} }

@ -1,10 +1,9 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { IJellyfinSettings } from "../../interfaces";
import { JellyfinService } from "../../services"; import { JellyfinService } from "../../services";
import { NotificationService } from "../../services"; import { NotificationService } from "../../services";
import { IJellyfinSettings } from "../../interfaces";
@Component({ @Component({
selector: "wizard-jellyfin", selector: "wizard-jellyfin",
templateUrl: "./jellyfin.component.html", templateUrl: "./jellyfin.component.html",
@ -35,7 +34,8 @@ export class JellyfinComponent implements OnInit {
ssl: false, ssl: false,
subDir: "", subDir: "",
serverHostname: "", serverHostname: "",
serverId: undefined serverId: undefined,
jellyfinSelectedLibraries: []
}); });
} }

@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ombi.Api.Emby; using Ombi.Api.Emby;
using Ombi.Api.Emby.Models; using Ombi.Api.Emby.Models;
using Ombi.Api.Plex; using Ombi.Api.Emby.Models.Media;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External; using Ombi.Core.Settings.Models.External;
@ -92,5 +92,13 @@ namespace Ombi.Controllers.V1.External
// Filter out any dupes // Filter out any dupes
return vm.DistinctBy(x => x.Id); return vm.DistinctBy(x => x.Id);
} }
[HttpPost("Library")]
public async Task<EmbyItemContainer<MediaFolders>> GetLibaries([FromBody] EmbyServers server)
{
var client = await EmbyApi.CreateClient();
var result = await client.GetLibraries(server.ApiKey, server.FullUri);
return result;
}
} }
} }

@ -66,6 +66,14 @@ namespace Ombi.Controllers.V1.External
return result; return result;
} }
[HttpPost("Library")]
public async Task<JellyfinItemContainer<MediaFolders>> GetLibaries([FromBody] JellyfinServers server)
{
var client = await JellyfinApi.CreateClient();
var result = await client.GetLibraries(server.ApiKey, server.FullUri);
return result;
}
/// <summary> /// <summary>
/// Gets the jellyfin users. /// Gets the jellyfin users.
/// </summary> /// </summary>

Loading…
Cancel
Save