mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 21:03:17 -07:00
feat(emby): Show watched status for Movie requests
* First step towards played sync * Change TMDB id format to integer This will better integrate with TMDB id type in the request model * Display played state in the requests list * Fix played status filter * Run played sync job after content sync instead of on its own * Add a toggle to activate played sync * Hoovering * FIx played sync job not being triggered * Expose played state according to hide requests setting * Fix tests * Fix tests for real * Add MySql migrations [skip ci]
This commit is contained in:
parent
06b46ec1d4
commit
9cfb10bb1e
30 changed files with 1727 additions and 139 deletions
|
@ -248,5 +248,37 @@ namespace Ombi.Api.Emby
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,5 +32,7 @@ namespace Ombi.Api.Emby
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ namespace Ombi.Core.Tests.Engine
|
|||
_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]
|
||||
|
|
|
@ -46,8 +46,9 @@ namespace Ombi.Core.Tests.Engine.V2
|
|||
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]
|
||||
|
|
|
@ -33,7 +33,8 @@ namespace Ombi.Core.Engine
|
|||
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;
|
||||
|
@ -43,6 +44,7 @@ namespace Ombi.Core.Engine
|
|||
_requestLog = rl;
|
||||
_mediaCacheService = mediaCacheService;
|
||||
_featureService = featureService;
|
||||
_userPlayedMovieRepository = userPlayedMovieRepository;
|
||||
}
|
||||
|
||||
private IMovieDbApi MovieApi { get; }
|
||||
|
@ -52,6 +54,7 @@ namespace Ombi.Core.Engine
|
|||
private readonly IRepository<RequestLog> _requestLog;
|
||||
private readonly IMediaCacheService _mediaCacheService;
|
||||
private readonly IFeatureService _featureService;
|
||||
protected readonly IUserPlayedMovieRepository _userPlayedMovieRepository;
|
||||
|
||||
/// <summary>
|
||||
/// Requests the movie.
|
||||
|
@ -252,7 +255,7 @@ namespace Ombi.Core.Engine
|
|||
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,
|
||||
|
@ -296,7 +299,7 @@ namespace Ombi.Core.Engine
|
|||
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,
|
||||
|
@ -381,7 +384,7 @@ namespace Ombi.Core.Engine
|
|||
// 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,
|
||||
|
@ -424,7 +427,7 @@ namespace Ombi.Core.Engine
|
|||
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,
|
||||
|
@ -506,18 +509,25 @@ namespace Ombi.Core.Engine
|
|||
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)
|
||||
{
|
||||
|
@ -543,6 +553,23 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -563,7 +590,7 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
|
||||
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList();
|
||||
await CheckForSubscription(shouldHide.UserId, results);
|
||||
await FillAdditionalFields(shouldHide, results);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
|
|
@ -197,6 +197,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
|
||||
services.AddScoped<IJellyfinContentRepository, JellyfinContentRepository>();
|
||||
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
|
||||
services.AddScoped<IUserPlayedMovieRepository, UserPlayedMovieRepository>();
|
||||
|
||||
services.AddScoped<ITvRequestRepository, TvRequestRepository>();
|
||||
services.AddScoped<IMovieRequestRepository, MovieRequestRepository>();
|
||||
|
@ -244,6 +245,7 @@ namespace Ombi.DependencyInjection
|
|||
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>();
|
||||
|
|
|
@ -8,10 +8,12 @@ using Ombi.Api.Emby;
|
|||
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;
|
||||
|
@ -19,108 +21,43 @@ 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;
|
||||
await base.Execute(context);
|
||||
|
||||
Api = _apiFactory.CreateClient(embySettings);
|
||||
|
||||
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))
|
||||
// Played state
|
||||
var isPlayedSyncEnabled = await _feature.FeatureEnabled(FeatureNames.PlayedSync);
|
||||
if(isPlayedSyncEnabled)
|
||||
{
|
||||
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
|
||||
{
|
||||
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>();
|
||||
|
@ -196,7 +133,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
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)
|
||||
|
@ -324,36 +261,6 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
146
src/Ombi.Schedule/Jobs/Emby/EmbyLibrarySync.cs
Normal file
146
src/Ombi.Schedule/Jobs/Emby/EmbyLibrarySync.cs
Normal file
|
@ -0,0 +1,146 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Hubs;
|
||||
using Quartz;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Emby
|
||||
{
|
||||
public abstract class EmbyLibrarySync
|
||||
{
|
||||
public EmbyLibrarySync(ISettingsService<EmbySettings> settings, IEmbyApiFactory api, ILogger<EmbyContentSync> logger,
|
||||
INotificationHubService notification)
|
||||
{
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
_apiFactory = api;
|
||||
_notification = notification;
|
||||
}
|
||||
|
||||
protected readonly ILogger<EmbyContentSync> _logger;
|
||||
protected readonly ISettingsService<EmbySettings> _settings;
|
||||
protected readonly IEmbyApiFactory _apiFactory;
|
||||
protected bool recentlyAdded;
|
||||
protected readonly INotificationHubService _notification;
|
||||
|
||||
protected const int AmountToTake = 100;
|
||||
|
||||
protected IEmbyApi Api { get; set; }
|
||||
|
||||
public virtual async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
|
||||
JobDataMap dataMap = context.JobDetail.JobDataMap;
|
||||
if (dataMap.TryGetValue(JobDataKeys.EmbyRecentlyAddedSearch, out var recentlyAddedObj))
|
||||
{
|
||||
recentlyAdded = Convert.ToBoolean(recentlyAddedObj);
|
||||
}
|
||||
|
||||
await _notification.SendNotificationToAdmins(recentlyAdded ? "Emby Recently Added Started" : "Emby Content Sync Started");
|
||||
|
||||
|
||||
var embySettings = await _settings.GetSettingsAsync();
|
||||
if (!embySettings.Enable)
|
||||
return;
|
||||
|
||||
Api = _apiFactory.CreateClient(embySettings);
|
||||
|
||||
foreach (var server in embySettings.Servers)
|
||||
{
|
||||
try
|
||||
{
|
||||
await StartServerCache(server);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
private async Task StartServerCache(EmbyServers server)
|
||||
{
|
||||
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, 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, 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, m.Key);
|
||||
await ProcessMovies(server, m.Key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ProcessMovies(server);
|
||||
await ProcessTv(server);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task ProcessTv(EmbyServers server, string parentId = default);
|
||||
|
||||
protected abstract Task ProcessMovies(EmbyServers server, string parentId = default);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
110
src/Ombi.Schedule/Jobs/Emby/EmbyPlayedSync.cs
Normal file
110
src/Ombi.Schedule/Jobs/Emby/EmbyPlayedSync.cs
Normal file
|
@ -0,0 +1,110 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.Emby.Models;
|
||||
using Ombi.Api.Emby.Models.Movie;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Hubs;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Emby
|
||||
{
|
||||
public class EmbyPlayedSync : EmbyLibrarySync, IEmbyPlayedSync
|
||||
{
|
||||
public EmbyPlayedSync(ISettingsService<EmbySettings> settings, IEmbyApiFactory api, ILogger<EmbyContentSync> logger,
|
||||
IUserPlayedMovieRepository repo, INotificationHubService notification, OmbiUserManager user) : base(settings, api, logger, notification)
|
||||
{
|
||||
_userManager = user;
|
||||
_repo = repo;
|
||||
}
|
||||
private OmbiUserManager _userManager { get; }
|
||||
|
||||
private readonly IUserPlayedMovieRepository _repo;
|
||||
|
||||
protected override Task ProcessTv(EmbyServers server, string parentId = default)
|
||||
{
|
||||
// TODO
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected async override Task ProcessMovies(EmbyServers server, string parentId = default)
|
||||
{
|
||||
|
||||
var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.EmbyUser || x.UserType == UserType.EmbyConnectUser).ToListAsync();
|
||||
foreach (var user in allUsers)
|
||||
{
|
||||
await ProcessMoviesUser(server, user, parentId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task ProcessMoviesUser(EmbyServers server, OmbiUser user, string parentId = default)
|
||||
{
|
||||
EmbyItemContainer<EmbyMovie> movies;
|
||||
if (recentlyAdded)
|
||||
{
|
||||
var recentlyAddedAmountToTake = 5; // to be adjusted?
|
||||
movies = await Api.GetMoviesPlayed(server.ApiKey, parentId, 0, recentlyAddedAmountToTake, user.ProviderUserId, server.FullUri);
|
||||
// Setting this so we don't attempt to grab more than we need
|
||||
if (movies.TotalRecordCount > recentlyAddedAmountToTake)
|
||||
{
|
||||
movies.TotalRecordCount = recentlyAddedAmountToTake;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
movies = await Api.GetMoviesPlayed(server.ApiKey, parentId, 0, AmountToTake, user.ProviderUserId, server.FullUri);
|
||||
}
|
||||
var totalCount = movies.TotalRecordCount;
|
||||
var processed = 0;
|
||||
var mediaToAdd = new HashSet<UserPlayedMovie>();
|
||||
|
||||
while (processed < totalCount)
|
||||
{
|
||||
foreach (var movie in movies.Items)
|
||||
{
|
||||
await ProcessMovie(movie, user, mediaToAdd, server);
|
||||
processed++;
|
||||
}
|
||||
|
||||
// Get the next batch
|
||||
// Recently Added should never be checked as the TotalRecords should equal the amount to take
|
||||
if (!recentlyAdded)
|
||||
{
|
||||
movies = await Api.GetMoviesPlayed(server.ApiKey, parentId, processed, AmountToTake, user.ProviderUserId, server.FullUri);
|
||||
}
|
||||
await _repo.AddRange(mediaToAdd);
|
||||
mediaToAdd.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessMovie(EmbyMovie movieInfo, OmbiUser user, ICollection<UserPlayedMovie> content, EmbyServers server)
|
||||
{
|
||||
if (movieInfo.ProviderIds.Tmdb.IsNullOrEmpty())
|
||||
{
|
||||
_logger.LogWarning($"Movie {movieInfo.Name} has no relevant metadata. Skipping.");
|
||||
return;
|
||||
}
|
||||
var userPlayedMovie = new UserPlayedMovie()
|
||||
{
|
||||
TheMovieDbId = int.Parse(movieInfo.ProviderIds.Tmdb),
|
||||
UserId = user.Id
|
||||
};
|
||||
// Check if it exists
|
||||
var existingMovie = await _repo.Get(userPlayedMovie.TheMovieDbId, userPlayedMovie.UserId);
|
||||
var alreadyGoingToAdd = content.Any(x => x.TheMovieDbId == userPlayedMovie.TheMovieDbId && x.UserId == userPlayedMovie.UserId);
|
||||
if (existingMovie == null && !alreadyGoingToAdd)
|
||||
{
|
||||
content.Add(userPlayedMovie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
6
src/Ombi.Schedule/Jobs/Emby/IEmbyPlayedSync.cs
Normal file
6
src/Ombi.Schedule/Jobs/Emby/IEmbyPlayedSync.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Ombi.Schedule.Jobs.Emby
|
||||
{
|
||||
public interface IEmbyPlayedSync : IBaseJob
|
||||
{
|
||||
}
|
||||
}
|
|
@ -15,15 +15,22 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
public class MediaDatabaseRefresh : IMediaDatabaseRefresh
|
||||
{
|
||||
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log,
|
||||
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo,
|
||||
ISettingsService<EmbySettings> embySettings, ISettingsService<JellyfinSettings> jellyfinSettings)
|
||||
public MediaDatabaseRefresh(
|
||||
ISettingsService<PlexSettings> s,
|
||||
ILogger<MediaDatabaseRefresh> log,
|
||||
IPlexContentRepository plexRepo,
|
||||
IEmbyContentRepository embyRepo,
|
||||
IJellyfinContentRepository jellyfinRepo,
|
||||
IUserPlayedMovieRepository userPlayedRepo,
|
||||
ISettingsService<EmbySettings> embySettings,
|
||||
ISettingsService<JellyfinSettings> jellyfinSettings)
|
||||
{
|
||||
_plexSettings = s;
|
||||
_log = log;
|
||||
_plexRepo = plexRepo;
|
||||
_embyRepo = embyRepo;
|
||||
_jellyfinRepo = jellyfinRepo;
|
||||
_userPlayedRepo = userPlayedRepo;
|
||||
_embySettings = embySettings;
|
||||
_jellyfinSettings = jellyfinSettings;
|
||||
_plexSettings.ClearCache();
|
||||
|
@ -34,6 +41,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
private readonly IPlexContentRepository _plexRepo;
|
||||
private readonly IEmbyContentRepository _embyRepo;
|
||||
private readonly IJellyfinContentRepository _jellyfinRepo;
|
||||
private readonly IUserPlayedMovieRepository _userPlayedRepo;
|
||||
private readonly ISettingsService<EmbySettings> _embySettings;
|
||||
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
|
||||
|
||||
|
@ -41,6 +49,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
try
|
||||
{
|
||||
await RemovePlayedData();
|
||||
await RemovePlexData();
|
||||
await RemoveEmbyData();
|
||||
await RemoveJellyfinData();
|
||||
|
@ -52,6 +61,20 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
|
||||
}
|
||||
|
||||
private async Task RemovePlayedData()
|
||||
{
|
||||
try
|
||||
{
|
||||
const string movieSql = "DELETE FROM UserPlayedMovie";
|
||||
await _userPlayedRepo.ExecuteSql(movieSql);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(LoggingEvents.MediaReferesh, e, "Refreshing Played Data Failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task RemoveEmbyData()
|
||||
{
|
||||
try
|
||||
|
|
|
@ -99,6 +99,7 @@ namespace Ombi.Schedule
|
|||
await OmbiQuartz.Instance.AddJob<IEmbyContentSync>(nameof(IEmbyContentSync), "Emby", JobSettingsHelper.EmbyContent(s));
|
||||
await OmbiQuartz.Instance.AddJob<IEmbyContentSync>(nameof(IEmbyContentSync) + "RecentlyAdded", "Emby", JobSettingsHelper.EmbyRecentlyAddedSync(s), new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, "true" } });
|
||||
await OmbiQuartz.Instance.AddJob<IEmbyEpisodeSync>(nameof(IEmbyEpisodeSync), "Emby", null);
|
||||
await OmbiQuartz.Instance.AddJob<IEmbyPlayedSync>(nameof(IEmbyPlayedSync), "Emby", null);
|
||||
await OmbiQuartz.Instance.AddJob<IEmbyAvaliabilityChecker>(nameof(IEmbyAvaliabilityChecker), "Emby", null);
|
||||
await OmbiQuartz.Instance.AddJob<IEmbyUserImporter>(nameof(IEmbyUserImporter), "Emby", JobSettingsHelper.UserImporter(s));
|
||||
}
|
||||
|
|
|
@ -21,5 +21,6 @@ namespace Ombi.Settings.Settings.Models
|
|||
{
|
||||
public const string Movie4KRequests = nameof(Movie4KRequests);
|
||||
public const string OldTrendingSource = nameof(OldTrendingSource);
|
||||
public const string PlayedSync = nameof(PlayedSync);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ namespace Ombi.Store.Context
|
|||
public DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; }
|
||||
public DbSet<SickRageCache> SickRageCache { get; set; }
|
||||
public DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
|
||||
public DbSet<UserPlayedMovie> UserPlayedMovie { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
|
|
@ -84,5 +84,10 @@ namespace Ombi.Store.Entities.Requests
|
|||
|
||||
[NotMapped]
|
||||
public override bool CanApprove => !Approved && !Available || !Approved4K && !Available4K;
|
||||
|
||||
[NotMapped]
|
||||
public bool WatchedByRequestedUser { get; set; }
|
||||
[NotMapped]
|
||||
public int PlayedByUsersCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
8
src/Ombi.Store/Entities/UserPlayedMovie.cs
Normal file
8
src/Ombi.Store/Entities/UserPlayedMovie.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Store.Entities
|
||||
{
|
||||
public class UserPlayedMovie : Entity
|
||||
{
|
||||
public int TheMovieDbId { get; set; }
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
}
|
566
src/Ombi.Store/Migrations/ExternalMySql/20230406152218_MovieUserPlayed.Designer.cs
generated
Normal file
566
src/Ombi.Store/Migrations/ExternalMySql/20230406152218_MovieUserPlayed.Designer.cs
generated
Normal file
|
@ -0,0 +1,566 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Ombi.Store.Context.MySql;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Ombi.Store.Migrations.ExternalMySql
|
||||
{
|
||||
[DbContext(typeof(ExternalMySqlContext))]
|
||||
[Migration("20230406152218_MovieUserPlayed")]
|
||||
partial class MovieUserPlayed
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("CouchPotatoCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("EmbyId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<bool>("Has4K")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ProviderId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Quality")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("EmbyContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("EmbyId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ParentId")
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<string>("ProviderId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.ToTable("EmbyEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("Has4K")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("JellyfinId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<string>("ProviderId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Quality")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("JellyfinContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("JellyfinId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ParentId")
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<string>("ProviderId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.ToTable("JellyfinEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("ArtistId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ForeignAlbumId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("Monitored")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<decimal>("PercentOfTracks")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<DateTime>("ReleaseDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("TrackCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("LidarrAlbumCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ArtistId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ArtistName")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ForeignArtistId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("Monitored")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("LidarrArtistCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("GrandparentKey")
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ParentKey")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GrandparentKey");
|
||||
|
||||
b.ToTable("PlexEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ParentKey")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("PlexContentId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("PlexServerContentId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("SeasonKey")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PlexServerContentId");
|
||||
|
||||
b.ToTable("PlexSeasonsContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("Has4K")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<string>("Quality")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ReleaseYear")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("RequestId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PlexServerContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexWatchlistHistory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TmdbId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PlexWatchlistHistory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("Has4K")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("HasFile")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("HasRegular")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RadarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TvDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SickRageCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TvDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SickRageEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TvDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("HasFile")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("MovieDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TvDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("UserPlayedMovie");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("ParentId")
|
||||
.HasPrincipalKey("EmbyId");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("ParentId")
|
||||
.HasPrincipalKey("JellyfinId");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("GrandparentKey")
|
||||
.HasPrincipalKey("Key");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexServerContent", null)
|
||||
.WithMany("Seasons")
|
||||
.HasForeignKey("PlexServerContentId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
|
||||
{
|
||||
b.Navigation("Episodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
|
||||
{
|
||||
b.Navigation("Episodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
|
||||
{
|
||||
b.Navigation("Episodes");
|
||||
|
||||
b.Navigation("Seasons");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Ombi.Store.Migrations.ExternalMySql
|
||||
{
|
||||
public partial class MovieUserPlayed : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserPlayedMovie",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
TheMovieDbId = table.Column<int>(type: "int", nullable: false),
|
||||
UserId = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserPlayedMovie", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserPlayedMovie");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ namespace Ombi.Store.Migrations.ExternalMySql
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.0")
|
||||
.HasAnnotation("ProductVersion", "6.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
|
||||
|
@ -488,6 +488,23 @@ namespace Ombi.Store.Migrations.ExternalMySql
|
|||
b.ToTable("SonarrEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("UserPlayedMovie");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
|
||||
|
|
564
src/Ombi.Store/Migrations/ExternalSqlite/20230310130339_MovieUserPlayed.Designer.cs
generated
Normal file
564
src/Ombi.Store/Migrations/ExternalSqlite/20230310130339_MovieUserPlayed.Designer.cs
generated
Normal file
|
@ -0,0 +1,564 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Ombi.Store.Context.Sqlite;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Ombi.Store.Migrations.ExternalSqlite
|
||||
{
|
||||
[DbContext(typeof(ExternalSqliteContext))]
|
||||
[Migration("20230310130339_MovieUserPlayed")]
|
||||
partial class MovieUserPlayed
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("CouchPotatoCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EmbyId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Has4K")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Quality")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("EmbyContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EmbyId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ParentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.ToTable("EmbyEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Has4K")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("JellyfinId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Quality")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("JellyfinContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("JellyfinId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ParentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.ToTable("JellyfinEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ArtistId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ForeignAlbumId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Monitored")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("PercentOfTracks")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ReleaseDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TrackCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("LidarrAlbumCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ArtistId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ArtistName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ForeignArtistId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Monitored")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("LidarrArtistCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("GrandparentKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ParentKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GrandparentKey");
|
||||
|
||||
b.ToTable("PlexEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ParentKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PlexContentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PlexServerContentId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SeasonKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PlexServerContentId");
|
||||
|
||||
b.ToTable("PlexSeasonsContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Has4K")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Quality")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ReleaseYear")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("RequestId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TheMovieDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TvDbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PlexServerContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexWatchlistHistory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TmdbId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PlexWatchlistHistory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Has4K")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HasFile")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HasRegular")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RadarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TvDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SickRageCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TvDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SickRageEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TvDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HasFile")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MovieDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TvDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("UserPlayedMovie");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("ParentId")
|
||||
.HasPrincipalKey("EmbyId");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("ParentId")
|
||||
.HasPrincipalKey("JellyfinId");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("GrandparentKey")
|
||||
.HasPrincipalKey("Key");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexServerContent", null)
|
||||
.WithMany("Seasons")
|
||||
.HasForeignKey("PlexServerContentId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
|
||||
{
|
||||
b.Navigation("Episodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
|
||||
{
|
||||
b.Navigation("Episodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
|
||||
{
|
||||
b.Navigation("Episodes");
|
||||
|
||||
b.Navigation("Seasons");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Ombi.Store.Migrations.ExternalSqlite
|
||||
{
|
||||
public partial class MovieUserPlayed : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserPlayedMovie",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
TheMovieDbId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserPlayedMovie", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserPlayedMovie");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace Ombi.Store.Migrations.ExternalSqlite
|
|||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.0");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
|
||||
{
|
||||
|
@ -486,6 +486,23 @@ namespace Ombi.Store.Migrations.ExternalSqlite
|
|||
b.ToTable("SonarrEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TheMovieDbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("UserPlayedMovie");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
|
||||
|
|
13
src/Ombi.Store/Repository/IUserPlayedMovieRepository.cs
Normal file
13
src/Ombi.Store/Repository/IUserPlayedMovieRepository.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Store.Repository
|
||||
{
|
||||
public interface IUserPlayedMovieRepository : IExternalRepository<UserPlayedMovie>
|
||||
{
|
||||
Task<UserPlayedMovie> Get(int theMovieDbId, string userId);
|
||||
}
|
||||
}
|
27
src/Ombi.Store/Repository/UserPlayedMovieRepository.cs
Normal file
27
src/Ombi.Store/Repository/UserPlayedMovieRepository.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Store.Repository
|
||||
{
|
||||
public class UserPlayedMovieRepository : ExternalRepository<UserPlayedMovie>, IUserPlayedMovieRepository
|
||||
{
|
||||
protected ExternalContext Db { get; }
|
||||
public UserPlayedMovieRepository(ExternalContext db) : base(db)
|
||||
{
|
||||
Db = db;
|
||||
}
|
||||
|
||||
public async Task<UserPlayedMovie> Get(int theMovieDbId, string userId)
|
||||
{
|
||||
return await Db.UserPlayedMovie.FirstOrDefaultAsync(x => x.TheMovieDbId == theMovieDbId && x.UserId == userId);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ export interface IMovieRequests extends IFullBaseRequest {
|
|||
deniedReason4K: string;
|
||||
requestedDate4k: Date;
|
||||
requestedDate: Date;
|
||||
watchedByRequestedUser: boolean;
|
||||
playedByUsersCount: number;
|
||||
|
||||
// For the UI
|
||||
rootPathOverrideTitle: string;
|
||||
|
@ -212,4 +214,4 @@ export class BaseRequestOptions {
|
|||
requestOnBehalf: string | undefined;
|
||||
rootFolderOverride: number | undefined;
|
||||
qualityPathOverride: number | undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,24 @@
|
|||
<td mat-cell id="requestedStatus{{element.id}}" *matCellDef="let element"> {{element.requestStatus | translate}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="watchedByRequestedUser">
|
||||
<th
|
||||
mat-header-cell
|
||||
*matHeaderCellDef
|
||||
mat-sort-header
|
||||
disableClear
|
||||
matTooltip="{{ 'Requests.WatchedTooltip' | translate}}">
|
||||
{{ 'Requests.Watched' | translate}}
|
||||
</th>
|
||||
<td mat-cell id="watchedByRequestedUser{{element.id}}" *matCellDef="let element">
|
||||
<mat-checkbox
|
||||
[checked]="element.watchedByRequestedUser"
|
||||
disabled="true"
|
||||
matTooltip="{{'Requests.WatchedByUsersCount' | translate: {count: element.playedByUsersCount} }}">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef> </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
|
|
|
@ -24,10 +24,11 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
|
|||
public dataSource: MatTableDataSource<IMovieRequests>;
|
||||
public resultsLength: number;
|
||||
public isLoadingResults = true;
|
||||
public displayedColumns: string[] = ['title', 'requestedUser.requestedBy', 'status', 'requestStatus','requestedDate', 'actions'];
|
||||
public displayedColumns: string[] = ['title', 'requestedUser.requestedBy', 'status', 'requestStatus','requestedDate'];
|
||||
public gridCount: string = "15";
|
||||
public isAdmin: boolean;
|
||||
public is4kEnabled = false;
|
||||
public isPlayedSyncEnabled = false;
|
||||
public manageOwnRequests: boolean;
|
||||
public defaultSort: string = "requestedDate";
|
||||
public defaultOrder: string = "desc";
|
||||
|
@ -65,10 +66,9 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
this.is4kEnabled = this.featureFacade.is4kEnabled();
|
||||
if ((this.isAdmin || this.auth.hasRole("Request4KMovie"))
|
||||
&& this.is4kEnabled) {
|
||||
this.displayedColumns.splice(4, 0, 'has4kRequest');
|
||||
}
|
||||
this.isPlayedSyncEnabled = this.featureFacade.isPlayedSyncEnabled();
|
||||
|
||||
this.addDynamicColumns();
|
||||
|
||||
const defaultCount = this.storageService.get(this.storageKeyGridCount);
|
||||
const defaultSort = this.storageService.get(this.storageKey);
|
||||
|
@ -88,6 +88,20 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
}
|
||||
|
||||
addDynamicColumns() {
|
||||
if ((this.isAdmin || this.auth.hasRole("Request4KMovie"))
|
||||
&& this.is4kEnabled) {
|
||||
this.displayedColumns.splice(4, 0, 'has4kRequest');
|
||||
}
|
||||
|
||||
if (this.isPlayedSyncEnabled) {
|
||||
this.displayedColumns.push('watchedByRequestedUser');
|
||||
}
|
||||
|
||||
// always put the actions column at the end
|
||||
this.displayedColumns.push('actions');
|
||||
}
|
||||
|
||||
public async ngAfterViewInit() {
|
||||
|
||||
this.storageService.save(this.storageKeyGridCount, this.gridCount);
|
||||
|
@ -263,4 +277,4 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
return request.requestedDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,4 +23,6 @@ export class FeaturesFacade {
|
|||
|
||||
public is4kEnabled = (): boolean => this.store.selectSnapshot(FeaturesSelectors.is4kEnabled);
|
||||
|
||||
}
|
||||
public isPlayedSyncEnabled = (): boolean => this.store.selectSnapshot(FeaturesSelectors.isPlayedSyncEnabled);
|
||||
|
||||
}
|
||||
|
|
|
@ -15,4 +15,9 @@ export class FeaturesSelectors {
|
|||
return features.filter(x => x.name === "Movie4KRequests")[0].enabled;
|
||||
}
|
||||
|
||||
}
|
||||
@Selector([FeaturesSelectors.features])
|
||||
public static isPlayedSyncEnabled(features: IFeatureEnablement[]): boolean {
|
||||
return features.filter(x => x.name === "PlayedSync")[0].enabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -159,6 +159,9 @@
|
|||
"RequestedBy": "Requested By",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Request status",
|
||||
"Watched": "Watched",
|
||||
"WatchedTooltip": "The user who made the request has watched it",
|
||||
"WatchedByUsersCount": "{{count}} users have watched this.",
|
||||
"Denied": " Denied:",
|
||||
"TheatricalRelease": "Theatrical Release: {{date}}",
|
||||
"ReleaseDate": "Released: {{date}}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue