Skip to content
Merged
32 changes: 32 additions & 0 deletions src/Ombi.Api.Emby/EmbyApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,5 +248,37 @@ private static void AddHeaders(Request req, string apiKey)
req.AddContentHeader("Content-Type", "application/json");
req.AddHeader("Device", "Ombi");
}

public async Task<EmbyItemContainer<EmbyMovie>> GetMoviesPlayed(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await GetPlayed<EmbyMovie>("Movie", apiKey, userId, baseUri, startIndex, count, parentIdFilder);
}

private async Task<EmbyItemContainer<T>> GetPlayed<T>(string type, string apiKey, string userId, string baseUri, int startIndex, int count, string parentIdFilder = default)
{
var request = new Request($"emby/items", baseUri, HttpMethod.Get);

request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", "ProviderIds");
request.AddQueryString("UserId", userId);
request.AddQueryString("isPlayed", true.ToString());

// paginate and display recently played items first
request.AddQueryString("sortBy", "DatePlayed");
request.AddQueryString("SortOrder", "Descending");
request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString());

if (!string.IsNullOrEmpty(parentIdFilder))
{
request.AddQueryString("ParentId", parentIdFilder);
}

AddHeaders(request, apiKey);

var obj = await Api.Request<EmbyItemContainer<T>>(request);
return obj;
}
}
}
2 changes: 2 additions & 0 deletions src/Ombi.Api.Emby/IBaseEmbyApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId,
Task<EmbyItemContainer<EmbyMovie>> RecentlyAddedMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
Task<EmbyItemContainer<EmbyEpisodes>> RecentlyAddedEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
Task<EmbyItemContainer<EmbySeries>> RecentlyAddedShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);

Task<EmbyItemContainer<EmbyMovie>> GetMoviesPlayed(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
}
}
1 change: 1 addition & 0 deletions src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public void Setup()
_subject = _mocker.CreateInstance<MovieRequestEngine>();
var list = DbHelper.GetQueryableMockDbSet(new RequestSubscription());
_mocker.Setup<IRepository<RequestSubscription>, IQueryable<RequestSubscription>>(x => x.GetAll()).Returns(new List<RequestSubscription>().AsQueryable().BuildMock());
_mocker.Setup<IUserPlayedMovieRepository, IQueryable<UserPlayedMovie>>(x => x.GetAll()).Returns(new List<UserPlayedMovie>().AsQueryable().BuildMock());
}

[Test]
Expand Down
3 changes: 2 additions & 1 deletion src/Ombi.Core.Tests/Engine/V2/MovieRequestEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ public void Setup()
var requestSubs = new Mock<IRepository<RequestSubscription>>();
var mediaCache = new Mock<IMediaCacheService>();
var featureService = new Mock<IFeatureService>();
var userPlayedMovieRepository = new Mock<IUserPlayedMovieRepository>();
_engine = new MovieRequestEngine(movieApi.Object, requestService.Object, user.Object, notificationHelper.Object, rules.Object, movieSender.Object,
logger.Object, userManager.Object, requestLogRepo.Object, cache.Object, ombiSettings.Object, requestSubs.Object, mediaCache.Object, featureService.Object);
logger.Object, userManager.Object, requestLogRepo.Object, cache.Object, ombiSettings.Object, requestSubs.Object, mediaCache.Object, featureService.Object, userPlayedMovieRepository.Object);
}

[Test]
Expand Down
43 changes: 35 additions & 8 deletions src/Ombi.Core/Engine/MovieRequestEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestServi
INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger<MovieRequestEngine> log,
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache,
ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, IMediaCacheService mediaCacheService,
IFeatureService featureService)
IFeatureService featureService,
IUserPlayedMovieRepository userPlayedMovieRepository)
: base(user, requestService, r, manager, cache, ombiSettings, sub)
{
MovieApi = movieApi;
Expand All @@ -43,6 +44,7 @@ public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestServi
_requestLog = rl;
_mediaCacheService = mediaCacheService;
_featureService = featureService;
_userPlayedMovieRepository = userPlayedMovieRepository;
}

private IMovieDbApi MovieApi { get; }
Expand All @@ -52,6 +54,7 @@ public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestServi
private readonly IRepository<RequestLog> _requestLog;
private readonly IMediaCacheService _mediaCacheService;
private readonly IFeatureService _featureService;
protected readonly IUserPlayedMovieRepository _userPlayedMovieRepository;

/// <summary>
/// Requests the movie.
Expand Down Expand Up @@ -252,7 +255,7 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int p
var requests = await (OrderMovies(allRequests, orderFilter.OrderType)).Skip(position).Take(count)
.ToListAsync();

await CheckForSubscription(shouldHide.UserId, requests);
await FillAdditionalFields(shouldHide, requests);
return new RequestsViewModel<MovieRequests>
{
Collection = requests,
Expand Down Expand Up @@ -296,7 +299,7 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int p
var total = requests.Count();
requests = requests.Skip(position).Take(count).ToList();

await CheckForSubscription(shouldHide.UserId, requests);
await FillAdditionalFields(shouldHide, requests);
return new RequestsViewModel<MovieRequests>
{
Collection = requests,
Expand Down Expand Up @@ -381,7 +384,7 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int coun
// TODO fix this so we execute this on the server
requests = requests.Skip(position).Take(count).ToList();

await CheckForSubscription(shouldHide.UserId, requests);
await FillAdditionalFields(shouldHide, requests);
return new RequestsViewModel<MovieRequests>
{
Collection = requests,
Expand Down Expand Up @@ -424,7 +427,7 @@ public async Task<RequestsViewModel<MovieRequests>> GetUnavailableRequests(int c
var total = requests.Count();
requests = requests.Skip(position).Take(count).ToList();

await CheckForSubscription(shouldHide.UserId, requests);
await FillAdditionalFields(shouldHide, requests);
return new RequestsViewModel<MovieRequests>
{
Collection = requests,
Expand Down Expand Up @@ -506,18 +509,25 @@ public async Task<IEnumerable<MovieRequests>> GetRequests()
allRequests = await MovieRepository.GetWithUser().ToListAsync();
}

await CheckForSubscription(shouldHide.UserId, allRequests);
await FillAdditionalFields(shouldHide, allRequests);

return allRequests;
}

public async Task<MovieRequests> GetRequest(int requestId)
{
var shouldHide = await HideFromOtherUsers();
// TODO: this query should return the request only if the user is allowed to see it (see shouldHide implementations)
var request = await MovieRepository.GetWithUser().Where(x => x.Id == requestId).FirstOrDefaultAsync();
await CheckForSubscription((await GetUser()).Id, new List<MovieRequests> { request });
await FillAdditionalFields(shouldHide, new List<MovieRequests> { request });

return request;
}
private async Task FillAdditionalFields(HideResult shouldHide, List<MovieRequests> requests)
{
await CheckForSubscription(shouldHide.UserId, requests);
await CheckForPlayed(shouldHide, requests);
}

private async Task CheckForSubscription(string UserId, List<MovieRequests> movieRequests)
{
Expand All @@ -543,6 +553,23 @@ private async Task CheckForSubscription(string UserId, List<MovieRequests> movie
}
}
}

private async Task CheckForPlayed(HideResult shouldHide, List<MovieRequests> movieRequests)
{
var theMovieDbIds = movieRequests.Select(x => x.TheMovieDbId);
var plays = await _userPlayedMovieRepository.GetAll().Where(x =>
theMovieDbIds.Contains(x.TheMovieDbId))
.ToListAsync();
foreach (var request in movieRequests)
{
request.WatchedByRequestedUser = plays.Exists(x => x.TheMovieDbId == request.TheMovieDbId && x.UserId == request.RequestedUserId);

if (!shouldHide.Hide)
{
request.PlayedByUsersCount = plays.Count(x => x.TheMovieDbId == request.TheMovieDbId);
}
}
}

/// <summary>
/// Searches the movie request.
Expand All @@ -563,7 +590,7 @@ public async Task<IEnumerable<MovieRequests>> SearchMovieRequest(string search)
}

var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList();
await CheckForSubscription(shouldHide.UserId, results);
await FillAdditionalFields(shouldHide, results);

return results;
}
Expand Down
2 changes: 2 additions & 0 deletions src/Ombi.DependencyInjection/IocExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ public static void RegisterStore(this IServiceCollection services)
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
services.AddScoped<IJellyfinContentRepository, JellyfinContentRepository>();
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddScoped<IUserPlayedMovieRepository, UserPlayedMovieRepository>();

services.AddScoped<ITvRequestRepository, TvRequestRepository>();
services.AddScoped<IMovieRequestRepository, MovieRequestRepository>();
Expand Down Expand Up @@ -244,6 +245,7 @@ public static void RegisterJobs(this IServiceCollection services)
services.AddTransient<IPlexContentSync, PlexContentSync>();
services.AddTransient<IPlexWatchlistImport, PlexWatchlistImport>();
services.AddTransient<IEmbyContentSync, EmbyContentSync>();
services.AddTransient<IEmbyPlayedSync, EmbyPlayedSync>();
services.AddTransient<IEmbyEpisodeSync, EmbyEpisodeSync>();
services.AddTransient<IEmbyAvaliabilityChecker, EmbyAvaliabilityChecker>();
services.AddTransient<IJellyfinContentSync, JellyfinContentSync>();
Expand Down
139 changes: 23 additions & 116 deletions src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,119 +8,56 @@
using Ombi.Api.Emby.Models;
using Ombi.Api.Emby.Models.Media.Tv;
using Ombi.Api.Emby.Models.Movie;
using Ombi.Core.Services;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Hubs;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
using MediaType = Ombi.Store.Entities.MediaType;

namespace Ombi.Schedule.Jobs.Emby
{
public class EmbyContentSync : IEmbyContentSync
public class EmbyContentSync : EmbyLibrarySync, IEmbyContentSync
{
public EmbyContentSync(ISettingsService<EmbySettings> settings, IEmbyApiFactory api, ILogger<EmbyContentSync> logger,
IEmbyContentRepository repo, INotificationHubService notification)
public EmbyContentSync(
ISettingsService<EmbySettings> settings,
IEmbyApiFactory api,
ILogger<EmbyContentSync> logger,
IEmbyContentRepository repo,
INotificationHubService notification,
IFeatureService feature):
base(settings, api, logger, notification)
{
_logger = logger;
_settings = settings;
_apiFactory = api;
_repo = repo;
_notification = notification;
_feature = feature;
}

private readonly ILogger<EmbyContentSync> _logger;
private readonly ISettingsService<EmbySettings> _settings;
private readonly IEmbyApiFactory _apiFactory;
private readonly IEmbyContentRepository _repo;
private readonly INotificationHubService _notification;
private readonly IFeatureService _feature;

private const int AmountToTake = 100;

private IEmbyApi Api { get; set; }

public async Task Execute(IJobExecutionContext context)
public async override Task Execute(IJobExecutionContext context)
{
JobDataMap dataMap = context.JobDetail.JobDataMap;
var recentlyAddedSearch = false;
if (dataMap.TryGetValue(JobDataKeys.EmbyRecentlyAddedSearch, out var recentlyAddedObj))
{
recentlyAddedSearch = Convert.ToBoolean(recentlyAddedObj);
}

var embySettings = await _settings.GetSettingsAsync();
if (!embySettings.Enable)
return;

Api = _apiFactory.CreateClient(embySettings);
await base.Execute(context);

await _notification.SendNotificationToAdmins(recentlyAddedSearch ? "Emby Recently Added Started" : "Emby Content Sync Started");

foreach (var server in embySettings.Servers)
{
try
{
await StartServerCache(server, recentlyAddedSearch);
}
catch (Exception e)
{
await _notification.SendNotificationToAdmins("Emby Content Sync Failed");
_logger.LogError(e, "Exception when caching Emby for server {0}", server.Name);
}
}

await _notification.SendNotificationToAdmins("Emby Content Sync Finished");
// Episodes
await OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IEmbyEpisodeSync), "Emby"), new JobDataMap(new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, recentlyAdded.ToString() } }));


await OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IEmbyEpisodeSync), "Emby"), new JobDataMap(new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, recentlyAddedSearch.ToString() } }));
}


private async Task StartServerCache(EmbyServers server, bool recentlyAdded)
{
if (!ValidateSettings(server))
{
return;
}


if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled))
{
var movieLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");

foreach (var movieParentIdFilder in movieLibsToFilter)
{
_logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'");
await ProcessMovies(server, recentlyAdded, movieParentIdFilder.Key);
}

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, recentlyAdded, tvParentIdFilter.Key);
}


var mixedLibs = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "mixed");
foreach (var m in mixedLibs)
{
_logger.LogInformation($"Scanning Lib '{m.Title}'");
await ProcessTv(server, recentlyAdded, m.Key);
await ProcessMovies(server, recentlyAdded, m.Key);
}
}
else
// Played state
var isPlayedSyncEnabled = await _feature.FeatureEnabled(FeatureNames.PlayedSync);
if(isPlayedSyncEnabled)
{
await ProcessMovies(server, recentlyAdded);
await ProcessTv(server, recentlyAdded);
await OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IEmbyPlayedSync), "Emby"), new JobDataMap(new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, recentlyAdded.ToString() } }));
}
}

private async Task ProcessTv(EmbyServers server, bool recentlyAdded, string parentId = default)

protected async override Task ProcessTv(EmbyServers server, string parentId = default)
{
// TV Time
var mediaToAdd = new HashSet<EmbyContent>();
Expand Down Expand Up @@ -196,7 +133,7 @@ private async Task ProcessTv(EmbyServers server, bool recentlyAdded, string pare
await _repo.AddRange(mediaToAdd);
}

private async Task ProcessMovies(EmbyServers server, bool recentlyAdded, string parentId = default)
protected override async Task ProcessMovies(EmbyServers server, string parentId = default)
{
EmbyItemContainer<EmbyMovie> movies;
if (recentlyAdded)
Expand Down Expand Up @@ -319,36 +256,6 @@ private void MapEmbyContent(EmbyContent content, EmbyMovie movieInfo, EmbyServer
content.Quality = has4K ? null : quality;
content.Has4K = has4K;
}

private bool ValidateSettings(EmbyServers server)
{
if (server?.Ip == null || string.IsNullOrEmpty(server?.ApiKey))
{
_logger.LogInformation(LoggingEvents.EmbyContentCacher, $"Server {server?.Name} is not configured correctly");
return false;
}

return true;
}

private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;

if (disposing)
{
//_settings?.Dispose();
}
_disposed = true;
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

}
Loading