feat(radarr): 4K Requests and Radarr 4K support

* feat: updated radarr settings API to support 4k

* feat: refactored the radarr setting page to support the new model

* feat: Added 4k radarr to the settings page

* feat: Added the new Movie 4k Request

* feat: Got some of the backend rules done

* feat: Made a load of progress

* Removed the csproj ref

* feat: fixed the radarr ui

* feat: fixed up all the movie requests page

* feat: Hide the 4K buttons when the user does not have the 4k permission

* fix: fixed the templateref issue

* test: fixed up all the tests

* feat: Added migrations for media sever quality. Emby and Radarr Sync jobs now pull the quality

* feat: Done the media sync jobs

* feat: plex availability checker

* feat: Updated the jellyfin availability checker to check for 4k

* feat: updated emby availbility checker to check for 4k

* feat: almost got it all working now

* feat: Added 4k approve to the request list options

* feat: Added 4k to the requests list and bulk approve

* feat: Added the features service

* feat: added feature update to the frontend

* feat: got the features page working

* feat: Applied the feature service on the backend

* feat: added the feature flag on the UI

* feat: added 4k to the card
pull/4510/head
Jamie 2 years ago committed by GitHub
parent 09f648515e
commit ba88848866
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -170,7 +170,7 @@ namespace Ombi.Api.Emby
request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("Fields", includeOverview ? "ProviderIds,MediaStreams,Overview" : "ProviderIds,MediaStreams ");
request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString());
request.AddQueryString("sortBy", "DateCreated");

@ -2,35 +2,6 @@ namespace Ombi.Api.Emby.Models.Movie
{
public class EmbyMediastream
{
public string Codec { get; set; }
public string Language { get; set; }
public string TimeBase { get; set; }
public string CodecTimeBase { get; set; }
public string NalLengthSize { get; set; }
public bool IsInterlaced { get; set; }
public bool IsAVC { get; set; }
public int BitRate { get; set; }
public int BitDepth { get; set; }
public int RefFrames { get; set; }
public bool IsDefault { get; set; }
public bool IsForced { get; set; }
public int Height { get; set; }
public int Width { get; set; }
public float AverageFrameRate { get; set; }
public float RealFrameRate { get; set; }
public string Profile { get; set; }
public string Type { get; set; }
public string AspectRatio { get; set; }
public int Index { get; set; }
public bool IsExternal { get; set; }
public bool IsTextSubtitleStream { get; set; }
public bool SupportsExternalStream { get; set; }
public string PixelFormat { get; set; }
public int Level { get; set; }
public bool IsAnamorphic { get; set; }
public string DisplayTitle { get; set; }
public string ChannelLayout { get; set; }
public int Channels { get; set; }
public int SampleRate { get; set; }
}
}

@ -30,5 +30,6 @@ namespace Ombi.Api.Emby.Models.Movie
public int CriticRating { get; set; }
public string Overview { get; set; }
public EmbyProviderids ProviderIds { get; set; }
public EmbyMediastream[] MediaStreams { get; set; }
}
}

@ -157,7 +157,7 @@ namespace Ombi.Api.Jellyfin
request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview,ParentId" : "ProviderIds,ParentId");
request.AddQueryString("Fields", includeOverview ? "ProviderIds,MediaStreams Overview,ParentId" : "ProviderIds,ParentId,MediaStreams");
request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString());
if(!string.IsNullOrEmpty(parentIdFilder))

@ -2,35 +2,6 @@ namespace Ombi.Api.Jellyfin.Models.Movie
{
public class JellyfinMediastream
{
public string Codec { get; set; }
public string Language { get; set; }
public string TimeBase { get; set; }
public string CodecTimeBase { get; set; }
public string NalLengthSize { get; set; }
public bool IsInterlaced { get; set; }
public bool IsAVC { get; set; }
public int BitRate { get; set; }
public int BitDepth { get; set; }
public int RefFrames { get; set; }
public bool IsDefault { get; set; }
public bool IsForced { get; set; }
public int Height { get; set; }
public int Width { get; set; }
public float AverageFrameRate { get; set; }
public float RealFrameRate { get; set; }
public string Profile { get; set; }
public string Type { get; set; }
public string AspectRatio { get; set; }
public int Index { get; set; }
public bool IsExternal { get; set; }
public bool IsTextSubtitleStream { get; set; }
public bool SupportsExternalStream { get; set; }
public string PixelFormat { get; set; }
public int Level { get; set; }
public bool IsAnamorphic { get; set; }
public string DisplayTitle { get; set; }
public string ChannelLayout { get; set; }
public int Channels { get; set; }
public int SampleRate { get; set; }
}
}

@ -30,5 +30,6 @@ namespace Ombi.Api.Jellyfin.Models.Movie
public int CriticRating { get; set; }
public string Overview { get; set; }
public JellyfinProviderids ProviderIds { get; set; }
public JellyfinMediastream[] MediaStreams { get; set; }
}
}

@ -44,7 +44,10 @@ namespace Ombi.Api.Radarr.Models
public int id { get; set; }
}
public class MovieQuality
{
public V3.Quality quality { get; set; }
}
public class Moviefile
{
public int movieId { get; set; }
@ -54,7 +57,7 @@ namespace Ombi.Api.Radarr.Models
public DateTime dateAdded { get; set; }
public string sceneName { get; set; }
public int indexerFlags { get; set; }
public V3.Quality quality { get; set; }
public MovieQuality quality { get; set; }
public Mediainfo mediaInfo { get; set; }
public string originalFilePath { get; set; }
public bool qualityCutoffNotMet { get; set; }

@ -25,5 +25,4 @@
public int resolution { get; set; }
public string modifier { get; set; }
}
}

@ -9,6 +9,7 @@ using Ombi.Api.TheMovieDb;
using Ombi.Core.Engine;
using Ombi.Core.Models.Requests;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Services;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
@ -43,8 +44,9 @@ namespace Ombi.Core.Tests.Engine.V2
var ombiSettings = new Mock<ISettingsService<OmbiSettings>>();
var requestSubs = new Mock<IRepository<RequestSubscription>>();
var mediaCache = new Mock<IMediaCacheService>();
var featureService = new Mock<IFeatureService>();
_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);
logger.Object, userManager.Object, requestLogRepo.Object, cache.Object, ombiSettings.Object, requestSubs.Object, mediaCache.Object, featureService.Object);
}
[Test]

@ -83,7 +83,7 @@ namespace Ombi.Core.Tests.Engine
Assert.That(result.Result, Is.True);
VoteRepository.Verify(x => x.Add(It.Is<Votes>(c => c.UserId == "abc" && c.VoteType == type)), Times.Once);
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Never);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1, false), Times.Never);
}
public static IEnumerable<TestCaseData> VoteData
{
@ -129,7 +129,7 @@ namespace Ombi.Core.Tests.Engine
Assert.That(result.Result, Is.False);
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Never);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1, false), Times.Never);
}
public static IEnumerable<TestCaseData> AttemptedTwiceData
{
@ -175,7 +175,7 @@ namespace Ombi.Core.Tests.Engine
Assert.That(result.Result, Is.True);
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Once);
VoteRepository.Verify(x => x.Add(It.Is<Votes>(v => v.VoteType == type)), Times.Once);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1, false), Times.Never);
}
public static IEnumerable<TestCaseData> VoteConvertData
{

@ -10,6 +10,7 @@ using Ombi.Test.Common;
using System.Collections.Generic;
using Ombi.Store.Entities;
using System;
using Ombi.Core.Services;
namespace Ombi.Core.Tests.Rule.Request
{
@ -28,21 +29,23 @@ namespace Ombi.Core.Tests.Rule.Request
PrincipalMock = new Mock<IPrincipal>();
PrincipalMock.Setup(x => x.Identity.Name).Returns("abc");
FeatureService = new Mock<IFeatureService>();
UserManager = MockHelper.MockUserManager(_users);
Rule = new AutoApproveRule(PrincipalMock.Object, UserManager.Object);
Rule = new AutoApproveRule(PrincipalMock.Object, UserManager.Object, FeatureService.Object);
}
private AutoApproveRule Rule { get; set; }
private Mock<IPrincipal> PrincipalMock { get; set; }
private Mock<OmbiUserManager> UserManager { get; set; }
private Mock<IFeatureService> FeatureService { get; set; }
[Test]
public async Task Should_ReturnSuccess_WhenAdminAndRequestMovie()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Admin)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
Assert.True(result.Success);
@ -64,7 +67,7 @@ namespace Ombi.Core.Tests.Rule.Request
public async Task Should_ReturnSuccess_WhenAutoApproveMovieAndRequestMovie()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveMovie)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
Assert.True(result.Success);
@ -137,5 +140,17 @@ namespace Ombi.Core.Tests.Rule.Request
Assert.True(result.Success);
Assert.False(request.Approved);
}
[Test]
public async Task Should_ReturnFail_When4kRequestAndFeatureNotEnabled()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), It.IsAny<string>())).ReturnsAsync(false);
var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie, Is4kRequest = true };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.False(request.Approved4K);
}
}
}

@ -42,7 +42,42 @@ namespace Ombi.Core.Tests.Rule.Request
public async Task Should_ReturnSuccess_WhenRequestingMovieWithMovieRole()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestMovie)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
Assert.True(result.Success);
}
[Test]
public async Task Should_ReturnSuccess_WhenRequestingMovie4KWithMovieRole()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestMovie)).ReturnsAsync(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Request4KMovie)).ReturnsAsync(true);
var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie, Has4KRequest = true };
var result = await Rule.Execute(request);
Assert.True(result.Success);
}
[Test]
public async Task Should_ReturnFailure_WhenRequestingMovie4KWithMovieRole()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestMovie)).ReturnsAsync(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Request4KMovie)).ReturnsAsync(false);
var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie, Is4kRequest = true };
var result = await Rule.Execute(request);
Assert.False(result.Success);
Assert.False(string.IsNullOrEmpty(result.Message));
}
[Test]
public async Task Should_ReturnSuccess_WhenRequestingMovie4KWithAutoApprove()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestMovie)).ReturnsAsync(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveMovie)).ReturnsAsync(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Request4KMovie)).ReturnsAsync(false);
var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie, Has4KRequest = true };
var result = await Rule.Execute(request);
Assert.True(result.Success);
@ -52,7 +87,7 @@ namespace Ombi.Core.Tests.Rule.Request
public async Task Should_ReturnFail_WhenRequestingMovieWithoutMovieRole()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestMovie)).ReturnsAsync(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
Assert.False(result.Success);

@ -9,7 +9,9 @@ using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Rule.Rules;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Core.Services;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
@ -24,12 +26,14 @@ namespace Ombi.Core.Tests.Rule.Request
public void Setup()
{
ContextMock = new Mock<IMovieRequestRepository>();
Rule = new ExistingMovieRequestRule(ContextMock.Object);
FeatureService = new Mock<IFeatureService>();
Rule = new ExistingMovieRequestRule(ContextMock.Object, FeatureService.Object);
}
private ExistingMovieRequestRule Rule { get; set; }
private Mock<IMovieRequestRepository> ContextMock { get; set; }
private Mock<IFeatureService> FeatureService { get; set; }
[Test]
public async Task ExistingRequestRule_Movie_Has_Been_Requested_With_TheMovieDBId()
@ -96,5 +100,82 @@ namespace Ombi.Core.Tests.Rule.Request
Assert.That(result.Success, Is.True);
Assert.That(result.Message, Is.Null.Or.Empty);
}
[Test]
public async Task ExistingRequestRule_Movie_HasAlready4K_Request()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<MovieRequests>
{
new MovieRequests
{
TheMovieDbId = 2,
ImdbId = "2",
RequestType = RequestType.Movie,
Is4kRequest = true
}
}.AsQueryable().BuildMock().Object);
var o = new MovieRequests
{
TheMovieDbId = 2,
ImdbId = "1",
Has4KRequest = true
};
var result = await Rule.Execute(o);
Assert.That(result.Success, Is.False);
Assert.That(result.Message, Is.Not.Empty);
}
[Test]
public async Task ExistingRequestRule_Movie_4K_Request()
{
FeatureService.Setup(x => x.FeatureEnabled(FeatureNames.Movie4KRequests)).ReturnsAsync(true);
ContextMock.Setup(x => x.GetAll()).Returns(new List<MovieRequests>
{
new MovieRequests
{
TheMovieDbId = 2,
ImdbId = "2",
RequestType = RequestType.Movie,
Is4kRequest = false
}
}.AsQueryable().BuildMock().Object);
var o = new MovieRequests
{
TheMovieDbId = 2,
ImdbId = "1",
Is4kRequest = true
};
var result = await Rule.Execute(o);
Assert.That(result.Success, Is.True);
Assert.That(result.Message, Is.Null.Or.Empty);
}
[Test]
public async Task ExistingRequestRule_Movie_4K_Request_FeatureNotEnabled()
{
FeatureService.Setup(x => x.FeatureEnabled(FeatureNames.Movie4KRequests)).ReturnsAsync(false);
ContextMock.Setup(x => x.GetAll()).Returns(new List<MovieRequests>
{
new MovieRequests
{
TheMovieDbId = 2,
ImdbId = "2",
RequestType = RequestType.Movie,
Is4kRequest = false
}
}.AsQueryable().BuildMock().Object);
var o = new MovieRequests
{
TheMovieDbId = 2,
ImdbId = "1",
Is4kRequest = true
};
var result = await Rule.Execute(o);
Assert.That(result.Success, Is.False);
Assert.That(result.Message, Is.Not.Null);
}
}
}

@ -35,7 +35,48 @@ namespace Ombi.Core.Tests.Rule.Search
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings());
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
ProviderId = "123"
TheMovieDbId = "123",
Quality = "1"
});
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.True(search.Available);
}
[Test]
public async Task Movie_ShouldBe_Available_WhenFoundInEmby_4K()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings());
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
TheMovieDbId = "123",
Has4K = true
});
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.True(search.Available4K);
Assert.False(search.Available);
}
[Test]
public async Task Movie_ShouldBe_Available_WhenFoundInEmby_Both()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings());
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
TheMovieDbId = "123",
Has4K = true,
Quality = "1"
});
var search = new SearchMovieViewModel()
{
@ -44,6 +85,7 @@ namespace Ombi.Core.Tests.Rule.Search
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.True(search.Available4K);
Assert.True(search.Available);
}

@ -30,13 +30,14 @@ namespace Ombi.Core.Tests.Rule.Search
[Test]
public async Task ShouldBe_Requested_WhenExisitngMovie()
public async Task ShouldBe_Requested_WhenExistingMovie()
{
var list = new MovieRequests
{
TheMovieDbId = 123,
Approved = true,
RequestType = RequestType.Movie
RequestType = RequestType.Movie,
RequestedDate = System.DateTime.Now,
};
MovieMock.Setup(x => x.GetRequestAsync(123)).ReturnsAsync(list);

@ -35,7 +35,48 @@ namespace Ombi.Core.Tests.Rule.Search
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings());
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new JellyfinContent
{
ProviderId = "123"
TheMovieDbId = "123",
Quality = "1080"
});
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.True(search.Available);
}
[Test]
public async Task Movie_ShouldBe_Available_WhenFoundInJellyfin_4K()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings());
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new JellyfinContent
{
TheMovieDbId = "123",
Has4K = true
});
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.False(search.Available);
Assert.True(search.Available4K);
}
[Test]
public async Task Movie_ShouldBe_Available_WhenFoundInJellyfin_Both()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings());
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new JellyfinContent
{
TheMovieDbId = "123",
Has4K = true,
Quality = "1"
});
var search = new SearchMovieViewModel()
{
@ -45,6 +86,7 @@ namespace Ombi.Core.Tests.Rule.Search
Assert.True(result.Success);
Assert.True(search.Available);
Assert.True(search.Available4K);
}
[Test]
@ -66,7 +108,7 @@ namespace Ombi.Core.Tests.Rule.Search
});
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new JellyfinContent
{
ProviderId = "123",
TheMovieDbId = "123",
JellyfinId = 1.ToString()
});
var search = new SearchMovieViewModel()

@ -28,25 +28,67 @@ namespace Ombi.Core.Tests.Rule.Search
{
var list = new List<RadarrCache>(){new RadarrCache
{
TheMovieDbId = 123
TheMovieDbId = 123,
HasRegular = true
}}.AsQueryable();
ContextMock.Setup(x => x.GetAll()).Returns(list);
var request = new SearchMovieViewModel { Id = 123 };
var result =await Rule.Execute(request);
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.True(request.Approved);
}
[Test]
public async Task Should_ReturnAvailabl_WhenMovieIsInRadarr_4K()
{
var list = new List<RadarrCache>(){new RadarrCache
{
TheMovieDbId = 123,
Has4K = true,
HasFile = true
}}.AsQueryable();
ContextMock.Setup(x => x.GetAll()).Returns(list);
var request = new SearchMovieViewModel { Id = 123 };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Available);
Assert.True(request.Available4K);
}
[Test]
public async Task Should_ReturnAvailable_WhenMovieIsInRadarr_Both()
{
var list = new List<RadarrCache>(){new RadarrCache
{
TheMovieDbId = 123,
Has4K = true,
HasRegular = true,
HasFile = true
}}.AsQueryable();
ContextMock.Setup(x => x.GetAll()).Returns(list);
var request = new SearchMovieViewModel { Id = 123 };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.True(request.Available);
Assert.True(request.Available4K);
}
[Test]
public async Task Should_ReturnNotApproved_WhenMovieIsNotInRadarr()
{
var list = DbHelper.GetQueryableMockDbSet(new RadarrCache
{
TheMovieDbId = 000012
TheMovieDbId = 000012,
});
ContextMock.Setup(x => x.GetAll()).Returns(list);

@ -18,9 +18,9 @@ namespace Ombi.Core.Engine.Interfaces
Task RemoveAllMovieRequests();
Task<MovieRequests> GetRequest(int requestId);
Task<MovieRequests> UpdateMovieRequest(MovieRequests request);
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
Task<RequestEngineResult> ApproveMovieById(int requestId);
Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason);
Task<RequestEngineResult> ApproveMovie(MovieRequests request, bool is4K);
Task<RequestEngineResult> ApproveMovieById(int requestId, bool is4K);
Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason, bool is4K);
Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder);
Task<RequestsViewModel<MovieRequests>> GetUnavailableRequests(int count, int position, string sortProperty,

@ -19,11 +19,11 @@ namespace Ombi.Core.Engine.Interfaces
Task<IEnumerable<T>> GetRequests();
Task<bool> UserHasRequest(string userId);
Task<RequestEngineResult> MarkUnavailable(int modelId);
Task<RequestEngineResult> MarkAvailable(int modelId);
Task<RequestEngineResult> MarkUnavailable(int modelId, bool is4K);
Task<RequestEngineResult> MarkAvailable(int modelId, bool is4K);
Task<int> GetTotal();
Task UnSubscribeRequest(int requestId, RequestType type);
Task SubscribeToRequest(int requestId, RequestType type);
Task<RequestEngineResult> ReProcessRequest(int requestId, CancellationToken cancellationToken);
Task<RequestEngineResult> ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken);
}
}

@ -22,6 +22,7 @@ using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Core.Models;
using System.Threading;
using Ombi.Core.Services;
namespace Ombi.Core.Engine
{
@ -30,7 +31,8 @@ namespace Ombi.Core.Engine
public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger<MovieRequestEngine> log,
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache,
ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, IMediaCacheService mediaCacheService)
ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, IMediaCacheService mediaCacheService,
IFeatureService featureService)
: base(user, requestService, r, manager, cache, ombiSettings, sub)
{
MovieApi = movieApi;
@ -39,6 +41,7 @@ namespace Ombi.Core.Engine
Logger = log;
_requestLog = rl;
_mediaCacheService = mediaCacheService;
_featureService = featureService;
}
private IMovieDbApi MovieApi { get; }
@ -47,6 +50,7 @@ namespace Ombi.Core.Engine
private ILogger<MovieRequestEngine> Logger { get; }
private readonly IRepository<RequestLog> _requestLog;
private readonly IMediaCacheService _mediaCacheService;
private readonly IFeatureService _featureService;
/// <summary>
/// Requests the movie.
@ -72,7 +76,8 @@ namespace Ombi.Core.Engine
var userDetails = await GetUser();
var canRequestOnBehalf = model.RequestOnBehalf.HasValue();
var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin);
var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser)
|| await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin);
if (canRequestOnBehalf && !isAdmin)
{
return new RequestEngineResult
@ -93,27 +98,53 @@ namespace Ombi.Core.Engine
};
}
var requestModel = new MovieRequests
var is4kFeatureEnabled = await _featureService.FeatureEnabled(FeatureNames.Movie4KRequests);
var is4kRequest = is4kFeatureEnabled && model.Is4kRequest;
MovieRequests requestModel;
bool isExisting = false;
// Do we already have a request? 4k or non 4k
var existingRequest = await MovieRepository.GetRequestAsync(movieInfo.Id);
if (existingRequest != null && is4kFeatureEnabled)
{
TheMovieDbId = movieInfo.Id,
RequestType = RequestType.Movie,
Overview = movieInfo.Overview,
ImdbId = movieInfo.ImdbId,
PosterPath = PosterPathHelper.FixPosterPath(movieInfo.PosterPath),
Title = movieInfo.Title,
ReleaseDate = !string.IsNullOrEmpty(movieInfo.ReleaseDate)
? DateTime.Parse(movieInfo.ReleaseDate)
: DateTime.MinValue,
Status = movieInfo.Status,
RequestedDate = DateTime.UtcNow,
Approved = false,
RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id,
Background = movieInfo.BackdropPath,
LangCode = model.LanguageCode,
RequestedByAlias = model.RequestedByAlias,
RootPathOverride = model.RootFolderOverride.GetValueOrDefault(),
QualityOverride = model.QualityPathOverride.GetValueOrDefault()
};
if (model.Is4kRequest)
{
existingRequest.Is4kRequest = true;
existingRequest.RequestedDate4k = DateTime.Now;
}
else
{
existingRequest.RequestedDate = DateTime.Now;
}
isExisting = true;
requestModel = existingRequest;
}
else
{
requestModel = new MovieRequests
{
TheMovieDbId = movieInfo.Id,
RequestType = RequestType.Movie,
Overview = movieInfo.Overview,
ImdbId = movieInfo.ImdbId,
PosterPath = PosterPathHelper.FixPosterPath(movieInfo.PosterPath),
Title = movieInfo.Title,
ReleaseDate = !string.IsNullOrEmpty(movieInfo.ReleaseDate)
? DateTime.Parse(movieInfo.ReleaseDate)
: DateTime.MinValue,
Status = movieInfo.Status,
RequestedDate = model.Is4kRequest ? DateTime.MinValue : DateTime.Now,
Approved = false,
RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id,
Background = movieInfo.BackdropPath,
LangCode = model.LanguageCode,
RequestedByAlias = model.RequestedByAlias,
RootPathOverride = model.RootFolderOverride.GetValueOrDefault(),
QualityOverride = model.QualityPathOverride.GetValueOrDefault(),
RequestedDate4k = model.Is4kRequest ? DateTime.Now : DateTime.MinValue,
Is4kRequest = model.Is4kRequest
};
}
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
requestModel.DigitalReleaseDate = usDates?.ReleaseDate
@ -132,10 +163,10 @@ namespace Ombi.Core.Engine
if (requestModel.Approved) // The rules have auto approved this
{
var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf);
var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf, isExisting, is4kRequest);
if (requestEngineResult.Result)
{
var result = await ApproveMovie(requestModel);
var result = await ApproveMovie(requestModel, model.Is4kRequest);
if (result.IsError)
{
Logger.LogWarning("Tried auto sending movie but failed. Message: {0}", result.Message);
@ -153,7 +184,7 @@ namespace Ombi.Core.Engine
// If there are no providers then it's successful but movie has not been sent
}
return await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf);
return await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf, isExisting, is4kRequest);
}
@ -508,13 +539,13 @@ namespace Ombi.Core.Engine
return results;
}
public async Task<RequestEngineResult> ApproveMovieById(int requestId)
public async Task<RequestEngineResult> ApproveMovieById(int requestId, bool is4K)
{
var request = await MovieRepository.Find(requestId);
return await ApproveMovie(request);
return await ApproveMovie(request, is4K);
}
public async Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason)
public async Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason, bool is4K)
{
var request = await MovieRepository.Find(modelId);
if (request == null)
@ -525,8 +556,16 @@ namespace Ombi.Core.Engine
};
}
request.Denied = true;
request.DeniedReason = denyReason;
if (is4K)
{
request.Denied4K = true;
request.DeniedReason4K = denyReason;
}
else
{
request.Denied = true;
request.DeniedReason = denyReason;
}
await MovieRepository.Update(request);
await _mediaCacheService.Purge();
@ -540,7 +579,7 @@ namespace Ombi.Core.Engine
};
}
public async Task<RequestEngineResult> ApproveMovie(MovieRequests request)
public async Task<RequestEngineResult> ApproveMovie(MovieRequests request, bool is4K)
{
if (request == null)
{
@ -550,9 +589,18 @@ namespace Ombi.Core.Engine
};
}
request.MarkedAsApproved = DateTime.Now;
request.Approved = true;
request.Denied = false;
if (is4K)
{
request.MarkedAsApproved4K = DateTime.Now;
request.Approved4K = true;
request.Denied4K = false;
}
else
{
request.MarkedAsApproved = DateTime.Now;
request.Approved = true;
request.Denied = false;
}
await MovieRepository.Update(request);
var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification, string.Empty);
@ -562,7 +610,7 @@ namespace Ombi.Core.Engine
}
await _mediaCacheService.Purge();
return await ProcessSendingMovie(request);
return await ProcessSendingMovie(request, is4K);
}
public async Task<RequestEngineResult> RequestCollection(int collectionId, CancellationToken cancellationToken)
@ -590,11 +638,11 @@ namespace Ombi.Core.Engine
return new RequestEngineResult { Result = true, Message = $"The collection {collections.name} has been successfully added!", RequestId = results.FirstOrDefault().RequestId };
}
private async Task<RequestEngineResult> ProcessSendingMovie(MovieRequests request)
private async Task<RequestEngineResult> ProcessSendingMovie(MovieRequests request, bool is4K)
{
if (request.Approved)
{
var result = await Sender.Send(request);
var result = await Sender.Send(request, is4K);
if (result.Success && result.Sent)
{
return new RequestEngineResult
@ -662,7 +710,7 @@ namespace Ombi.Core.Engine
var result = await CheckCanManageRequest(request);
if (result.IsError)
return result;
await MovieRepository.Delete(request);
await _mediaCacheService.Purge();
return new RequestEngineResult
@ -683,7 +731,7 @@ namespace Ombi.Core.Engine
return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId);
}
public async Task<RequestEngineResult> ReProcessRequest(int requestId, CancellationToken cancellationToken)
public async Task<RequestEngineResult> ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken)
{
var request = await MovieRepository.Find(requestId);
if (request == null)
@ -695,10 +743,10 @@ namespace Ombi.Core.Engine
};
}
return await ProcessSendingMovie(request);
return await ProcessSendingMovie(request, is4K);
}
public async Task<RequestEngineResult> MarkUnavailable(int modelId)
public async Task<RequestEngineResult> MarkUnavailable(int modelId, bool is4K)
{
var request = await MovieRepository.Find(modelId);
if (request == null)
@ -709,7 +757,14 @@ namespace Ombi.Core.Engine
};
}
request.Available = false;
if (is4K)
{
request.Available4K = false;
}
else
{
request.Available = false;
}
await MovieRepository.Update(request);
await _mediaCacheService.Purge();
@ -720,7 +775,7 @@ namespace Ombi.Core.Engine
};
}
public async Task<RequestEngineResult> MarkAvailable(int modelId)
public async Task<RequestEngineResult> MarkAvailable(int modelId, bool is4K)
{
var request = await MovieRepository.Find(modelId);
if (request == null)
@ -730,9 +785,16 @@ namespace Ombi.Core.Engine
ErrorMessage = "Request does not exist"
};
}
request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
if (!is4K)
{
request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
}
else
{
request.Available4K = true;
request.MarkedAsAvailable4K = DateTime.Now;
}
await NotificationHelper.Notify(request, NotificationType.RequestAvailable);
await MovieRepository.Update(request);
await _mediaCacheService.Purge();
@ -744,9 +806,20 @@ namespace Ombi.Core.Engine
};
}
private async Task<RequestEngineResult> AddMovieRequest(MovieRequests model, string movieName, string requestOnBehalf)
private async Task<RequestEngineResult> AddMovieRequest(MovieRequests model, string movieName, string requestOnBehalf, bool isExisting, bool is4k)
{
await MovieRepository.Add(model);
if (is4k)
{
model.Has4KRequest = true;
}
if (!isExisting)
{
await MovieRepository.Add(model);
}
else
{
await MovieRepository.Update(model);
}
var result = await RunSpecificRule(model, SpecificRules.CanSendNotification, requestOnBehalf);
if (result.Success)

@ -793,7 +793,7 @@ namespace Ombi.Core.Engine
return await TvRepository.GetChild().AnyAsync(x => x.RequestedUserId == userId);
}
public async Task<RequestEngineResult> MarkUnavailable(int modelId)
public async Task<RequestEngineResult> MarkUnavailable(int modelId, bool is4K)
{
var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == modelId);
if (request == null)
@ -821,7 +821,7 @@ namespace Ombi.Core.Engine
};
}
public async Task<RequestEngineResult> MarkAvailable(int modelId)
public async Task<RequestEngineResult> MarkAvailable(int modelId, bool is4K)
{
ChildRequests request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == modelId);
if (request == null)
@ -918,7 +918,7 @@ namespace Ombi.Core.Engine
return await AfterRequest(model.ChildRequests.FirstOrDefault(), requestOnBehalf);
}
public async Task<RequestEngineResult> ReProcessRequest(int requestId, CancellationToken cancellationToken)
public async Task<RequestEngineResult> ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken)
{
var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId, cancellationToken);
if (request == null)

@ -406,6 +406,13 @@ namespace Ombi.Core.Engine.V2
mapped.Subscribed = viewMovie.Subscribed;
mapped.ShowSubscribe = viewMovie.ShowSubscribe;
mapped.DigitalReleaseDate = viewMovie.DigitalReleaseDate;
mapped.RequestedDate4k = viewMovie.RequestedDate4k;
mapped.Approved4K = viewMovie.Approved4K;
mapped.Available4K = viewMovie.Available4K;
mapped.Denied4K = viewMovie.Denied4K;
mapped.DeniedReason4K = viewMovie.DeniedReason4K;
mapped.Has4KRequest = viewMovie.Has4KRequest;
return mapped;
}

@ -193,7 +193,7 @@ namespace Ombi.Core.Engine
case RequestType.Movie:
if (totalVotes >= voteSettings.MovieVoteMax)
{
result = await _movieRequestEngine.ApproveMovieById(requestId);
result = await _movieRequestEngine.ApproveMovieById(requestId, false);
}
break;
case RequestType.Album:

@ -34,6 +34,8 @@ namespace Ombi.Core.Models.Requests
public int TheMovieDbId { get; set; }
public string LanguageCode { get; set; } = "en";
public bool Is4kRequest { get; set; }
/// <summary>
/// This is only set from a HTTP Header
/// </summary>

@ -28,5 +28,14 @@ namespace Ombi.Core.Models.Search
public override RequestType Type => RequestType.Movie;
public ReleaseDatesDto ReleaseDates { get; set; }
public DateTime? DigitalReleaseDate { get; set; }
public bool Has4KRequest { get; set; }
public bool Approved4K { get; set; }
public DateTime MarkedAsApproved4K { get; set; }
public DateTime RequestedDate4k { get; set; }
public bool Available4K { get; set; }
public DateTime? MarkedAsAvailable4K { get; set; }
public bool? Denied4K { get; set; }
public DateTime MarkedAsDenied4K { get; set; }
public string DeniedReason4K { get; set; }
}
}

@ -41,6 +41,15 @@ namespace Ombi.Core.Models.Search.V2
public Recommendations Recommendations { get; set; }
public ExternalIds ExternalIds { get; set; }
public Keywords Keywords { get; set; }
public bool Has4KRequest { get; set; }
public bool Approved4K { get; set; }
public DateTime MarkedAsApproved4K { get; set; }
public DateTime RequestedDate4k { get; set; }
public bool Available4K { get; set; }
public DateTime? MarkedAsAvailable4K { get; set; }
public bool? Denied4K { get; set; }
public DateTime MarkedAsDenied4K { get; set; }
public string DeniedReason4K { get; set; }
}
public class Keywords
{

@ -5,7 +5,9 @@ using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Services;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
@ -13,14 +15,16 @@ namespace Ombi.Core.Rule.Rules.Request
{
public class AutoApproveRule : BaseRequestRule, IRules<BaseRequest>
{
public AutoApproveRule(IPrincipal principal, OmbiUserManager um)
public AutoApproveRule(IPrincipal principal, OmbiUserManager um, IFeatureService featureService)
{
User = principal;
_manager = um;
_featureService = featureService;
}
private IPrincipal User { get; }
private readonly OmbiUserManager _manager;
private readonly IFeatureService _featureService;
public async Task<RuleResult> Execute(BaseRequest obj)
{
@ -28,17 +32,40 @@ namespace Ombi.Core.Rule.Rules.Request
var user = await _manager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username);
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin) || user.IsSystemUser)
{
obj.Approved = true;
if (obj is MovieRequests movie)
{
await Check4K(movie);
}
else
{
obj.Approved = true;
}
return Success();
}
if (obj.RequestType == RequestType.Movie && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
obj.Approved = true;
{
var movie = (MovieRequests)obj;
await Check4K(movie);
}
if (obj.RequestType == RequestType.TvShow && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
obj.Approved = true;
if (obj.RequestType == RequestType.Album && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
obj.Approved = true;
return Success(); // We don't really care, we just don't set the obj to approve
}
private async Task Check4K(MovieRequests movie)
{
var featureEnabled = await _featureService.FeatureEnabled(FeatureNames.Movie4KRequests);
if (movie.Is4kRequest && featureEnabled)
{
movie.Approved4K = true;
}
else
{
movie.Approved = true;
}
}
}
}

@ -33,8 +33,23 @@ namespace Ombi.Core.Rule.Rules.Request
if (obj.RequestType == RequestType.Movie)
{
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
return Success();
var movie = (MovieRequests)obj;
var hasAutoApprove = await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie);
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || hasAutoApprove)
{
if (movie.Is4kRequest && !hasAutoApprove)
{
var has4kPermission = await _manager.IsInRoleAsync(user, OmbiRoles.Request4KMovie);
if (has4kPermission)
{
return Success();
}
}
else
{
return Success();
}
}
return Fail(ErrorCode.NoPermissionsRequestMovie, "You do not have permissions to Request a Movie");
}

@ -1,21 +1,24 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Core.Engine;
using Ombi.Store.Repository.Requests;
using Ombi.Core.Services;
using Ombi.Settings.Settings.Models;
namespace Ombi.Core.Rule.Rules.Request
{
public class ExistingMovieRequestRule : BaseRequestRule, IRules<BaseRequest>
{
public ExistingMovieRequestRule(IMovieRequestRepository movie)
private readonly IFeatureService _featureService;
public ExistingMovieRequestRule(IMovieRequestRepository movie, IFeatureService featureService)
{
Movie = movie;
_featureService = featureService;
}
private IMovieRequestRepository Movie { get; }
@ -35,7 +38,7 @@ namespace Ombi.Core.Rule.Rules.Request
var existing = await movieRequests.FirstOrDefaultAsync(x => x.TheMovieDbId == movie.TheMovieDbId);
if (existing != null) // Do we already have a request for this?
{
found = true;
found = await Check4KRequests(movie, existing);
}
if (!found && movie.ImdbId.HasValue())
@ -45,15 +48,30 @@ namespace Ombi.Core.Rule.Rules.Request
x.ImdbId == movie.ImdbId);
if (existing != null)
{
found = true;
found = await Check4KRequests(movie, existing);
}
}
if(found)
if (found)
{
return Fail(ErrorCode.AlreadyRequested, $"\"{obj.Title}\" has already been requested");
}
}
return Success();
}
private async Task<bool> Check4KRequests(MovieRequests movie, MovieRequests existing)
{
var featureEnabled = await _featureService.FeatureEnabled(FeatureNames.Movie4KRequests);
if (movie.Is4kRequest && existing.Has4KRequest && featureEnabled)
{
return true;
}
if (!movie.Is4kRequest && !existing.Has4KRequest || !featureEnabled)
{
return true;
}
return false;
}
}
}

@ -63,11 +63,29 @@ namespace Ombi.Core.Rule.Rules.Search
}
}
}
if (item != null)
{
obj.Available = true;
obj.EmbyUrl = item.Url;
if (obj is SearchMovieViewModel movie)
{
if (item.Has4K)
{
movie.Available4K = true;
obj.EmbyUrl = item.Url;
}
if (item.Quality.HasValue())
{
obj.Available = true;
obj.EmbyUrl = item.Url;
obj.Quality = item.Quality;
}
}
else
{
obj.Available = true;
obj.EmbyUrl = item.Url;
}
if (obj.Type == RequestType.TvShow)
{

@ -26,17 +26,27 @@ namespace Ombi.Core.Rule.Rules.Search
public async Task<RuleResult> Execute(SearchViewModel obj)
{
if (obj.Type == RequestType.Movie)
if (obj is SearchMovieViewModel movie)
{
var movieRequests = await Movie.GetRequestAsync(obj.Id);
if (movieRequests != null) // Do we already have a request for this?
{
obj.Requested = true;
obj.RequestId = movieRequests.Id;
obj.Approved = movieRequests.Approved;
obj.Denied = movieRequests.Denied ?? false;
obj.DeniedReason = movieRequests.DeniedReason;
obj.Available = movieRequests.Available;
// If the RequestDate is a min value, that means there's only a 4k request
movie.Requested = movieRequests.RequestedDate != DateTime.MinValue;
movie.RequestId = movieRequests.Id;
movie.Approved = movieRequests.Approved;
movie.Denied = movieRequests.Denied ?? false;
movie.DeniedReason = movieRequests.DeniedReason;
movie.Available = movieRequests.Available;
movie.Has4KRequest = movieRequests.Has4KRequest;
movie.RequestedDate4k = movieRequests.RequestedDate4k;
movie.Approved4K = movieRequests.Approved4K;
movie.Available4K = movieRequests.Available4K;
movie.Denied4K = movieRequests.Denied4K;
movie.DeniedReason4K = movieRequests.DeniedReason4K;
movie.MarkedAsApproved4K = movieRequests.MarkedAsApproved4K;
movie.MarkedAsAvailable4K = movieRequests.MarkedAsAvailable4K;
movie.MarkedAsDenied4K = movieRequests.MarkedAsDenied4K;
return Success();
}

@ -80,8 +80,26 @@ namespace Ombi.Core.Rule.Rules.Search
obj.TheMovieDbId = obj.Id.ToString();
useTheMovieDb = true;
}
obj.Available = true;
obj.JellyfinUrl = item.Url;
if (obj is SearchMovieViewModel movie)
{
if (item.Has4K)
{
movie.Available4K = true;
obj.JellyfinUrl = item.Url;
}
if (item.Quality.HasValue())
{
obj.Available = true;
obj.EmbyUrl = item.Url;
obj.Quality = item.Quality;
}
}
else
{
obj.Available = true;
obj.JellyfinUrl = item.Url;
}
if (obj.Type == RequestType.TvShow)
{

@ -89,7 +89,25 @@ namespace Ombi.Core.Rule.Rules.Search
obj.TheMovieDbId = obj.Id.ToString();
useTheMovieDb = true;
}
obj.Available = true;
if (obj is SearchMovieViewModel movie)
{
if (item.Has4K)
{
movie.Available4K = true;
}
if (item.Quality.HasValue())
{
obj.Available = true;
obj.Quality = item.Quality;
}
}
else
{
obj.Available = true;
}
if (item.Url.StartsWith("http"))
{
obj.PlexUrl = item.Url;
@ -99,11 +117,9 @@ namespace Ombi.Core.Rule.Rules.Search
// legacy content
obj.PlexUrl = PlexHelper.BuildPlexMediaUrl(item.Url, host);
}
obj.Quality = item.Quality;
if (obj.Type == RequestType.TvShow)
if (obj is SearchTvShowViewModel search)
{
var search = (SearchTvShowViewModel)obj;
// Let's go through the episodes now
if (search.SeasonRequests.Any())
{

@ -18,16 +18,23 @@ namespace Ombi.Core.Rule.Rules.Search
public Task<RuleResult> Execute(SearchViewModel obj)
{
if (obj.Type == RequestType.Movie)
if (obj is SearchMovieViewModel movie)
{
// Check if it's in Radarr
var result = _db.GetAll().FirstOrDefault(x => x.TheMovieDbId == obj.Id);
if (result != null)
{
obj.Approved = true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something?
movie.Approved = true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something?
if (result.HasFile)
{
obj.Available = true;
if (result.Has4K)
{
movie.Available4K = true;
}
if (result.HasRegular)
{
movie.Available = true;
}
}
}
}

@ -6,6 +6,6 @@ namespace Ombi.Core
{
public interface IMovieSender
{
Task<SenderResult> Send(MovieRequests model);
Task<SenderResult> Send(MovieRequests model, bool is4K);
}
}

@ -20,13 +20,12 @@ namespace Ombi.Core.Senders
{
public class MovieSender : IMovieSender
{
public MovieSender(ISettingsService<RadarrSettings> radarrSettings, IRadarrApi api, ILogger<MovieSender> log,
public MovieSender(ISettingsService<RadarrSettings> radarrSettings, ISettingsService<Radarr4KSettings> radarr4kSettings, ILogger<MovieSender> log,
ISettingsService<DogNzbSettings> dogSettings, IDogNzbApi dogApi, ISettingsService<CouchPotatoSettings> cpSettings,
ICouchPotatoApi cpApi, IRepository<UserQualityProfiles> userProfiles, IRepository<RequestQueue> requestQueue, INotificationHelper notify,
IRadarrV3Api radarrV3Api)
{
_radarrSettings = radarrSettings;
_radarrV2Api = api;
_log = log;
_dogNzbSettings = dogSettings;
_dogNzbApi = dogApi;
@ -36,10 +35,11 @@ namespace Ombi.Core.Senders
_requestQueuRepository = requestQueue;
_notificationHelper = notify;
_radarrV3Api = radarrV3Api;
_radarr4KSettings = radarr4kSettings;
}
private readonly ISettingsService<RadarrSettings> _radarrSettings;
private readonly IRadarrApi _radarrV2Api;
private readonly ISettingsService<Radarr4KSettings> _radarr4KSettings;
private readonly ILogger<MovieSender> _log;
private readonly IDogNzbApi _dogNzbApi;
private readonly ISettingsService<DogNzbSettings> _dogNzbSettings;
@ -50,16 +50,24 @@ namespace Ombi.Core.Senders
private readonly INotificationHelper _notificationHelper;
private readonly IRadarrV3Api _radarrV3Api;
public async Task<SenderResult> Send(MovieRequests model)
public async Task<SenderResult> Send(MovieRequests model, bool is4K)
{
try
{
var cpSettings = await _couchPotatoSettings.GetSettingsAsync();
//var watcherSettings = await WatcherSettings.GetSettingsAsync();
var radarrSettings = await _radarrSettings.GetSettingsAsync();
RadarrSettings radarrSettings;
if (is4K)
{
radarrSettings = await _radarr4KSettings.GetSettingsAsync();
}
else
{
radarrSettings = await _radarrSettings.GetSettingsAsync();
}
if (radarrSettings.Enabled)
{
return await SendToRadarr(model, radarrSettings);
return await SendToRadarr(model, is4K, radarrSettings);
}
var dogSettings = await _dogNzbSettings.GetSettingsAsync();
@ -123,7 +131,7 @@ namespace Ombi.Core.Senders
return await _dogNzbApi.AddMovie(settings.ApiKey, id);
}
private async Task<SenderResult> SendToRadarr(MovieRequests model, RadarrSettings settings)
private async Task<SenderResult> SendToRadarr(MovieRequests model, bool is4K, RadarrSettings settings)
{
var qualityToUse = int.Parse(settings.DefaultQualityProfile);

@ -0,0 +1,28 @@
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using System.Linq;
using System.Threading.Tasks;
namespace Ombi.Core.Services
{
public interface IFeatureService
{
Task<bool> FeatureEnabled(string featureName);
}
public class FeatureService : IFeatureService
{
private readonly ISettingsService<FeatureSettings> _featureSettings;
public FeatureService(ISettingsService<FeatureSettings> featureSettings)
{
_featureSettings = featureSettings;
}
public async Task<bool> FeatureEnabled(string featureName)
{
var settings = await _featureSettings.GetSettingsAsync();
return settings.Features?.Where(x => x.Name.Equals(featureName, System.StringComparison.InvariantCultureIgnoreCase)).Select(x => x.Enabled)?.FirstOrDefault() ?? false;
}
}
}

@ -224,6 +224,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ITelegramNotification, TelegramNotification>();
services.AddTransient<ILegacyMobileNotification, LegacyMobileNotification>();
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();
services.AddScoped<IFeatureService, FeatureService>();
}
public static void RegisterJobs(this IServiceCollection services)

@ -16,5 +16,6 @@
public const string ReceivesNewsletter = nameof(ReceivesNewsletter);
public const string ManageOwnRequests = nameof(ManageOwnRequests);
public const string EditCustomPage = nameof(EditCustomPage);
public const string Request4KMovie = nameof(Request4KMovie);
}
}

@ -133,12 +133,88 @@ namespace Ombi.Schedule.Tests
}
}));
await _subject.MovieLoop(new PlexServers { Ip = "http://test.com/", Port = 80}, content, contentToAdd, contentProcessed);
await _subject.MovieLoop(new PlexServers { Ip = "http://test.com/", Port = 80 }, content, contentToAdd, contentProcessed);
var first = contentToAdd.First();
Assert.That(first.ImdbId, Is.EqualTo("tt0322259"));
_mocker.Verify<IPlexApi>(x => x.GetMetadata(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Once);
}
[Test]
public async Task UpdatesExistingMovieWhen_WeFindAnotherQuality()
{
var content = new Mediacontainer
{
Metadata = new[]
{
new Metadata
{
ratingKey = 11,
title = "test1",
year = 2021,
type = "movie",
Media = new Medium[1]
{
new Medium
{
videoResolution = "4k"
}
}
},
}
};
var contentToAdd = new HashSet<PlexServerContent>();
var contentProcessed = new Dictionary<int, int>();
_mocker.Setup<IPlexContentRepository>(x =>
x.GetFirstContentByCustom(It.IsAny<Expression<Func<PlexServerContent, bool>>>()))
.Returns(Task.FromResult(new PlexServerContent
{
Quality = "1080"
}));
await _subject.MovieLoop(new PlexServers(), content, contentToAdd, contentProcessed);
Assert.That(contentToAdd, Is.Empty);
_mocker.Verify<IPlexContentRepository>(x => x.Update(It.Is<PlexServerContent>(x => x.Quality == "1080" && x.Has4K)), Times.Once);
}
[Test]
public async Task DoesNotUpdatesExistingMovieWhen_WeFindSameQuality()
{
var content = new Mediacontainer
{
Metadata = new[]
{
new Metadata
{
ratingKey = 11,
title = "test1",
year = 2021,
type = "movie",
Media = new Medium[1]
{
new Medium
{
videoResolution = "1080"
}
}
},
}
};
var contentToAdd = new HashSet<PlexServerContent>();
var contentProcessed = new Dictionary<int, int>();
_mocker.Setup<IPlexContentRepository>(x =>
x.GetFirstContentByCustom(It.IsAny<Expression<Func<PlexServerContent, bool>>>()))
.Returns(Task.FromResult(new PlexServerContent
{
Quality = "1080"
}));
await _subject.MovieLoop(new PlexServers(), content, contentToAdd, contentProcessed);
Assert.That(contentToAdd, Is.Empty);
_mocker.Verify<IPlexContentRepository>(x => x.Update(It.IsAny<PlexServerContent>()), Times.Never);
}
}
}

@ -53,10 +53,11 @@ namespace Ombi.Schedule.Jobs.Emby
private async Task ProcessMovies()
{
var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available);
var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available || (!x.Available4K && x.Has4KRequest));
foreach (var movie in movies)
{
var has4kRequest = movie.Has4KRequest;
EmbyContent embyContent = null;
if (movie.TheMovieDbId > 0)
{
@ -75,8 +76,18 @@ namespace Ombi.Schedule.Jobs.Emby
_log.LogInformation("We have found the request {0} on Emby, sending the notification", movie?.Title ?? string.Empty);
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
if (has4kRequest && embyContent.Has4K)
{
movie.Available4K = true;
movie.MarkedAsAvailable4K = DateTime.Now;
}
// If we have a non-4k versison then mark as available
if (embyContent.Quality.HasValue())
{
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
}
if (movie.Available)
{
var recipient = movie.RequestedUser.Email.HasValue() ? movie.RequestedUser.Email : string.Empty;

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
@ -86,10 +87,10 @@ namespace Ombi.Schedule.Jobs.Emby
private async Task StartServerCache(EmbyServers server, bool recentlyAdded)
{
if (!ValidateSettings(server))
{
return;
}
//await _repo.ExecuteSql("DELETE FROM EmbyEpisode");
//await _repo.ExecuteSql("DELETE FROM EmbyContent");
if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled))
{
@ -209,6 +210,7 @@ namespace Ombi.Schedule.Jobs.Emby
var totalCount = movies.TotalRecordCount;
var processed = 0;
var mediaToAdd = new HashSet<EmbyContent>();
var mediaToUpdate = new HashSet<EmbyContent>();
while (processed < totalCount)
{
foreach (var movie in movies.Items)
@ -219,13 +221,13 @@ namespace Ombi.Schedule.Jobs.Emby
await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
foreach (var item in movieInfo.Items)
{
await ProcessMovies(item, mediaToAdd, server);
await ProcessMovies(item, mediaToAdd, mediaToUpdate, server);
}
}
else
{
// Regular movie
await ProcessMovies(movie, mediaToAdd, server);
await ProcessMovies(movie, mediaToAdd, mediaToUpdate, server);
}
processed++;
@ -238,24 +240,32 @@ namespace Ombi.Schedule.Jobs.Emby
movies = await Api.GetAllMovies(server.ApiKey, parentId, processed, AmountToTake, server.AdministratorId, server.FullUri);
}
await _repo.AddRange(mediaToAdd);
await _repo.UpdateRange(mediaToUpdate);
mediaToAdd.Clear();
}
}
private async Task ProcessMovies(EmbyMovie movieInfo, ICollection<EmbyContent> content, EmbyServers server)
private async Task ProcessMovies(EmbyMovie movieInfo, ICollection<EmbyContent> content, ICollection<EmbyContent> toUpdate, EmbyServers server)
{
var quality = movieInfo.MediaStreams?.FirstOrDefault()?.DisplayTitle ?? string.Empty;
var has4K = false;
if (quality.Contains("4K", CompareOptions.IgnoreCase))
{
has4K = true;
}
// Check if it exists
var existingMovie = await _repo.GetByEmbyId(movieInfo.Id);
var alreadyGoingToAdd = content.Any(x => x.EmbyId == movieInfo.Id);
if (existingMovie == null && !alreadyGoingToAdd)
{
if (!movieInfo.ProviderIds.Any())
{
_logger.LogWarning($"Movie {movieInfo.Name} has no relevant metadata. Skipping.");
return;
}
_logger.LogDebug("Adding new movie {0}", movieInfo.Name);
_logger.LogDebug($"Adding new movie {movieInfo.Name}");
content.Add(new EmbyContent
{
ImdbId = movieInfo.ProviderIds.Imdb,
@ -264,13 +274,26 @@ namespace Ombi.Schedule.Jobs.Emby
Type = MediaType.Movie,
EmbyId = movieInfo.Id,
Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname),
AddedAt = DateTime.UtcNow
AddedAt = DateTime.UtcNow,
Quality = has4K ? null : quality,
Has4K = has4K
});
}
else
{
// we have this
_logger.LogDebug("We already have movie {0}", movieInfo.Name);
if (!existingMovie.Quality.Equals(quality, StringComparison.InvariantCultureIgnoreCase))
{
_logger.LogDebug($"We have found another quality for Movie '{movieInfo.Name}', Quality: '{quality}'");
existingMovie.Quality = has4K ? null : quality;
existingMovie.Has4K = has4K;
toUpdate.Add(existingMovie);
}
else
{
// we have this
_logger.LogDebug($"We already have movie {movieInfo.Name}");
}
}
}

@ -80,10 +80,11 @@ namespace Ombi.Schedule.Jobs.Jellyfin
private async Task ProcessMovies()
{
var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available);
var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available || (!x.Available4K && x.Has4KRequest));
foreach (var movie in movies)
{
var has4kRequest = movie.Has4KRequest;
JellyfinContent jellyfinContent = null;
if (movie.TheMovieDbId > 0)
{
@ -102,8 +103,19 @@ namespace Ombi.Schedule.Jobs.Jellyfin
_log.LogInformation("We have found the request {0} on Jellyfin, sending the notification", movie?.Title ?? string.Empty);
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
if (has4kRequest && jellyfinContent.Has4K)
{
movie.Available4K = true;
movie.MarkedAsAvailable4K = DateTime.Now;
}
// If we have a non-4k versison then mark as available
if (jellyfinContent.Quality.HasValue())
{
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
}
if (movie.Available)
{
var recipient = movie.RequestedUser.Email.HasValue() ? movie.RequestedUser.Email : string.Empty;

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
@ -77,9 +78,6 @@ namespace Ombi.Schedule.Jobs.Jellyfin
return;
}
//await _repo.ExecuteSql("DELETE FROM JellyfinEpisode");
//await _repo.ExecuteSql("DELETE FROM JellyfinContent");
if (server.JellyfinSelectedLibraries.Any() && server.JellyfinSelectedLibraries.Any(x => x.Enabled))
{
var movieLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");
@ -179,6 +177,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
var totalCount = movies.TotalRecordCount;
var processed = 0;
var mediaToAdd = new HashSet<JellyfinContent>();
var mediaToUpdate = new HashSet<JellyfinContent>();
while (processed < totalCount)
{
foreach (var movie in movies.Items)
@ -189,7 +188,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
foreach (var item in movieInfo.Items)
{
await ProcessMovies(item, mediaToAdd, server);
await ProcessMovies(item, mediaToAdd, mediaToUpdate, server);
}
processed++;
@ -198,20 +197,28 @@ namespace Ombi.Schedule.Jobs.Jellyfin
{
processed++;
// Regular movie
await ProcessMovies(movie, mediaToAdd, server);
await ProcessMovies(movie, mediaToAdd, mediaToUpdate, server);
}
}
// Get the next batch
movies = await Api.GetAllMovies(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri);
await _repo.AddRange(mediaToAdd);
await _repo.UpdateRange(mediaToUpdate);
mediaToAdd.Clear();
}
}
private async Task ProcessMovies(JellyfinMovie movieInfo, ICollection<JellyfinContent> content, JellyfinServers server)
private async Task ProcessMovies(JellyfinMovie movieInfo, ICollection<JellyfinContent> content, ICollection<JellyfinContent> toUpdate, JellyfinServers server)
{
var quality = movieInfo.MediaStreams?.FirstOrDefault()?.DisplayTitle ?? string.Empty;
var has4K = false;
if (quality.Contains("4K", CompareOptions.IgnoreCase))
{
has4K = true;
}
// Check if it exists
var existingMovie = await _repo.GetByJellyfinId(movieInfo.Id);
var alreadyGoingToAdd = content.Any(x => x.JellyfinId == movieInfo.Id);
@ -222,7 +229,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
_logger.LogWarning($"Movie {movieInfo.Name} has no relevant metadata. Skipping.");
return;
}
_logger.LogDebug("Adding new movie {0}", movieInfo.Name);
_logger.LogDebug($"Adding new movie {movieInfo.Name}");
content.Add(new JellyfinContent
{
ImdbId = movieInfo.ProviderIds.Imdb,
@ -231,13 +238,26 @@ namespace Ombi.Schedule.Jobs.Jellyfin
Type = MediaType.Movie,
JellyfinId = movieInfo.Id,
Url = JellyfinHelper.GetJellyfinMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname),
AddedAt = DateTime.UtcNow
AddedAt = DateTime.UtcNow,
Quality = has4K ? null : quality,
Has4K = has4K
});
}
else
{
// we have this
_logger.LogDebug("We already have movie {0}", movieInfo.Name);
if (!existingMovie.Quality.Equals(quality, StringComparison.InvariantCultureIgnoreCase))
{
_logger.LogDebug($"We have found another quality for Movie '{movieInfo.Name}', Quality: '{quality}'");
existingMovie.Quality = has4K ? null : quality;
existingMovie.Has4K = has4K;
toUpdate.Add(existingMovie);
}
else
{
// we have this
_logger.LogDebug($"We already have movie {movieInfo.Name}");
}
}
}

@ -25,7 +25,7 @@ namespace Ombi.Schedule.Jobs.Ombi
_ombiSettings = ombiSettings;
_movieRequests = movieRequest;
_tvRequestRepository = tvRequestRepository;
_musicRequestRepository = _musicRequestRepository;
_musicRequestRepository = musicRequestRepository;
_logger = logger;
}

@ -49,7 +49,9 @@ namespace Ombi.Schedule.Jobs.Ombi
await _requestQueue.SaveChangesAsync();
continue;
}
var result = await _movieSender.Send(movieRequest);
// TODO probably need to add something to the request queue to better idenitfy if it's a 4k request
var result = await _movieSender.Send(movieRequest, movieRequest.Approved4K);
if (result.Success)
{
request.Completed = DateTime.UtcNow;

@ -180,16 +180,12 @@ namespace Ombi.Schedule.Jobs.Plex
private async Task ProcessMovies()
{
// Get all non available
var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available);
var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available || (!x.Available4K && x.Has4KRequest));
var itemsForAvailbility = new List<AvailabilityModel>();
foreach (var movie in movies)
{
if (movie.Available)
{
return;
}
var has4kRequest = movie.Has4KRequest;
PlexServerContent item = null;
if (movie.ImdbId.HasValue())
{
@ -208,9 +204,23 @@ namespace Ombi.Schedule.Jobs.Plex
continue;
}
_log.LogInformation("[PAC] - Movie request {0} is now available, sending notification", $"{movie.Title} - {movie.Id}");
movie.Available = true;
movie.MarkedAsAvailable = DateTime.UtcNow;
_log.LogInformation($"[PAC] - Movie request {movie.Title} - {movie.Id} is now available, sending notification");
if (has4kRequest && item.Has4K)
{
movie.Available4K = true;
movie.Approved4K = true;
movie.MarkedAsAvailable4K = DateTime.Now;
}
// If we have a non-4k versison then mark as available
if (item.Quality.HasValue())
{
movie.Available = true;
movie.Approved = true;
movie.MarkedAsAvailable = DateTime.Now;
}
itemsForAvailbility.Add(new AvailabilityModel
{
Id = movie.Id,
@ -222,9 +232,9 @@ namespace Ombi.Schedule.Jobs.Plex
{
await _movieRepo.SaveChangesAsync();
}
foreach (var i in itemsForAvailbility)
{
foreach (var i in itemsForAvailbility.DistinctBy(x => x.Id))
{
await _notificationService.Notify(new NotificationOptions
{
DateTime = DateTime.Now,
@ -234,8 +244,6 @@ namespace Ombi.Schedule.Jobs.Plex
Recipient = i.RequestedUser
});
}
//await _repo.SaveChangesAsync();
}
private bool _disposed;

@ -293,7 +293,7 @@ namespace Ombi.Schedule.Jobs.Plex
Dictionary<int, int> contentProcessed)
{
Logger.LogDebug("Processing Movies");
foreach (var movie in content?.Metadata ?? new Metadata[] { })
foreach (var movie in content?.Metadata ?? Array.Empty<Metadata>())
{
// Let's check if we have this movie
@ -302,11 +302,31 @@ namespace Ombi.Schedule.Jobs.Plex
var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title
&& x.ReleaseYear == movie.year.ToString()
&& x.Type == MediaType.Movie);
// The rating key keeps changing
//var existing = await Repo.GetByKey(movie.ratingKey);
if (existing != null)
{
Logger.LogDebug("We already have movie {0}", movie.title);
// We need to see if this is a different quality,
// We want to know if this is a 4k content for example
var foundQualities = movie.Media?.Select(x => x.videoResolution);
foreach (var quality in foundQualities)
{
if (quality.Equals(existing.Quality))
{
// We got it
continue;
}
// We don't have this quality
if (quality.Equals("4k", StringComparison.InvariantCultureIgnoreCase))
{
Logger.LogDebug($"We already have movie {movie.title}, But found a 4K version!");
existing.Has4K = true;
await Repo.Update(existing);
}
}
Logger.LogDebug($"We already have movie {movie.title}");
continue;
}
@ -349,6 +369,10 @@ namespace Ombi.Schedule.Jobs.Plex
}
var providerIds = PlexHelper.GetProviderIdsFromMetadata(guids.ToArray());
var qualities = movie.Media?.Select(x => x.videoResolution);
var is4k = qualities != null && qualities.Any(x => x.Equals("4k", StringComparison.InvariantCultureIgnoreCase));
var selectedQuality = is4k ? string.Empty : qualities?.OrderBy(x => x)?.FirstOrDefault() ?? string.Empty;
var item = new PlexServerContent
{
AddedAt = DateTime.Now,
@ -358,7 +382,8 @@ namespace Ombi.Schedule.Jobs.Plex
Title = movie.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey, servers.ServerHostname),
Seasons = new List<PlexSeasonsContent>(),
Quality = movie.Media?.FirstOrDefault()?.videoResolution ?? string.Empty
Quality = selectedQuality,
Has4K = is4k,
};
if (providerIds.ImdbId.HasValue())
{

@ -1,11 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Store.Entities;
namespace Ombi.Schedule.Jobs.Radarr
namespace Ombi.Schedule.Jobs.Radarr
{
public interface IRadarrSync : IBaseJob
{
Task<IEnumerable<RadarrCache>> GetCachedContent();
}
}

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -10,106 +9,114 @@ using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
using Serilog;
namespace Ombi.Schedule.Jobs.Radarr
{
public class RadarrSync : IRadarrSync
{
public RadarrSync(ISettingsService<RadarrSettings> radarr, IRadarrV3Api radarrApi, ILogger<RadarrSync> log, ExternalContext ctx)
public RadarrSync(ISettingsService<RadarrSettings> radarr, ISettingsService<Radarr4KSettings> radarr4k, IRadarrV3Api radarrApi, ILogger<RadarrSync> log, ExternalContext ctx,
IExternalRepository<RadarrCache> radarrRepo)
{
RadarrSettings = radarr;
RadarrApi = radarrApi;
Logger = log;
_radarrSettings = radarr;
_radarr4kSettings = radarr4k;
_api = radarrApi;
_logger = log;
_ctx = ctx;
RadarrSettings.ClearCache();
_radarrRepo = radarrRepo;
_radarrSettings.ClearCache();
_radarr4kSettings.ClearCache();
}
private ISettingsService<RadarrSettings> RadarrSettings { get; }
private IRadarrV3Api RadarrApi { get; }
private ILogger<RadarrSync> Logger { get; }
private readonly ISettingsService<RadarrSettings> _radarrSettings;
private readonly ISettingsService<Radarr4KSettings> _radarr4kSettings;
private readonly IRadarrV3Api _api;
private readonly ILogger<RadarrSync> _logger;
private readonly ExternalContext _ctx;
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
private readonly IExternalRepository<RadarrCache> _radarrRepo;
public async Task Execute(IJobExecutionContext job)
{
await SemaphoreSlim.WaitAsync();
try
{
var settings = await RadarrSettings.GetSettingsAsync();
if (settings.Enabled)
var strat = _ctx.Database.CreateExecutionStrategy();
await strat.ExecuteAsync(async () =>
{
try
// Let's remove the old cached data
using var tran = await _ctx.Database.BeginTransactionAsync();
await _ctx.Database.ExecuteSqlRawAsync("DELETE FROM RadarrCache");
tran.Commit();
});
var radarrSettings = _radarrSettings.GetSettingsAsync();
var radarr4kSettings = _radarr4kSettings.GetSettingsAsync();
await Process(await radarrSettings);
await Process(await radarr4kSettings);
}
catch (Exception)
{
_logger.LogInformation(LoggingEvents.RadarrCacher, "Radarr is not setup, cannot cache episodes");
}
}
private async Task Process(RadarrSettings settings)
{
if (settings.Enabled)
{
try
{
var movies = await _api.GetMovies(settings.ApiKey, settings.FullUri);
var existingMovies = _radarrRepo.GetAll();
if (movies != null)
{
var movies = await RadarrApi.GetMovies(settings.ApiKey, settings.FullUri);
if (movies != null)
var movieIds = new List<RadarrCache>();
foreach (var m in movies)
{
var strat = _ctx.Database.CreateExecutionStrategy();
await strat.ExecuteAsync(async () =>
if (m.monitored || m.hasFile)
{
// Let's remove the old cached data
using (var tran = await _ctx.Database.BeginTransactionAsync())
if (m.tmdbId > 0)
{
await _ctx.Database.ExecuteSqlRawAsync("DELETE FROM RadarrCache");
tran.Commit();
}
});
var is4k = m.movieFile?.quality?.quality?.resolution >= 2160;
var movieIds = new List<RadarrCache>();
foreach (var m in movies)
{
if (m.monitored || m.hasFile)
{
if (m.tmdbId > 0)
// Do we have a cached movie for this already?
var existing = await existingMovies.FirstOrDefaultAsync(x => x.TheMovieDbId == m.tmdbId);
if (existing != null)
{
existing.Has4K = is4k;
existing.HasFile = m.hasFile;
}
else
{
movieIds.Add(new RadarrCache
{
TheMovieDbId = m.tmdbId,
HasFile = m.hasFile
HasFile = m.hasFile,
Has4K = is4k,
HasRegular = !is4k
});
}
else
{
Logger.LogError("TMDBId is not > 0 for movie {0}", m.title);
}
}
}
strat = _ctx.Database.CreateExecutionStrategy();
await strat.ExecuteAsync(async () =>
{
using (var tran = await _ctx.Database.BeginTransactionAsync())
else
{
await _ctx.RadarrCache.AddRangeAsync(movieIds);
await _ctx.SaveChangesAsync();
tran.Commit();
_logger.LogError($"TMDBId is not > 0 for movie {m.title}");
}
});
}
}
await OmbiQuartz.TriggerJob(nameof(IArrAvailabilityChecker), "DVR");
}
catch (System.Exception ex)
{
Logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Radarr");
// Save from the updates made to the existing movies (they are in the EF Change Tracker)
await _radarrRepo.SaveChangesAsync();
await _radarrRepo.AddRange(movieIds);
}
await OmbiQuartz.TriggerJob(nameof(IArrAvailabilityChecker), "DVR");
}
catch (System.Exception ex)
{
_logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Radarr");
}
}
catch (Exception)
{
Logger.LogInformation(LoggingEvents.RadarrCacher, "Radarr is not setup, cannot cache episodes");
}
finally
{
SemaphoreSlim.Release();
}
}
public async Task<IEnumerable<RadarrCache>> GetCachedContent()
{
return await _ctx.RadarrCache.ToListAsync();
}
private bool _disposed;

@ -10,4 +10,15 @@
public string MinimumAvailability { get; set; }
public bool ScanForAvailability { get; set; }
}
public class Radarr4KSettings : RadarrSettings
{
// no additional properties needed
}
public class RadarrCombinedModel
{
public RadarrSettings Radarr { get; set; }
public Radarr4KSettings Radarr4K { get; set; }
}
}

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ombi.Settings.Settings.Models
{
public class FeatureSettings : Settings
{
public List<FeatureEnablement> Features { get; set; }
}
public class FeatureEnablement
{
public string Name { get; set; }
public bool Enabled { get; set; }
}
public static class FeatureNames
{
public const string Movie4KRequests = nameof(Movie4KRequests);
}
}

@ -1,5 +1,6 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Ombi.Store.Entities;
namespace Ombi.Store.Context

@ -12,7 +12,11 @@ namespace Ombi.Store.Entities
public string TvDbId { get; set; }
public string TheMovieDbId { get; set; }
public MediaType Type { get; set; }
/// <summary>
/// Only populated if it's not 4k
/// </summary>
public string Quality { get; set; }
public bool Has4K { get; set; }
public string Url { get; set; }
public ICollection<IMediaServerEpisode> Episodes { get; set; }

@ -41,7 +41,6 @@ namespace Ombi.Store.Entities
/// Plex's internal ID for this item
/// </summary>
public int Key { get; set; }
public string Quality { get; set; }
public int? RequestId { get; set; }

@ -7,5 +7,7 @@ namespace Ombi.Store.Entities
{
public int TheMovieDbId { get; set; }
public bool HasFile { get; set; }
public bool Has4K { get; set; }
public bool HasRegular { get; set; }
}
}

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
using System;
namespace Ombi.Store.Entities.Requests
{
@ -22,9 +23,28 @@ namespace Ombi.Store.Entities.Requests
[NotMapped]
public bool ShowSubscribe { get; set; }
/// <summary>
/// This is only used during the request process to identify if
/// it's a regular request or a 4k
/// </summary>
[NotMapped]
public bool Is4kRequest { get; set; }
public int RootPathOverride { get; set; }
public int QualityOverride { get; set; }
public bool Has4KRequest { get; set; }
public bool Approved4K { get; set; }
public DateTime MarkedAsApproved4K { get; set; }
public DateTime RequestedDate4k { get; set; }
public bool Available4K { get; set; }
public DateTime? MarkedAsAvailable4K { get; set; }
public bool? Denied4K { get; set; }
public DateTime MarkedAsDenied4K { get; set; }
public string DeniedReason4K { get; set; }
/// <summary>
/// Only Use for setting the Language Code, Use the LanguageCode property for reading
/// </summary>

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Ombi.Store
{
internal static class MigrationHelper
{
public static void InsertRole(this MigrationBuilder mb, string role)
{
mb.Sql($@"
INSERT INTO AspnetRoles(Id, ConcurrencyStamp, Name, NormalizedName)
SELECT '{Guid.NewGuid()}','{Guid.NewGuid()}','{role}', '{role.ToUpper()}'
WHERE NOT EXISTS(SELECT 1 FROM AspnetRoles WHERE Name = '{role}');");
}
public static void InsertRoleMySql(this MigrationBuilder mb, string role)
{
mb.Sql($@"
INSERT INTO AspNetRoles(Id, ConcurrencyStamp, Name, NormalizedName)
SELECT '{Guid.NewGuid()}','{Guid.NewGuid()}','{role}', '{role.ToUpper()}'
WHERE NOT EXISTS(SELECT 1 FROM AspNetRoles WHERE Name = '{role}');");
}
}
}

@ -0,0 +1,527 @@
// <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("20220211213229_MediaServerQualities")]
partial class MediaServerQualities
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.0")
.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<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<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<int>("GrandparentKey")
.HasColumnType("int");
b.Property<int>("Key")
.HasColumnType("int");
b.Property<int>("ParentKey")
.HasColumnType("int");
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<int>("ParentKey")
.HasColumnType("int");
b.Property<int>("PlexContentId")
.HasColumnType("int");
b.Property<int?>("PlexServerContentId")
.HasColumnType("int");
b.Property<int>("SeasonKey")
.HasColumnType("int");
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<string>("ImdbId")
.HasColumnType("longtext");
b.Property<int>("Key")
.HasColumnType("int");
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.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.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")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
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,59 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.ExternalMySql
{
public partial class MediaServerQualities : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Has4K",
table: "RadarrCache",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "HasRegular",
table: "RadarrCache",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "Quality",
table: "JellyfinContent",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "Quality",
table: "EmbyContent",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Has4K",
table: "RadarrCache");
migrationBuilder.DropColumn(
name: "HasRegular",
table: "RadarrCache");
migrationBuilder.DropColumn(
name: "Quality",
table: "JellyfinContent");
migrationBuilder.DropColumn(
name: "Quality",
table: "EmbyContent");
}
}
}

@ -0,0 +1,536 @@
// <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("20220212210902_MediaServer4k")]
partial class MediaServer4k
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.0")
.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<int>("GrandparentKey")
.HasColumnType("int");
b.Property<int>("Key")
.HasColumnType("int");
b.Property<int>("ParentKey")
.HasColumnType("int");
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<int>("ParentKey")
.HasColumnType("int");
b.Property<int>("PlexContentId")
.HasColumnType("int");
b.Property<int?>("PlexServerContentId")
.HasColumnType("int");
b.Property<int>("SeasonKey")
.HasColumnType("int");
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<int>("Key")
.HasColumnType("int");
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.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.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")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
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,48 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.ExternalMySql
{
public partial class MediaServer4k : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Has4K",
table: "PlexServerContent",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Has4K",
table: "JellyfinContent",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Has4K",
table: "EmbyContent",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Has4K",
table: "PlexServerContent");
migrationBuilder.DropColumn(
name: "Has4K",
table: "JellyfinContent");
migrationBuilder.DropColumn(
name: "Has4K",
table: "EmbyContent");
}
}
}

@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context.MySql;
#nullable disable
namespace Ombi.Store.Migrations.ExternalMySql
{
[DbContext(typeof(ExternalMySqlContext))]
@ -14,8 +16,8 @@ namespace Ombi.Store.Migrations.ExternalMySql
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 64)
.HasAnnotation("ProductVersion", "5.0.1");
.HasAnnotation("ProductVersion", "6.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
@ -44,12 +46,18 @@ namespace Ombi.Store.Migrations.ExternalMySql
.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");
@ -122,6 +130,9 @@ namespace Ombi.Store.Migrations.ExternalMySql
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
@ -132,6 +143,9 @@ namespace Ombi.Store.Migrations.ExternalMySql
b.Property<string>("ProviderId")
.HasColumnType("longtext");
b.Property<string>("Quality")
.HasColumnType("longtext");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
@ -321,6 +335,9 @@ namespace Ombi.Store.Migrations.ExternalMySql
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
@ -362,9 +379,15 @@ namespace Ombi.Store.Migrations.ExternalMySql
.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");

@ -0,0 +1,525 @@
// <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("20220211213347_MediaServerQualities")]
partial class MediaServerQualities
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.0");
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<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<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<int>("GrandparentKey")
.HasColumnType("INTEGER");
b.Property<int>("Key")
.HasColumnType("INTEGER");
b.Property<int>("ParentKey")
.HasColumnType("INTEGER");
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<int>("ParentKey")
.HasColumnType("INTEGER");
b.Property<int>("PlexContentId")
.HasColumnType("INTEGER");
b.Property<int?>("PlexServerContentId")
.HasColumnType("INTEGER");
b.Property<int>("SeasonKey")
.HasColumnType("INTEGER");
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<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<int>("Key")
.HasColumnType("INTEGER");
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.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.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")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
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,57 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.ExternalSqlite
{
public partial class MediaServerQualities : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Has4K",
table: "RadarrCache",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "HasRegular",
table: "RadarrCache",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "Quality",
table: "JellyfinContent",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Quality",
table: "EmbyContent",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Has4K",
table: "RadarrCache");
migrationBuilder.DropColumn(
name: "HasRegular",
table: "RadarrCache");
migrationBuilder.DropColumn(
name: "Quality",
table: "JellyfinContent");
migrationBuilder.DropColumn(
name: "Quality",
table: "EmbyContent");
}
}
}

@ -0,0 +1,534 @@
// <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("20220212210807_MediaServer4k")]
partial class MediaServer4k
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.0");
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<int>("GrandparentKey")
.HasColumnType("INTEGER");
b.Property<int>("Key")
.HasColumnType("INTEGER");
b.Property<int>("ParentKey")
.HasColumnType("INTEGER");
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<int>("ParentKey")
.HasColumnType("INTEGER");
b.Property<int>("PlexContentId")
.HasColumnType("INTEGER");
b.Property<int?>("PlexServerContentId")
.HasColumnType("INTEGER");
b.Property<int>("SeasonKey")
.HasColumnType("INTEGER");
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<int>("Key")
.HasColumnType("INTEGER");
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.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.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")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
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,48 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.ExternalSqlite
{
public partial class MediaServer4k : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Has4K",
table: "PlexServerContent",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Has4K",
table: "JellyfinContent",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Has4K",
table: "EmbyContent",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Has4K",
table: "PlexServerContent");
migrationBuilder.DropColumn(
name: "Has4K",
table: "JellyfinContent");
migrationBuilder.DropColumn(
name: "Has4K",
table: "EmbyContent");
}
}
}

@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context.Sqlite;
#nullable disable
namespace Ombi.Store.Migrations.ExternalSqlite
{
[DbContext(typeof(ExternalSqliteContext))]
@ -13,8 +15,7 @@ namespace Ombi.Store.Migrations.ExternalSqlite
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.1");
modelBuilder.HasAnnotation("ProductVersion", "6.0.0");
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
@ -43,12 +44,18 @@ namespace Ombi.Store.Migrations.ExternalSqlite
.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");
@ -121,6 +128,9 @@ namespace Ombi.Store.Migrations.ExternalSqlite
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
@ -131,6 +141,9 @@ namespace Ombi.Store.Migrations.ExternalSqlite
b.Property<string>("ProviderId")
.HasColumnType("TEXT");
b.Property<string>("Quality")
.HasColumnType("TEXT");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
@ -320,6 +333,9 @@ namespace Ombi.Store.Migrations.ExternalSqlite
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
@ -361,9 +377,15 @@ namespace Ombi.Store.Migrations.ExternalSqlite
.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");

File diff suppressed because it is too large Load Diff

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Ombi.Helpers;
#nullable disable
namespace Ombi.Store.Migrations.OmbiMySql
{
public partial class Radarr4kRole : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertRoleMySql(OmbiRoles.Request4KMovie);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.OmbiMySql
{
public partial class MovieRequest4K : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Has4KRequest",
table: "MovieRequests",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Has4KRequest",
table: "MovieRequests");
}
}
}

@ -0,0 +1,102 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.OmbiMySql
{
public partial class _4kMovieProperties : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Approved4K",
table: "MovieRequests",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Available4K",
table: "MovieRequests",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Denied4K",
table: "MovieRequests",
type: "tinyint(1)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DeniedReason4K",
table: "MovieRequests",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsApproved4K",
table: "MovieRequests",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsAvailable4K",
table: "MovieRequests",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsDenied4K",
table: "MovieRequests",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "RequestedDate4k",
table: "MovieRequests",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Approved4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "Available4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "Denied4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "DeniedReason4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsApproved4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsAvailable4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsDenied4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "RequestedDate4k",
table: "MovieRequests");
}
}
}

@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context.MySql;
#nullable disable
namespace Ombi.Store.Migrations.OmbiMySql
{
[DbContext(typeof(OmbiMySqlContext))]
@ -14,8 +16,8 @@ namespace Ombi.Store.Migrations.OmbiMySql
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 64)
.HasAnnotation("ProductVersion", "5.0.1");
.HasAnnotation("ProductVersion", "6.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
@ -40,7 +42,7 @@ namespace Ombi.Store.Migrations.OmbiMySql
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
@ -63,7 +65,7 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
@ -86,7 +88,7 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
@ -108,7 +110,7 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
@ -123,7 +125,7 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
@ -142,7 +144,7 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
@ -342,7 +344,7 @@ namespace Ombi.Store.Migrations.OmbiMySql
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
@ -406,28 +408,6 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.ToTable("RequestQueue");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("RequestId")
.HasColumnType("int");
b.Property<int>("RequestType")
.HasColumnType("int");
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
{
b.Property<int>("Id")
@ -661,21 +641,36 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.Property<bool>("Approved")
.HasColumnType("tinyint(1)");
b.Property<bool>("Approved4K")
.HasColumnType("tinyint(1)");
b.Property<bool>("Available")
.HasColumnType("tinyint(1)");
b.Property<bool>("Available4K")
.HasColumnType("tinyint(1)");
b.Property<string>("Background")
.HasColumnType("longtext");
b.Property<bool?>("Denied")
.HasColumnType("tinyint(1)");
b.Property<bool?>("Denied4K")
.HasColumnType("tinyint(1)");
b.Property<string>("DeniedReason")
.HasColumnType("longtext");
b.Property<string>("DeniedReason4K")
.HasColumnType("longtext");
b.Property<DateTime?>("DigitalReleaseDate")
.HasColumnType("datetime(6)");
b.Property<bool>("Has4KRequest")
.HasColumnType("tinyint(1)");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
@ -688,12 +683,21 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.Property<DateTime>("MarkedAsApproved")
.HasColumnType("datetime(6)");
b.Property<DateTime>("MarkedAsApproved4K")
.HasColumnType("datetime(6)");
b.Property<DateTime?>("MarkedAsAvailable")
.HasColumnType("datetime(6)");
b.Property<DateTime?>("MarkedAsAvailable4K")
.HasColumnType("datetime(6)");
b.Property<DateTime>("MarkedAsDenied")
.HasColumnType("datetime(6)");
b.Property<DateTime>("MarkedAsDenied4K")
.HasColumnType("datetime(6)");
b.Property<string>("Overview")
.HasColumnType("longtext");
@ -715,6 +719,9 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.Property<DateTime>("RequestedDate")
.HasColumnType("datetime(6)");
b.Property<DateTime>("RequestedDate4k")
.HasColumnType("datetime(6)");
b.Property<string>("RequestedUserId")
.HasColumnType("varchar(255)");
@ -815,6 +822,28 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("RequestId")
.HasColumnType("int");
b.Property<int>("RequestType")
.HasColumnType("int");
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
@ -1052,15 +1081,6 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.Navigation("User");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
@ -1145,6 +1165,15 @@ namespace Ombi.Store.Migrations.OmbiMySql
b.Navigation("User");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Ombi.Helpers;
#nullable disable
namespace Ombi.Store.Migrations.OmbiSqlite
{
public partial class Radarr4kRole : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertRole(OmbiRoles.Request4KMovie);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.OmbiSqlite
{
public partial class MovieRequest4K : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Has4KRequest",
table: "MovieRequests",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Has4KRequest",
table: "MovieRequests");
}
}
}

@ -0,0 +1,101 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.OmbiSqlite
{
public partial class _4kMovieProperties : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Approved4K",
table: "MovieRequests",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Available4K",
table: "MovieRequests",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Denied4K",
table: "MovieRequests",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DeniedReason4K",
table: "MovieRequests",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsApproved4K",
table: "MovieRequests",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsAvailable4K",
table: "MovieRequests",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsDenied4K",
table: "MovieRequests",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "RequestedDate4k",
table: "MovieRequests",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Approved4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "Available4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "Denied4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "DeniedReason4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsApproved4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsAvailable4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsDenied4K",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "RequestedDate4k",
table: "MovieRequests");
}
}
}

@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context.Sqlite;
#nullable disable
namespace Ombi.Store.Migrations.OmbiSqlite
{
[DbContext(typeof(OmbiSqliteContext))]
@ -13,8 +15,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.1");
modelBuilder.HasAnnotation("ProductVersion", "6.0.0");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
@ -39,7 +40,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
@ -62,7 +63,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
@ -85,7 +86,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
@ -107,7 +108,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
@ -122,7 +123,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
@ -141,7 +142,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
@ -341,7 +342,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
@ -405,28 +406,6 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.ToTable("RequestQueue");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("RequestId")
.HasColumnType("INTEGER");
b.Property<int>("RequestType")
.HasColumnType("INTEGER");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
{
b.Property<int>("Id")
@ -660,21 +639,36 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.Property<bool>("Approved")
.HasColumnType("INTEGER");
b.Property<bool>("Approved4K")
.HasColumnType("INTEGER");
b.Property<bool>("Available")
.HasColumnType("INTEGER");
b.Property<bool>("Available4K")
.HasColumnType("INTEGER");
b.Property<string>("Background")
.HasColumnType("TEXT");
b.Property<bool?>("Denied")
.HasColumnType("INTEGER");
b.Property<bool?>("Denied4K")
.HasColumnType("INTEGER");
b.Property<string>("DeniedReason")
.HasColumnType("TEXT");
b.Property<string>("DeniedReason4K")
.HasColumnType("TEXT");
b.Property<DateTime?>("DigitalReleaseDate")
.HasColumnType("TEXT");
b.Property<bool>("Has4KRequest")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
@ -687,12 +681,21 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.Property<DateTime>("MarkedAsApproved")
.HasColumnType("TEXT");
b.Property<DateTime>("MarkedAsApproved4K")
.HasColumnType("TEXT");
b.Property<DateTime?>("MarkedAsAvailable")
.HasColumnType("TEXT");
b.Property<DateTime?>("MarkedAsAvailable4K")
.HasColumnType("TEXT");
b.Property<DateTime>("MarkedAsDenied")
.HasColumnType("TEXT");
b.Property<DateTime>("MarkedAsDenied4K")
.HasColumnType("TEXT");
b.Property<string>("Overview")
.HasColumnType("TEXT");
@ -714,6 +717,9 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.Property<DateTime>("RequestedDate")
.HasColumnType("TEXT");
b.Property<DateTime>("RequestedDate4k")
.HasColumnType("TEXT");
b.Property<string>("RequestedUserId")
.HasColumnType("TEXT");
@ -814,6 +820,28 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("RequestId")
.HasColumnType("INTEGER");
b.Property<int>("RequestType")
.HasColumnType("INTEGER");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
@ -1051,15 +1079,6 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.Navigation("User");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
@ -1144,6 +1163,15 @@ namespace Ombi.Store.Migrations.OmbiSqlite
b.Navigation("User");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")

@ -25,7 +25,6 @@
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -37,7 +36,6 @@ namespace Ombi.Store.Repository
{
public class EmbyContentRepository : MediaServerContentRepository<EmbyContent>, IEmbyContentRepository
{
public EmbyContentRepository(ExternalContext db):base(db)
{
}
@ -97,7 +95,13 @@ namespace Ombi.Store.Repository
{
Db.EmbyContent.Update((EmbyContent)existingContent);
}
public override Task UpdateRange(IEnumerable<IMediaServerContent> existingContent)
{
Db.EmbyContent.UpdateRange((EmbyContent)existingContent);
return InternalSaveChanges();
}
public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Emby;
}
}

@ -10,6 +10,7 @@ namespace Ombi.Store.Repository
{
RecentlyAddedType RecentlyAddedType{ get; }
Task Update(IMediaServerContent existingContent);
Task UpdateRange(IEnumerable<IMediaServerContent> existingContent);
IQueryable<IMediaServerEpisode> GetAllEpisodes();
Task<IMediaServerEpisode> Add(IMediaServerEpisode content);
Task AddRange(IEnumerable<IMediaServerEpisode> content);

@ -98,6 +98,12 @@ namespace Ombi.Store.Repository
Db.JellyfinContent.Update((JellyfinContent)existingContent);
}
public override Task UpdateRange(IEnumerable<IMediaServerContent> existingContent)
{
Db.JellyfinContent.UpdateRange((JellyfinContent)existingContent);
return InternalSaveChanges();
}
public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Jellyfin;
}
}

@ -21,5 +21,6 @@ namespace Ombi.Store.Repository
public abstract Task<IMediaServerEpisode> Add(IMediaServerEpisode content);
public abstract Task AddRange(IEnumerable<IMediaServerEpisode> content);
public abstract void UpdateWithoutSave(IMediaServerContent existingContent);
public abstract Task UpdateRange(IEnumerable<IMediaServerContent> existingContent);
}
}

@ -164,5 +164,10 @@ namespace Ombi.Store.Repository
await InternalSaveChanges();
}
public override Task UpdateRange(IEnumerable<IMediaServerContent> existingContent)
{
Db.PlexServerContent.UpdateRange((PlexServerContent)existingContent);
return InternalSaveChanges();
}
}
}

@ -126,6 +126,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.RottenTomatoes", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.I18n", "Ombi.I18n\Ombi.I18n.csproj", "{6A922D57-8622-4C36-8E6E-D5BA9E8DA6C0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.MediaServer", "Ombi.Api.MediaServer\Ombi.Api.MediaServer.csproj", "{AFC0BA9B-E38D-479F-825A-2F94EE4D6120}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -439,6 +441,12 @@ Global
{6A922D57-8622-4C36-8E6E-D5BA9E8DA6C0}.NonUiBuild|Any CPU.Build.0 = NonUiBuild|Any CPU
{6A922D57-8622-4C36-8E6E-D5BA9E8DA6C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A922D57-8622-4C36-8E6E-D5BA9E8DA6C0}.Release|Any CPU.Build.0 = Release|Any CPU
{AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.NonUiBuild|Any CPU.ActiveCfg = NonUiBuild|Any CPU
{AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.NonUiBuild|Any CPU.Build.0 = NonUiBuild|Any CPU
{AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -487,6 +495,7 @@ Global
{E2186FDA-D827-4781-8663-130AC382F12C} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{5DE40A66-B369-469E-8626-ECE23D9D8034} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{8F19C701-7881-4BC7-8BBA-B068A6B954AD} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{AFC0BA9B-E38D-479F-825A-2F94EE4D6120} = {9293CA11-360A-4C20-A674-B9E794431BF5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869}

@ -21,6 +21,8 @@ import { CustomPageComponent } from "./custompage/custompage.component";
import { CustomizationState } from "./state/customization/customization.state";
import { DataViewModule } from "primeng/dataview";
import { DialogModule } from "primeng/dialog";
import { FEATURES_INITIALIZER } from "./state/features/features-initializer";
import { FeatureState } from "./state/features";
import { JwtModule } from "@auth0/angular-jwt";
import { LandingPageComponent } from "./landingpage/landingpage.component";
import { LandingPageService } from "./services";
@ -38,6 +40,8 @@ import { MatInputModule } from "@angular/material/input";
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from "@angular/material/menu";
import { MatNativeDateModule } from '@angular/material/core';
import { MatPaginatorI18n } from "./localization/MatPaginatorI18n";
import { MatPaginatorIntl } from "@angular/material/paginator";
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from "@angular/material/slide-toggle";
@ -63,11 +67,9 @@ import { StorageService } from "./shared/storage/storage-service";
import { TokenResetPasswordComponent } from "./login/tokenresetpassword.component";
import { TooltipModule } from "primeng/tooltip";
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
import { TranslateService } from "@ngx-translate/core";
import { UnauthorizedInterceptor } from "./auth/unauthorized.interceptor";
import { environment } from "../environments/environment";
import { MatPaginatorIntl } from "@angular/material/paginator";
import { TranslateService } from "@ngx-translate/core";
import { MatPaginatorI18n } from "./localization/MatPaginatorI18n";
const routes: Routes = [
{ path: "*", component: PageNotFoundComponent },
@ -162,10 +164,10 @@ export function JwtTokenGetter() {
}),
SidebarModule,
MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, LayoutModule, MatSlideToggleModule,
NgxsModule.forRoot([CustomizationState], {
NgxsModule.forRoot([CustomizationState, FeatureState], {
developmentMode: !environment.production,
}),
...environment.production ? [] :
...environment.production ? [] :
[
NgxsReduxDevtoolsPluginModule.forRoot(),
]
@ -205,6 +207,7 @@ export function JwtTokenGetter() {
StorageService,
RequestService,
SignalRNotificationService,
FEATURES_INITIALIZER,
CUSTOMIZATION_INITIALIZER,
{
provide: APP_BASE_HREF,

@ -5,7 +5,7 @@
</div>
<div *ngIf="discoverResults" class="row full-height">
<div class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults">
<discover-card [isAdmin]="isAdmin" [result]="result"></discover-card>
<discover-card [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
</div>
</div>
</div>

@ -1,4 +1,4 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { SearchV2Service } from "../../../services";
import { IActorCredits, IActorCast } from "../../../interfaces/ISearchTvResultV2";
@ -6,30 +6,31 @@ import { IDiscoverCardResult } from "../../interfaces";
import { RequestType } from "../../../interfaces";
import { AuthService } from "../../../auth/auth.service";
import { forkJoin } from "rxjs";
import { isEqual } from "lodash";
import { FeaturesFacade } from "../../../state/features/features.facade";
@Component({
templateUrl: "./discover-actor.component.html",
styleUrls: ["./discover-actor.component.scss"],
})
export class DiscoverActorComponent {
export class DiscoverActorComponent implements OnInit {
public actorId: number;
public loadingFlag: boolean;
public isAdmin: boolean;
public is4kEnabled = false;
public discoverResults: IDiscoverCardResult[] = [];
constructor(private searchService: SearchV2Service,
private route: ActivatedRoute,
private auth: AuthService) {
private auth: AuthService,
private featureService: FeaturesFacade) {
this.route.params.subscribe((params: any) => {
this.actorId = params.actorId;
this.isAdmin = this.auth.isAdmin();
this.search();
});
}
private search() {
ngOnInit() {
this.isAdmin = this.auth.isAdmin();
this.is4kEnabled = this.featureService.is4kEnabled();
this.discoverResults = [];
this.loading();

@ -20,12 +20,24 @@
</a>
</div>
<div [ngClass]="result.posterPath.includes('images/') ? 'button-request-container-show' : 'button-request-container'" class="row" *ngIf="!result.available && !result.approved && !result.requested">
<div class="button-request poster-overlay">
<button id="requestButton{{result.id}}{{result.type}}{{discoverType}}" *ngIf="requestable" mat-raised-button class="btn-ombi full-width poster-request-btn" (click)="request($event)">
<div *ngIf="!is4kEnabled" class="button-request poster-overlay">
<button id="requestButton{{result.id}}{{result.type}}{{discoverType}}" *ngIf="requestable" mat-raised-button class="btn-ombi full-width poster-request-btn" (click)="request($event, false)">
<i *ngIf="!loading" class="fa-lg fas fa-cloud-download-alt"></i>
<i *ngIf="loading" class="fas fa-spinner fa-pulse fa-2x fa-fw" aria-hidden="true"></i>
{{'Common.Request' | translate }}
</button>
</div>
<div *ngIf="is4kEnabled && requestable" class="button-request poster-overlay">
<button [matMenuTriggerFor]="menu" id="requestButton{{result.id}}{{result.type}}{{discoverType}}" mat-raised-button class="btn-ombi full-width poster-request-btn">
<i *ngIf="!loading" class="fa-lg fas fa-cloud-download-alt"></i>
<i *ngIf="loading" class="fas fa-spinner fa-pulse fa-2x fa-fw" aria-hidden="true"></i>
{{'Common.Request' | translate }}
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item class="btn-ombi full-width poster-request-btn" (click)="request($event, false)">{{'Common.Request' | translate }}</button>
<button mat-menu-item class="btn-ombi full-width poster-request-btn" (click)="request($event, true)">{{'Common.Request4K' | translate }}</button>
</mat-menu>
</div>
</div>
</div>

@ -292,4 +292,8 @@ a.poster-overlay:hover{
.btn-ombi{
background-color:#293a4c;
}
::ng-deep .mat-menu-panel {
min-width: 190px !important;
}

@ -21,6 +21,7 @@ export class DiscoverCardComponent implements OnInit {
@Input() public discoverType: DiscoverType;
@Input() public result: IDiscoverCardResult;
@Input() public isAdmin: boolean;
@Input() public is4kEnabled: boolean = false;
public RequestType = RequestType;
public hide: boolean;
public fullyLoaded = false;
@ -111,7 +112,7 @@ export class DiscoverCardComponent implements OnInit {
return "";
}
public request(event: any) {
public request(event: any, is4k: boolean) {
event.preventDefault();
this.loading = true;
switch (this.result.type) {
@ -121,14 +122,15 @@ export class DiscoverCardComponent implements OnInit {
return;
case RequestType.movie:
if (this.isAdmin) {
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.result.id }, panelClass: 'modal-panel' });
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.result.id, }, panelClass: 'modal-panel' });
dialog.afterClosed().subscribe((result) => {
if (result) {
this.requestService.requestMovie({ theMovieDbId: +this.result.id,
languageCode: this.translate.currentLang,
qualityPathOverride: result.radarrPathId,
requestOnBehalf: result.username?.id,
rootFolderOverride: result.radarrFolderId, }).subscribe(x => {
rootFolderOverride: result.radarrFolderId,
is4KRequest: is4k }).subscribe(x => {
if (x.result) {
this.result.requested = true;
this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.result.title }), "Ok");
@ -139,7 +141,7 @@ export class DiscoverCardComponent implements OnInit {
}
});
} else {
this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: this.translate.currentLang, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null }).subscribe(x => {
this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: this.translate.currentLang, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null, is4KRequest: is4k }).subscribe(x => {
if (x.result) {
this.result.requested = true;
this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.result.title }), "Ok");

@ -8,6 +8,6 @@
<p-carousel #carousel [numVisible]="10" [numScroll]="10" [page]="0" [value]="discoverResults" [responsiveOptions]="responsiveOptions" (onPage)="newPage()">
<ng-template let-result pTemplate="item">
<discover-card [discoverType]="discoverType" [isAdmin]="isAdmin" [result]="result"></discover-card>
<discover-card [discoverType]="discoverType" [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
</ng-template>
</p-carousel>

@ -5,6 +5,7 @@ import { SearchV2Service } from "../../../services";
import { StorageService } from "../../../shared/storage/storage-service";
import { MatButtonToggleChange } from '@angular/material/button-toggle';
import { Carousel } from 'primeng/carousel';
import { FeaturesFacade } from "../../../state/features/features.facade";
export enum DiscoverType {
Upcoming,
@ -36,6 +37,7 @@ export class CarouselListComponent implements OnInit {
public RequestType = RequestType;
public loadingFlag: boolean;
public DiscoverType = DiscoverType;
public is4kEnabled = false;
get mediaTypeStorageKey() {
return "DiscoverOptions" + this.discoverType.toString();
@ -44,7 +46,8 @@ export class CarouselListComponent implements OnInit {
private currentlyLoaded = 0;
constructor(private searchService: SearchV2Service,
private storageService: StorageService) {
private storageService: StorageService,
private featureFacade: FeaturesFacade) {
this.responsiveOptions = [
{
breakpoint: '4000px',
@ -135,6 +138,7 @@ export class CarouselListComponent implements OnInit {
}
public async ngOnInit() {
this.is4kEnabled = this.featureFacade.is4kEnabled();
this.currentlyLoaded = 0;
const localDiscoverOptions = +this.storageService.get(this.mediaTypeStorageKey);
if (localDiscoverOptions) {

@ -16,7 +16,7 @@
</div>
<div *ngIf="discoverResults" class="row full-height">
<div class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults">
<discover-card [isAdmin]="isAdmins" [result]="result"></discover-card>
<discover-card [isAdmin]="isAdmins" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
</div>
</div>
</div>

@ -8,6 +8,7 @@ import { IDiscoverCardResult } from "../../interfaces";
import { IMovieCollectionsViewModel } from "../../../interfaces/ISearchTvResultV2";
import { RequestServiceV2 } from "../../../services/requestV2.service";
import { RequestType } from "../../../interfaces";
import { FeaturesFacade } from "../../../state/features/features.facade";
@Component({
templateUrl: "./discover-collections.component.html",
@ -19,6 +20,7 @@ export class DiscoverCollectionsComponent implements OnInit {
public collection: IMovieCollectionsViewModel;
public loadingFlag: boolean;
public isAdmin: boolean;
public is4kEnabled = false;
public discoverResults: IDiscoverCardResult[] = [];
@ -27,13 +29,15 @@ export class DiscoverCollectionsComponent implements OnInit {
private requestServiceV2: RequestServiceV2,
private messageService: MessageService,
private auth: AuthService,
private translate: TranslateService) {
private translate: TranslateService,
private featureFacade: FeaturesFacade) {
this.route.params.subscribe((params: any) => {
this.collectionId = params.collectionId;
});
}
public async ngOnInit() {
this.is4kEnabled = this.featureFacade.is4kEnabled();
this.loadingFlag = true;
this.isAdmin = this.auth.isAdmin();
this.collection = await this.searchService.getMovieCollections(this.collectionId);

@ -4,7 +4,7 @@
</div>
<div *ngIf="discoverResults.length > 0" class="row full-height discoverResults col" >
<div id="searchResults" class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults" data-test="searchResultsCount" attr.search-count="{{discoverResults.length}}">
<discover-card [isAdmin]="isAdmin" [result]="result"></discover-card>
<discover-card [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
</div>
</div>
<div *ngIf="!loadingFlag && discoverResults.length === 0">

@ -10,6 +10,7 @@ import { SearchFilter } from "../../../my-nav/SearchFilter";
import { SearchV2Service } from "../../../services";
import { StorageService } from "../../../shared/storage/storage-service";
import { isEqual } from "lodash";
import { FeaturesFacade } from "../../../state/features/features.facade";
@Component({
templateUrl: "./search-results.component.html",
@ -21,6 +22,7 @@ export class DiscoverSearchResultsComponent implements OnInit {
public searchTerm: string;
public results: IMultiSearchResult[];
public isAdmin: boolean;
public is4kEnabled = false;
public discoverResults: IDiscoverCardResult[] = [];
@ -34,7 +36,8 @@ export class DiscoverSearchResultsComponent implements OnInit {
private router: Router,
private advancedDataService: AdvancedSearchDialogDataService,
private store: StorageService,
private authService: AuthService) {
private authService: AuthService,
private featureFacade: FeaturesFacade) {
this.route.params.subscribe((params: any) => {
this.isAdvancedSearch = this.router.url === '/discover/advanced/search';
if (this.isAdvancedSearch) {
@ -53,6 +56,7 @@ export class DiscoverSearchResultsComponent implements OnInit {
}
public async ngOnInit() {
this.is4kEnabled = this.featureFacade.is4kEnabled();
this.isAdmin = this.authService.isAdmin();
this.filterService.onFilterChange.subscribe(async x => {
if (!isEqual(this.filter, x)) {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save