This commit is contained in:
Jamie Rees 2019-04-23 10:31:28 +01:00
commit f16b33c437
367 changed files with 12069 additions and 3073 deletions

View file

@ -0,0 +1,36 @@
using System.Net.Http;
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public class GotifyApi : IGotifyApi
{
public GotifyApi(IApi api)
{
_api = api;
}
private readonly IApi _api;
public async Task PushAsync(string baseUrl, string accessToken, string subject, string body, sbyte priority)
{
var request = new Request("/message", baseUrl, HttpMethod.Post);
request.AddQueryString("token", accessToken);
request.AddHeader("Access-Token", accessToken);
request.ApplicationJsonContentType();
var jsonBody = new
{
message = body,
title = subject,
priority = priority
};
request.AddJsonBody(jsonBody);
await _api.Request(request);
}
}
}

View file

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public interface IGotifyApi
{
Task PushAsync(string endpoint, string accessToken, string subject, string body, sbyte priority);
}
}

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
<PackageVersion></PackageVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

View file

@ -12,5 +12,6 @@ namespace Ombi.Api.Trakt
Task<IEnumerable<TraktMostWatchedShow>> GetMostWatchesShows(TraktTimePeriod period = null, int? page = default(int?), int? limitPerPage = default(int?));
Task<IEnumerable<TraktShow>> GetPopularShows(int? page = default(int?), int? limitPerPage = default(int?));
Task<IEnumerable<TraktTrendingShow>> GetTrendingShows(int? page = default(int?), int? limitPerPage = default(int?));
Task<TraktShow> GetTvExtendedInfo(string imdbId);
}
}

View file

@ -23,7 +23,7 @@ namespace Ombi.Api.Trakt
public async Task<IEnumerable<TraktShow>> GetPopularShows(int? page = null, int? limitPerPage = null)
{
var popular = await Client.Shows.GetPopularShowsAsync(new TraktExtendedInfo { Full = true, Images = true}, null, page ?? 1, limitPerPage ?? 10);
var popular = await Client.Shows.GetPopularShowsAsync(new TraktExtendedInfo { Full = true, Images = true }, null, page ?? 1, limitPerPage ?? 10);
return popular.Value;
}
@ -44,6 +44,11 @@ namespace Ombi.Api.Trakt
var anticipatedShows = await Client.Shows.GetMostWatchedShowsAsync(period ?? TraktTimePeriod.Monthly, new TraktExtendedInfo { Full = true, Images = true }, null, page ?? 1, limitPerPage ?? 10);
return anticipatedShows.Value;
}
public async Task<TraktShow> GetTvExtendedInfo(string imdbId)
{
return await Client.Shows.GetShowAsync(imdbId, new TraktExtendedInfo { Full = true });
}
}
}

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.TvMaze.Models;
using Ombi.Api.TvMaze.Models.V2;
namespace Ombi.Api.TvMaze
{
@ -11,5 +12,6 @@ namespace Ombi.Api.TvMaze
Task<List<TvMazeSearch>> Search(string searchTerm);
Task<TvMazeShow> ShowLookup(int showId);
Task<TvMazeShow> ShowLookupByTheTvDbId(int theTvDbId);
Task<FullSearch> GetTvFullInformation(int id);
}
}

View file

@ -0,0 +1,144 @@
using System;
namespace Ombi.Api.TvMaze.Models.V2
{
public class FullSearch
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public string type { get; set; }
public string language { get; set; }
public string[] genres { get; set; }
public string status { get; set; }
public int runtime { get; set; }
public string premiered { get; set; }
public string officialSite { get; set; }
public Schedule schedule { get; set; }
public Rating rating { get; set; }
public int weight { get; set; }
public Network network { get; set; }
public object webChannel { get; set; }
public Externals externals { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public int updated { get; set; }
public _Links _links { get; set; }
public _Embedded _embedded { get; set; }
}
public class Schedule
{
public string time { get; set; }
public string[] days { get; set; }
}
public class Rating
{
public float average { get; set; }
}
public class Network
{
public int id { get; set; }
public string name { get; set; }
public Country country { get; set; }
}
public class Country
{
public string name { get; set; }
public string code { get; set; }
public string timezone { get; set; }
}
public class Externals
{
public int tvrage { get; set; }
public int thetvdb { get; set; }
public string imdb { get; set; }
}
public class Image
{
public string medium { get; set; }
public string original { get; set; }
}
public class _Links
{
public Self self { get; set; }
public Previousepisode previousepisode { get; set; }
}
public class Self
{
public string href { get; set; }
}
public class Previousepisode
{
public string href { get; set; }
}
public class _Embedded
{
public Cast[] cast { get; set; }
public Crew[] crew { get; set; }
public Episode[] episodes { get; set; }
}
public class Cast
{
public Person person { get; set; }
public Character character { get; set; }
public bool self { get; set; }
public bool voice { get; set; }
}
public class Person
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public Country country { get; set; }
public string birthday { get; set; }
public object deathday { get; set; }
public string gender { get; set; }
public Image image { get; set; }
public _Links _links { get; set; }
}
public class Character
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public Image image { get; set; }
public _Links _links { get; set; }
}
public class Crew
{
public string type { get; set; }
public Person person { get; set; }
}
public class Episode
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public int season { get; set; }
public int number { get; set; }
public string airdate { get; set; }
public string airtime { get; set; }
public DateTime airstamp { get; set; }
public int runtime { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public _Links _links { get; set; }
}
}

View file

@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.TvMaze.Models;
using Ombi.Api.TvMaze.Models.V2;
using Ombi.Helpers;
namespace Ombi.Api.TvMaze
@ -15,7 +15,6 @@ namespace Ombi.Api.TvMaze
{
Api = api;
Logger = logger;
//Mapper = mapper;
}
private string Uri = "http://api.tvmaze.com";
private IApi Api { get; }
@ -75,5 +74,17 @@ namespace Ombi.Api.TvMaze
return await Api.Request<List<TvMazeSeasons>>(request);
}
public async Task<FullSearch> GetTvFullInformation(int id)
{
var request = new Request($"shows/{id}", Uri, HttpMethod.Get);
request.AddQueryString("embed[]", "cast");
request.AddQueryString("embed[]", "crew");
request.AddQueryString("embed[]", "episodes");
request.AddContentHeader("Content-Type", "application/json");
return await Api.Request<FullSearch>(request);
}
}
}

View file

@ -0,0 +1,106 @@
using Microsoft.AspNetCore.Identity;
using Moq;
using NUnit.Framework;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Test.Common;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Ombi.Core.Tests.Authentication
{
[TestFixture]
public class OmbiUserManagerTests
{
[SetUp]
public void Setup()
{
UserStore = new Mock<IUserStore<OmbiUser>>();
PlexApi = new Mock<IPlexApi>();
AuthenticationSettings = new Mock<ISettingsService<AuthenticationSettings>>();
AuthenticationSettings.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(new AuthenticationSettings());
_um = new OmbiUserManager(UserStore.Object, null, null, null, null, null, null, null, null,
PlexApi.Object, null, null, AuthenticationSettings.Object);
}
public OmbiUserManager _um { get; set; }
private Mock<IUserStore<OmbiUser>> UserStore { get; set; }
private Mock<IPlexApi> PlexApi { get; set; }
private Mock<ISettingsService<AuthenticationSettings>> AuthenticationSettings { get; set; }
[Test]
public async Task CheckPassword_PlexUser_EmailLogin_ValidPassword()
{
var user = new OmbiUser
{
UserType = UserType.PlexUser,
EmailLogin = true,
Email = "MyEmail@email.com"
};
PlexApi.Setup(x => x.SignIn(It.IsAny<UserRequest>()))
.ReturnsAsync(new PlexAuthentication
{
user = new User
{
authentication_token = "abc"
}
});
var result = await _um.CheckPasswordAsync(user, "pass");
Assert.That(result, Is.True);
PlexApi.Verify(x => x.SignIn(It.Is<UserRequest>(c => c.login == "MyEmail@email.com")), Times.Once);
}
[Test]
public async Task CheckPassword_PlexUser_UserNameLogin_ValidPassword()
{
var user = new OmbiUser
{
UserType = UserType.PlexUser,
EmailLogin = false,
Email = "MyEmail@email.com",
UserName = "heyhey"
};
PlexApi.Setup(x => x.SignIn(It.IsAny<UserRequest>()))
.ReturnsAsync(new PlexAuthentication
{
user = new User
{
authentication_token = "abc"
}
});
var result = await _um.CheckPasswordAsync(user, "pass");
Assert.That(result, Is.True);
PlexApi.Verify(x => x.SignIn(It.Is<UserRequest>(c => c.login == "heyhey")), Times.Once);
}
[Test]
public async Task CheckPassword_PlexUser_UserNameLogin_InvalidPassword()
{
var user = new OmbiUser
{
UserType = UserType.PlexUser,
EmailLogin = false,
Email = "MyEmail@email.com",
UserName = "heyhey"
};
PlexApi.Setup(x => x.SignIn(It.IsAny<UserRequest>()))
.ReturnsAsync(new PlexAuthentication());
var result = await _um.CheckPasswordAsync(user, "pass");
Assert.That(result, Is.False);
PlexApi.Verify(x => x.SignIn(It.Is<UserRequest>(c => c.login == "heyhey")), Times.Once);
}
}
}

View file

@ -0,0 +1,195 @@

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using Moq;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.V2;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Tests.Engine
{
[TestFixture]
public class CalendarEngineTests
{
public Mock<IMovieRequestRepository> MovieRepo { get; set; }
public Mock<ITvRequestRepository> TvRepo { get; set; }
public CalendarEngine CalendarEngine { get; set; }
[SetUp]
public void Setup()
{
MovieRepo = new Mock<IMovieRequestRepository>();
TvRepo = new Mock<ITvRequestRepository>();
var principle = new Mock<IPrincipal>();
var identity = new Mock<IIdentity>();
identity.Setup(x => x.Name).Returns("UnitTest");
principle.Setup(x => x.Identity).Returns(identity.Object);
CalendarEngine = new CalendarEngine(principle.Object, null, null, MovieRepo.Object, TvRepo.Object);
}
[Test]
public async Task Calendar_Movies_OnlyGet_PreviousAndFuture_90_Days()
{
var movies = new List<MovieRequests>
{
new MovieRequests
{
Title="Invalid",
ReleaseDate = new DateTime(2018,10,01)
},
new MovieRequests
{
Title="Invalid",
ReleaseDate = DateTime.Now.AddDays(91)
},
new MovieRequests
{
Title="Valid",
ReleaseDate = DateTime.Now
}
};
MovieRepo.Setup(x => x.GetAll()).Returns(movies.AsQueryable());
var data = await CalendarEngine.GetCalendarData();
Assert.That(data.Count, Is.EqualTo(1));
Assert.That(data[0].Title, Is.EqualTo("Valid"));
}
[Test]
public async Task Calendar_Episodes_OnlyGet_PreviousAndFuture_90_Days()
{
var tv = new List<ChildRequests>
{
new ChildRequests
{
SeasonRequests = new List<SeasonRequests>
{
new SeasonRequests
{
Episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
Title = "Invalid",
AirDate = new DateTime(2018,01,01)
},
new EpisodeRequests
{
Title = "Invalid",
AirDate = DateTime.Now.AddDays(91)
},
new EpisodeRequests
{
Title = "Valid",
AirDate = DateTime.Now
},
}
}
}
},
};
TvRepo.Setup(x => x.GetChild()).Returns(tv.AsQueryable());
var data = await CalendarEngine.GetCalendarData();
Assert.That(data.Count, Is.EqualTo(1));
Assert.That(data[0].Title, Is.EqualTo("Valid"));
}
[TestCaseSource(nameof(StatusTvColorData))]
public async Task<string> Calendar_Tv_StatusColor(AvailabilityTestModel model)
{
var tv = new List<ChildRequests>
{
new ChildRequests
{
SeasonRequests = new List<SeasonRequests>
{
new SeasonRequests
{
Episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
Title = "Valid",
AirDate = DateTime.Now,
Approved = model.Approved,
Available = model.Available
},
}
}
}
},
};
TvRepo.Setup(x => x.GetChild()).Returns(tv.AsQueryable());
var data = await CalendarEngine.GetCalendarData();
return data[0].BackgroundColor;
}
[TestCaseSource(nameof(StatusColorData))]
public async Task<string> Calendar_Movie_StatusColor(AvailabilityTestModel model)
{
var movies = new List<MovieRequests>
{
new MovieRequests
{
Title="Valid",
ReleaseDate = DateTime.Now,
Denied = model.Denied,
Approved = model.Approved,
Available = model.Available
},
};
MovieRepo.Setup(x => x.GetAll()).Returns(movies.AsQueryable());
var data = await CalendarEngine.GetCalendarData();
return data[0].BackgroundColor;
}
public static IEnumerable<TestCaseData> StatusColorData
{
get
{
yield return new TestCaseData(new AvailabilityTestModel
{
Approved = true,
Denied = true
}).Returns("red").SetName("Calendar_DeniedRequest");
foreach (var testCaseData in StatusTvColorData)
{
yield return testCaseData;
}
}
}
public static IEnumerable<TestCaseData> StatusTvColorData
{
get
{
yield return new TestCaseData(new AvailabilityTestModel
{
Available = true,
Approved = true
}).Returns("#469c83").SetName("Calendar_AvailableRequest");
yield return new TestCaseData(new AvailabilityTestModel
{
Approved = true
}).Returns("blue").SetName("Calendar_ApprovedRequest");
}
}
}
public class AvailabilityTestModel
{
public bool Available { get; set; }
public bool Denied { get; set; }
public bool Approved { get; set; }
}
}

View file

@ -3,16 +3,19 @@ using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using AutoFixture;
using MockQueryable.Moq;
using Moq;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Engine;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Test.Common;
namespace Ombi.Core.Tests.Engine
{
@ -30,12 +33,17 @@ namespace Ombi.Core.Tests.Engine
MovieRequestEngine = new Mock<IMovieRequestEngine>();
MovieRequestEngine = new Mock<IMovieRequestEngine>();
User = new Mock<IPrincipal>();
UserManager = new Mock<OmbiUserManager>();
UserManager.Setup(x => x.Users)
.Returns(new EnumerableQuery<OmbiUser>(new List<OmbiUser> {new OmbiUser {Id = "abc"}}));
User.Setup(x => x.Identity.Name).Returns("abc");
UserManager = MockHelper.MockUserManager(new List<OmbiUser> { new OmbiUser { Id = "abc", UserName = "abc" } });
Rule = new Mock<IRuleEvaluator>();
Engine = new VoteEngine(VoteRepository.Object, User.Object, UserManager.Object, Rule.Object, VoteSettings.Object, MusicRequestEngine.Object,
TvRequestEngine.Object, MovieRequestEngine.Object);
F.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
.ForEach(b => F.Behaviors.Remove(b));
F.Behaviors.Add(new OmitOnRecursionBehavior());
}
public Fixture F { get; set; }
@ -49,25 +57,160 @@ namespace Ombi.Core.Tests.Engine
public Mock<ITvRequestEngine> TvRequestEngine { get; set; }
public Mock<IMovieRequestEngine> MovieRequestEngine { get; set; }
[Test]
[Ignore("Need to mock the user manager")]
public async Task New_Upvote()
[TestCaseSource(nameof(VoteData))]
public async Task Vote(VoteType type, RequestType request)
{
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings());
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings
{
Enabled = true,
MovieVoteMax = 10
});
var votes = F.CreateMany<Votes>().ToList();
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes)
.AsQueryable()
.BuildMock().Object);
var result = new VoteEngineResult();
if (type == VoteType.Downvote)
{
result = await Engine.DownVote(1, request);
}
else
{
result = await Engine.UpVote(1, request);
}
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);
}
public static IEnumerable<TestCaseData> VoteData
{
get
{
yield return new TestCaseData(VoteType.Upvote, RequestType.Movie).SetName("Movie_Upvote");
yield return new TestCaseData(VoteType.Downvote, RequestType.Movie).SetName("Movie_Downvote");
yield return new TestCaseData(VoteType.Upvote, RequestType.TvShow).SetName("Tv_Upvote");
yield return new TestCaseData(VoteType.Downvote, RequestType.TvShow).SetName("Tv_Downvote");
}
}
[TestCaseSource(nameof(AttemptedTwiceData))]
public async Task Attempted_Twice(VoteType type, RequestType request)
{
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings
{
Enabled = true,
MovieVoteMax = 10
});
var votes = F.CreateMany<Votes>().ToList();
votes.Add(new Votes
{
RequestId = 1,
RequestType = RequestType.Movie,
UserId = "abc"
UserId = "abc",
VoteType = type
});
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes));
var result = await Engine.UpVote(1, RequestType.Movie);
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes)
.AsQueryable()
.BuildMock().Object);
var result = new VoteEngineResult();
if (type == VoteType.Downvote)
{
result = await Engine.DownVote(1, request);
}
else
{
result = await Engine.UpVote(1, request);
}
Assert.That(result.Result, Is.False);
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Never);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
}
public static IEnumerable<TestCaseData> AttemptedTwiceData
{
get
{
yield return new TestCaseData(VoteType.Upvote, RequestType.Movie).SetName("Upvote_Attemped_Twice_Movie");
yield return new TestCaseData(VoteType.Downvote, RequestType.Movie).SetName("Downvote_Attempted_Twice_Movie");
yield return new TestCaseData(VoteType.Upvote, RequestType.TvShow).SetName("Upvote_Attemped_Twice_Tv");
yield return new TestCaseData(VoteType.Downvote, RequestType.TvShow).SetName("Downvote_Attempted_Twice_Tv");
}
}
[TestCaseSource(nameof(VoteConvertData))]
public async Task Downvote_Converted_To_Upvote(VoteType type, RequestType request)
{
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings
{
Enabled = true,
MovieVoteMax = 10
});
var votes = F.CreateMany<Votes>().ToList();
votes.Add(new Votes
{
RequestId = 1,
RequestType = request,
UserId = "abc",
VoteType = type == VoteType.Upvote ? VoteType.Downvote : VoteType.Upvote
});
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes)
.AsQueryable()
.BuildMock().Object);
var result = new VoteEngineResult();
if (type == VoteType.Downvote)
{
result = await Engine.DownVote(1, request);
}
else
{
result = await Engine.UpVote(1, request);
}
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);
}
public static IEnumerable<TestCaseData> VoteConvertData
{
get
{
yield return new TestCaseData(VoteType.Upvote, RequestType.Movie).SetName("Downvote_Converted_To_UpVote_Movie");
yield return new TestCaseData(VoteType.Downvote, RequestType.Movie).SetName("UpVote_Converted_To_DownVote_Movie");
yield return new TestCaseData(VoteType.Upvote, RequestType.TvShow).SetName("Downvote_Converted_To_UpVote_TvShow");
yield return new TestCaseData(VoteType.Downvote, RequestType.TvShow).SetName("UpVote_Converted_To_DownVote_TvShow");
}
}
[TestCaseSource(nameof(VotingDisabledData))]
public async Task Voting_Disabled(RequestType type)
{
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings
{
Enabled = false,
MovieVoteMax = 10
});
var result = await Engine.UpVote(1, type);
Assert.That(result.Result, Is.True);
VoteRepository.Verify(x => x.Add(It.Is<Votes>(c => c.UserId == "abc" && c.VoteType == VoteType.Upvote)), Times.Once);
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Once);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
VoteRepository.Verify(x => x.Add(It.IsAny<Votes>()), Times.Never);
}
public static IEnumerable<TestCaseData> VotingDisabledData
{
get
{
yield return new TestCaseData(RequestType.Movie).SetName("Voting_Disabled_Movie");
yield return new TestCaseData(RequestType.TvShow).SetName("Voting_Disabled_TV");
}
}
}
}

View file

@ -7,14 +7,16 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.5.0" />
<PackageReference Include="Moq" Version="4.10.0" />
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="Nunit" Version="3.11.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.0.1"></packagereference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
<ProjectReference Include="..\Ombi.Test.Common\Ombi.Test.Common.csproj" />
</ItemGroup>
</Project>

View file

@ -4,29 +4,43 @@ using Moq;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Store.Entities.Requests;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Helpers;
using Ombi.Test.Common;
using System.Collections.Generic;
using Ombi.Store.Entities;
using System;
namespace Ombi.Core.Tests.Rule.Request
{
[TestFixture]
public class AutoApproveRuleTests
{
private List<OmbiUser> _users = new List<OmbiUser>
{
new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="abc" }
};
[SetUp]
public void Setup()
{
PrincipalMock = new Mock<IPrincipal>();
Rule = new AutoApproveRule(PrincipalMock.Object);
PrincipalMock.Setup(x => x.Identity.Name).Returns("abc");
UserManager = MockHelper.MockUserManager(_users);
Rule = new AutoApproveRule(PrincipalMock.Object, UserManager.Object);
}
private AutoApproveRule Rule { get; set; }
private Mock<IPrincipal> PrincipalMock { get; set; }
private Mock<OmbiUserManager> UserManager { get; set; }
[Test]
public async Task Should_ReturnSuccess_WhenAdminAndRequestMovie()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Admin)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -37,7 +51,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenAdminAndRequestTV()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Admin)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -48,7 +62,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenAutoApproveMovieAndRequestMovie()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.AutoApproveMovie)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveMovie)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -56,10 +70,21 @@ namespace Ombi.Core.Tests.Rule.Request
Assert.True(request.Approved);
}
[Test]
public async Task Should_ReturnFail_WhenAutoApproveMovie_And_RequestTV()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveMovie)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
}
[Test]
public async Task Should_ReturnSuccess_WhenAutoApproveTVAndRequestTV()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.AutoApproveTv)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveTv)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -67,9 +92,21 @@ namespace Ombi.Core.Tests.Rule.Request
Assert.True(request.Approved);
}
[Test]
public async Task Should_ReturnFail_WhenAutoApproveTV_And_RequestMovie()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveTv)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
}
[Test]
public async Task Should_ReturnFail_WhenNoClaimsAndRequestMovie()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), It.IsAny<string>())).ReturnsAsync(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -80,6 +117,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnFail_WhenNoClaimsAndRequestTV()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), It.IsAny<string>())).ReturnsAsync(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);

View file

@ -1,31 +1,46 @@
using System;
using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Rule.Rules;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Test.Common;
namespace Ombi.Core.Tests.Rule.Request
{
public class CanRequestRuleTests
{
private List<OmbiUser> _users = new List<OmbiUser>
{
new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="abc" }
};
[SetUp]
public void Setup()
{
PrincipalMock = new Mock<IPrincipal>();
Rule = new CanRequestRule(PrincipalMock.Object);
PrincipalMock.Setup(x => x.Identity.Name).Returns("abc");
UserManager = MockHelper.MockUserManager(_users);
Rule = new CanRequestRule(PrincipalMock.Object, UserManager.Object);
}
private CanRequestRule Rule { get; set; }
private Mock<IPrincipal> PrincipalMock { get; set; }
private Mock<OmbiUserManager> UserManager { get; set; }
[Test]
public async Task Should_ReturnSuccess_WhenRequestingMovieWithMovieRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestMovie)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestMovie)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -35,7 +50,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnFail_WhenRequestingMovieWithoutMovieRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestMovie)).Returns(false);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestMovie)).ReturnsAsync(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -46,7 +61,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenRequestingMovieWithAdminRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Admin)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -56,7 +71,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenRequestingTVWithAdminRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Admin)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -66,7 +81,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenRequestingTVWithTVRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestTv)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestTv)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -76,7 +91,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnFail_WhenRequestingTVWithoutTVRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestTv)).Returns(false);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestTv)).ReturnsAsync(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);

View file

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using MockQueryable.Moq;
using Moq;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Rule.Rules;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
using Ombi.Test.Common;
namespace Ombi.Core.Tests.Rule.Request
{
public class ExistingMovieRequestRuleTests
{
[SetUp]
public void Setup()
{
ContextMock = new Mock<IMovieRequestRepository>();
Rule = new ExistingMovieRequestRule(ContextMock.Object);
}
private ExistingMovieRequestRule Rule { get; set; }
private Mock<IMovieRequestRepository> ContextMock { get; set; }
[Test]
public async Task ExistingRequestRule_Movie_Has_Been_Requested_With_TheMovieDBId()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<MovieRequests>
{
new MovieRequests
{
TheMovieDbId = 1,
RequestType = RequestType.Movie
}
}.AsQueryable().BuildMock().Object);
var o = new MovieRequests
{
TheMovieDbId = 1,
};
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_Has_Been_Requested_With_ImdbId()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<MovieRequests>
{
new MovieRequests
{
TheMovieDbId = 11111,
ImdbId = 1.ToString(),
RequestType = RequestType.Movie
}
}.AsQueryable().BuildMock().Object);
var o = new MovieRequests
{
ImdbId = 1.ToString(),
};
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_HasNot_Been_Requested()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<MovieRequests>
{
new MovieRequests
{
TheMovieDbId = 2,
ImdbId = "2",
RequestType = RequestType.Movie
}
}.AsQueryable().BuildMock().Object);
var o = new MovieRequests
{
TheMovieDbId = 1,
ImdbId = "1"
};
var result = await Rule.Execute(o);
Assert.That(result.Success, Is.True);
Assert.That(result.Message, Is.Null.Or.Empty);
}
}
}

View file

@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Tests.Rule.Search
{
public class AvailabilityRuleHelperTests
{
[Test]
public void Is_Available_When_All_We_Have_All_Aired_Episodes()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(1), // Tomorrow!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.True);
}
[Test]
public void Is_Available_When_All_We_Have_All_Aired_Episodes_With_Unknown_Dates()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.True);
}
[Test]
public void Is_PartlyAvailable_When_All_We_Have_Some_Aired_Episodes()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-14), // Yesterday
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.False);
Assert.That(model.PartlyAvailable, Is.True);
}
[Test]
public void Is_SeasonAvailable_When_All_We_Have_All_Aired_Episodes_In_A_Season()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-14), // Yesterday
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var availableEpisodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests>
{
new SeasonRequests { Episodes = episodes },
new SeasonRequests { Episodes = availableEpisodes },
}
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.False);
Assert.That(model.PartlyAvailable, Is.True);
Assert.That(model.SeasonRequests[1].SeasonAvailable, Is.True);
}
[Test]
public void Is_NotAvailable_When_All_We_Have_No_Aired_Episodes()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-14),
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.False);
Assert.That(model.PartlyAvailable, Is.False);
}
[Test]
public void Is_NotAvailable_When_All_Episodes_Are_Unknown()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.MinValue,
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue,
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.False);
Assert.That(model.PartlyAvailable, Is.False);
}
}
}

View file

@ -4,6 +4,8 @@ using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
@ -16,15 +18,18 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup()
{
ContextMock = new Mock<IEmbyContentRepository>();
Rule = new EmbyAvailabilityRule(ContextMock.Object);
SettingsMock = new Mock<ISettingsService<EmbySettings>>();
Rule = new EmbyAvailabilityRule(ContextMock.Object, SettingsMock.Object);
}
private EmbyAvailabilityRule Rule { get; set; }
private Mock<IEmbyContentRepository> ContextMock { get; set; }
private Mock<ISettingsService<EmbySettings>> SettingsMock { get; set; }
[Test]
public async Task Movie_ShouldBe_Available_WhenFoundInEmby()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings());
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
ProviderId = "123"
@ -39,6 +44,64 @@ namespace Ombi.Core.Tests.Rule.Search
Assert.True(search.Available);
}
[Test]
public async Task Movie_Has_Custom_Url_When_Specified_In_Settings()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings
{
Enable = true,
Servers = new List<EmbyServers>
{
new EmbyServers
{
ServerHostname = "http://test.com/"
}
}
});
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
ProviderId = "123",
EmbyId = 1.ToString()
});
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.That(search.EmbyUrl, Is.EqualTo("http://test.com/#!/itemdetails.html?id=1"));
}
[Test]
public async Task Movie_Uses_Default_Url_When()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings
{
Enable = true,
Servers = new List<EmbyServers>
{
new EmbyServers
{
ServerHostname = string.Empty
}
}
});
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
ProviderId = "123",
EmbyId = 1.ToString()
});
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.That(search.EmbyUrl, Is.EqualTo("https://app.emby.media/#!/itemdetails.html?id=1"));
}
[Test]
public async Task Movie_ShouldBe_NotAvailable_WhenNotFoundInEmby()
{

View file

@ -11,7 +11,7 @@ using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Tests.Rule.Search
{
public class ExistignRequestRuleTests
public class ExistingRequestRuleTests
{
[SetUp]
public void Setup()
@ -39,18 +39,16 @@ namespace Ombi.Core.Tests.Rule.Search
RequestType = RequestType.Movie
};
MovieMock.Setup(x => x.GetRequest(123)).Returns(list);
MovieMock.Setup(x => x.GetRequestAsync(123)).ReturnsAsync(list);
var search = new SearchMovieViewModel
{
Id = 123,
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.True(search.Approved);
Assert.True(search.Requested);
Assert.That(result.Success, Is.True);
Assert.That(search.Approved, Is.True);
Assert.That(search.Requested, Is.True);
}
[Test]
@ -62,7 +60,7 @@ namespace Ombi.Core.Tests.Rule.Search
Approved = true
};
MovieMock.Setup(x => x.GetRequest(123)).Returns(list);
MovieMock.Setup(x => x.GetRequestAsync(123)).ReturnsAsync(list);
var search = new SearchMovieViewModel
{
Id = 999,

View file

@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Tests.Rule.Search
{
public class LidarrAlbumCacheRuleTests
{
[SetUp]
public void Setup()
{
ContextMock = new Mock<IExternalRepository<LidarrAlbumCache>>();
Rule = new LidarrAlbumCacheRule(ContextMock.Object);
}
private LidarrAlbumCacheRule Rule { get; set; }
private Mock<IExternalRepository<LidarrAlbumCache>> ContextMock { get; set; }
[Test]
public async Task Should_Not_Be_Monitored_Or_Available()
{
var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.False(request.Monitored);
}
[Test]
public async Task Should_Be_Monitored_But_Not_Available()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrAlbumCache>
{
new LidarrAlbumCache
{
ForeignAlbumId = "abc",
PercentOfTracks = 0
}
}.AsQueryable());
var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.True(request.Monitored);
Assert.That(request.PartiallyAvailable, Is.EqualTo(false));
Assert.That(request.Available, Is.EqualTo(false));
Assert.That(request.FullyAvailable, Is.EqualTo(false));
}
[Test]
public async Task Should_Be_Monitored_And_Partly_Available()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrAlbumCache>
{
new LidarrAlbumCache
{
ForeignAlbumId = "abc",
PercentOfTracks = 1
}
}.AsQueryable());
var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.True(request.Monitored);
Assert.That(request.PartiallyAvailable, Is.EqualTo(true));
Assert.That(request.Available, Is.EqualTo(false));
Assert.That(request.FullyAvailable, Is.EqualTo(false));
}
[Test]
public async Task Should_Be_Monitored_And_Fully_Available()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrAlbumCache>
{
new LidarrAlbumCache
{
ForeignAlbumId = "abc",
PercentOfTracks = 100
}
}.AsQueryable());
var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.True(request.Monitored);
Assert.That(request.PartiallyAvailable, Is.EqualTo(false));
Assert.That(request.FullyAvailable, Is.EqualTo(true));
}
[Test]
public async Task Should_Be_Monitored_And_Fully_Available_Casing()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrAlbumCache>
{
new LidarrAlbumCache
{
ForeignAlbumId = "abc",
PercentOfTracks = 100
}
}.AsQueryable());
var request = new SearchAlbumViewModel { ForeignAlbumId = "ABC" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.True(request.Monitored);
Assert.That(request.PartiallyAvailable, Is.EqualTo(false));
Assert.That(request.FullyAvailable, Is.EqualTo(true));
}
}
}

View file

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Tests.Rule.Search
{
public class LidarrArtistCacheRuleTests
{
[SetUp]
public void Setup()
{
ContextMock = new Mock<IExternalRepository<LidarrArtistCache>>();
Rule = new LidarrArtistCacheRule(ContextMock.Object);
}
private LidarrArtistCacheRule Rule { get; set; }
private Mock<IExternalRepository<LidarrArtistCache>> ContextMock { get; set; }
[Test]
public async Task Should_Not_Be_Monitored()
{
var request = new SearchArtistViewModel { ForignArtistId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Monitored);
}
[Test]
public async Task Should_Be_Monitored()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrArtistCache>
{
new LidarrArtistCache
{
ForeignArtistId = "abc",
}
}.AsQueryable());
var request = new SearchArtistViewModel { ForignArtistId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.True(request.Monitored);
}
[Test]
public async Task Should_Be_Monitored_Casing()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrArtistCache>
{
new LidarrArtistCache
{
ForeignArtistId = "abc",
}
}.AsQueryable());
var request = new SearchArtistViewModel { ForignArtistId = "ABC" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.True(request.Monitored);
}
}
}

View file

@ -1,55 +0,0 @@
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Tests.Rule.Search
{
public class PlexAvailabilityRuleTests
{
[SetUp]
public void Setup()
{
ContextMock = new Mock<IPlexContentRepository>();
Rule = new PlexAvailabilityRule(ContextMock.Object);
}
private PlexAvailabilityRule Rule { get; set; }
private Mock<IPlexContentRepository> ContextMock { get; set; }
[Test]
public async Task ShouldBe_Available_WhenFoundInPlex()
{
ContextMock.Setup(x => x.Get(It.IsAny<string>())).ReturnsAsync(new PlexServerContent
{
Url = "TestUrl",
ImdbId = "132"
});
var search = new SearchMovieViewModel
{
ImdbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.AreEqual("TestUrl", search.PlexUrl);
Assert.True(search.Available);
}
[Test]
public async Task ShouldBe_NotAvailable_WhenNotFoundInPlex()
{
ContextMock.Setup(x => x.Get(It.IsAny<string>())).Returns(Task.FromResult(default(PlexServerContent)));
var search = new SearchMovieViewModel();
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.Null(search.PlexUrl);
Assert.False(search.Available);
}
}
}

View file

@ -18,7 +18,7 @@ namespace Ombi.Core.Tests
{
get
{
yield return new TestCaseData("this!is^a*string",new []{'!','^','*'}).Returns("thisisastring").SetName("Basic Strip Multipe Chars");
yield return new TestCaseData("this!is^a*string",new []{'!','^','*'}).Returns("thisisastring").SetName("Basic Strip Multiple Chars");
yield return new TestCaseData("What is this madness'",new []{'\'','^','*'}).Returns("What is this madness").SetName("Basic Strip Multipe Chars");
}
}

View file

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Config;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine.Demo
{
public class DemoMovieSearchEngine : MovieSearchEngine, IDemoMovieSearchEngine
{
public DemoMovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper,
ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s,
IRepository<RequestSubscription> sub, IOptions<DemoLists> lists)
: base(identity, service, movApi, mapper, logger, r, um, mem, s, sub)
{
_demoLists = lists.Value;
}
private readonly DemoLists _demoLists;
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
{
var result = await MovieApi.SearchMovie(search, null, "en");
for (var i = 0; i < result.Count; i++)
{
if (!_demoLists.Movies.Contains(result[i].Id))
{
result.RemoveAt(i);
}
}
if(result.Count > 0)
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
{
var rand = new Random();
var responses = new List<SearchMovieViewModel>();
for (int i = 0; i < 10; i++)
{
var item = rand.Next(_demoLists.Movies.Length);
var movie = _demoLists.Movies[item];
if (responses.Any(x => x.Id == movie))
{
i--;
continue;
}
var movieResult = await MovieApi.GetMovieInformationWithExtraInfo(movie);
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieResult);
responses.Add(await ProcessSingleMovie(viewMovie));
}
return responses;
}
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
{
return await NowPlayingMovies();
}
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
{
return await NowPlayingMovies();
}
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
{
return await NowPlayingMovies();
}
}
public interface IDemoMovieSearchEngine
{
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies();
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
Task<IEnumerable<SearchMovieViewModel>> Search(string search);
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
}
}

View file

@ -0,0 +1,96 @@
using AutoMapper;
using Microsoft.Extensions.Options;
using Ombi.Api.Trakt;
using Ombi.Api.TvMaze;
using Ombi.Config;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
namespace Ombi.Core.Engine.Demo
{
public class DemoTvSearchEngine : TvSearchEngine, IDemoTvSearchEngine
{
public DemoTvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper,
ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo,
IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache,
ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub, IOptions<DemoLists> lists)
: base(identity, service, tvMaze, mapper, plexSettings, embySettings, repo, embyRepo, trakt, r, um, memCache, s, sub)
{
_demoLists = lists.Value;
}
private readonly DemoLists _demoLists;
public async Task<IEnumerable<SearchTvShowViewModel>> Search(string search)
{
var searchResult = await TvMazeApi.Search(search);
for (var i = 0; i < searchResult.Count; i++)
{
if (!_demoLists.TvShows.Contains(searchResult[i].show?.externals?.thetvdb ?? 0))
{
searchResult.RemoveAt(i);
}
}
if (searchResult != null)
{
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in searchResult)
{
if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false))
{
continue;
}
retVal.Add(ProcessResult(tvMazeSearch));
}
return retVal;
}
return null;
}
public async Task<IEnumerable<SearchTvShowViewModel>> NowPlayingMovies()
{
var rand = new Random();
var responses = new List<SearchTvShowViewModel>();
for (int i = 0; i < 10; i++)
{
var item = rand.Next(_demoLists.TvShows.Length);
var tv = _demoLists.TvShows[item];
if (responses.Any(x => x.Id == tv))
{
i--;
continue;
}
var movieResult = await TvMazeApi.ShowLookup(tv);
responses.Add(ProcessResult(movieResult));
}
return responses;
}
}
public interface IDemoTvSearchEngine
{
Task<IEnumerable<SearchTvShowViewModel>> Search(string search);
Task<IEnumerable<SearchTvShowViewModel>> NowPlayingMovies();
}
}

View file

@ -1,12 +1,24 @@
using Ombi.Core.Models.Search;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
namespace Ombi.Core
namespace Ombi.Core.Engine.Interfaces
{
public interface IMovieEngineV2
{
Task<MovieFullInfoViewModel> GetFullMovieInformation(int theMovieDbId, string langCode = null);
Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode);
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies();
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies(int currentPosition, int amountToLoad);
Task<MovieCollectionsViewModel> GetCollection(int collectionId, string langCode = null);
Task<int> GetTvDbId(int theMovieDbId);
Task<IEnumerable<SearchMovieViewModel>> PopularMovies(int currentlyLoaded, int toLoad);
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies(int currentlyLoaded, int toLoad);
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies(int currentlyLoaded, int toLoad);
int ResultLimit { get; set; }
}
}

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.UI;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Engine.Interfaces
@ -13,10 +14,11 @@ namespace Ombi.Core.Engine.Interfaces
Task RemoveMovieRequest(int requestId);
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<RequestsViewModel<MovieRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder);
}
}

View file

@ -23,5 +23,6 @@ namespace Ombi.Core.Engine.Interfaces
Task<IEnumerable<TvRequests>> GetRequestsLite();
Task UpdateQualityProfile(int requestId, int profileId);
Task UpdateRootPath(int requestId, int rootPath);
Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder);
}
}

View file

@ -9,9 +9,13 @@ namespace Ombi.Core.Engine.Interfaces
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
Task<IEnumerable<SearchTvShowViewModel>> Popular();
Task<IEnumerable<SearchTvShowViewModel>> Popular(int currentlyLoaded, int amountToLoad);
Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
Task<IEnumerable<SearchTvShowViewModel>> Anticipated(int currentlyLoaded, int amountToLoad);
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
Task<IEnumerable<SearchTvShowViewModel>> Trending();
Task<IEnumerable<SearchTvShowViewModel>> MostWatches(int currentlyLoaded, int amountToLoad);
Task<IEnumerable<SearchTvShowViewModel>> Trending(int currentlyLoaded, int amountToLoad);
int ResultLimit { get; set; }
}
}

View file

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Ombi.Core.Models.Search.V2;
namespace Ombi.Core
{
public interface ITVSearchEngineV2
{
Task<SearchFullInfoTvShowViewModel> GetShowInformation(int tvdbid);
}
}

View file

@ -4,6 +4,7 @@ using Ombi.Helpers;
using Ombi.Store.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
@ -200,6 +201,54 @@ namespace Ombi.Core.Engine
};
}
public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder)
{
var shouldHide = await HideFromOtherUsers();
IQueryable<MovieRequests> allRequests;
if (shouldHide.Hide)
{
allRequests =
MovieRepository.GetWithUser(shouldHide
.UserId);
}
else
{
allRequests =
MovieRepository
.GetWithUser();
}
var prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(sortProperty, true);
if (sortProperty.Contains('.'))
{
// This is a navigation property currently not supported
prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find("RequestedDate", true);
//var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
//var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
//var propType = firstProp.PropertyType;
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
}
allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
? allRequests.OrderBy(x => prop.GetValue(x))
: allRequests.OrderByDescending(x => prop.GetValue(x));
var total = await allRequests.CountAsync();
var requests = await allRequests.Skip(position).Take(count)
.ToListAsync();
requests.ForEach(async x =>
{
x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath);
await CheckForSubscription(shouldHide, x);
});
return new RequestsViewModel<MovieRequests>
{
Collection = requests,
Total = total
};
}
private IQueryable<MovieRequests> OrderMovies(IQueryable<MovieRequests> allRequests, OrderType type)
{
switch (type)
@ -259,6 +308,15 @@ namespace Ombi.Core.Engine
return allRequests;
}
public async Task<MovieRequests> GetRequest(int requestId)
{
var request = await MovieRepository.GetWithUser().Where(x => x.Id == requestId).FirstOrDefaultAsync();
request.PosterPath = PosterPathHelper.FixPosterPath(request.PosterPath);
await CheckForSubscription(new HideResult(), request);
return request;
}
private async Task CheckForSubscription(HideResult shouldHide, MovieRequests x)
{
if (shouldHide.UserId == x.RequestedUserId)
@ -493,7 +551,7 @@ namespace Ombi.Core.Engine
RequestType = RequestType.Movie,
});
return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!", RequestId = model.Id};
return new RequestEngineResult { Result = true, Message = $"{movieName} has been successfully added!", RequestId = model.Id };
}
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
@ -533,7 +591,7 @@ namespace Ombi.Core.Engine
return new RequestQuotaCountModel()
{
HasLimit = true,
HasLimit = true,
Limit = limit,
Remaining = count,
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),

View file

@ -31,10 +31,11 @@ namespace Ombi.Core.Engine
Logger = logger;
}
private IMovieDbApi MovieApi { get; }
private IMapper Mapper { get; }
protected IMovieDbApi MovieApi { get; }
protected IMapper Mapper { get; }
private ILogger<MovieSearchEngine> Logger { get; }
protected const int MovieLimit = 10;
/// <summary>
/// Lookups the imdb information.
@ -185,7 +186,7 @@ namespace Ombi.Core.Engine
return null;
}
private async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
IEnumerable<MovieSearchResult> movies)
{
var viewMovies = new List<SearchMovieViewModel>();
@ -196,7 +197,7 @@ namespace Ombi.Core.Engine
return viewMovies;
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false)
protected async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false)
{
if (lookupExtraInfo && viewMovie.ImdbId.IsNullOrEmpty())
{
@ -214,7 +215,7 @@ namespace Ombi.Core.Engine
// This requires the rules to be run first to populate the RequestId property
await CheckForSubscription(viewMovie);
return viewMovie;
}
@ -228,7 +229,7 @@ namespace Ombi.Core.Engine
}
var request = await RequestService.MovieRequestService.GetAll()
.AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id);
if (request)
if (request || viewModel.Available)
{
viewModel.ShowSubscribe = false;
}

View file

@ -69,6 +69,12 @@ namespace Ombi.Core.Engine
};
}
if(album?.artist == null)
{
// Lookup the artist
//album.artist = await _lidarrApi.ArtistLookup(album.artist, s.ApiKey, s.FullUri);
}
var userDetails = await GetUser();
var requestModel = new AlbumRequest
@ -83,7 +89,7 @@ namespace Ombi.Core.Engine
Title = album.title,
Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url,
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty,
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty, // This needs to be populated to send to Lidarr for new requests
RequestedByAlias = model.RequestedByAlias
};
if (requestModel.Cover.IsNullOrEmpty())

View file

@ -7,6 +7,7 @@ using Ombi.Core.Models.Search;
using Ombi.Helpers;
using Ombi.Store.Entities;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
@ -31,14 +32,13 @@ namespace Ombi.Core.Engine
{
public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager,
ITvSender sender, IAuditRepository audit, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
ITvSender sender, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
IRepository<RequestSubscription> sub) : base(user, requestService, rule, manager, cache, settings, sub)
{
TvApi = tvApi;
MovieDbApi = movApi;
NotificationHelper = helper;
TvSender = sender;
Audit = audit;
_requestLog = rl;
}
@ -46,7 +46,6 @@ namespace Ombi.Core.Engine
private ITvMazeApi TvApi { get; }
private IMovieDbApi MovieDbApi { get; }
private ITvSender TvSender { get; }
private IAuditRepository Audit { get; }
private readonly IRepository<RequestLog> _requestLog;
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
@ -84,8 +83,6 @@ namespace Ombi.Core.Engine
}
}
await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username);
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId);
if (existingRequest != null)
{
@ -160,7 +157,7 @@ namespace Ombi.Core.Engine
.ThenInclude(x => x.Episodes)
.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
.Skip(position).Take(count).ToListAsync();
}
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
@ -225,6 +222,59 @@ namespace Ombi.Core.Engine
}
public async Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder)
{
var shouldHide = await HideFromOtherUsers();
List<ChildRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = await TvRepository.GetChild(shouldHide.UserId).ToListAsync();
// Filter out children
FilterChildren(allRequests, shouldHide);
}
else
{
allRequests = await TvRepository.GetChild().ToListAsync();
}
if (allRequests == null)
{
return new RequestsViewModel<ChildRequests>();
}
var total = allRequests.Count;
var prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find(sortProperty, true);
if (sortProperty.Contains('.'))
{
// This is a navigation property currently not supported
prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find("Title", true);
//var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
//var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
//var propType = firstProp.PropertyType;
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
}
allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
? allRequests.OrderBy(x => prop.GetValue(x)).ToList()
: allRequests.OrderByDescending(x => prop.GetValue(x)).ToList();
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
// Make sure we do not show duplicate child requests
allRequests = allRequests.DistinctBy(x => x.ParentRequest.Title).ToList();
return new RequestsViewModel<ChildRequests>
{
Collection = allRequests,
Total = total,
};
}
public async Task<IEnumerable<TvRequests>> GetRequestsLite()
{
var shouldHide = await HideFromOtherUsers();
@ -282,17 +332,22 @@ namespace Ombi.Core.Engine
private static void FilterChildren(TvRequests t, HideResult shouldHide)
{
// Filter out children
FilterChildren(t.ChildRequests, shouldHide);
}
for (var j = 0; j < t.ChildRequests.Count; j++)
private static void FilterChildren(List<ChildRequests> t, HideResult shouldHide)
{
// Filter out children
for (var j = 0; j < t.Count; j++)
{
var child = t.ChildRequests[j];
var child = t[j];
if (child.RequestedUserId != shouldHide.UserId)
{
t.ChildRequests.RemoveAt(j);
t.RemoveAt(j);
j--;
}
}
}
public async Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId)
@ -351,7 +406,6 @@ namespace Ombi.Core.Engine
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id);
@ -385,6 +439,7 @@ namespace Ombi.Core.Engine
foreach (var ep in s.Episodes)
{
ep.Approved = true;
ep.Requested = true;
}
}
@ -393,7 +448,6 @@ namespace Ombi.Core.Engine
if (request.Approved)
{
NotificationHelper.Notify(request, NotificationType.RequestApproved);
await Audit.Record(AuditType.Approved, AuditArea.TvRequest, $"Approved Request {request.Title}", Username);
// Autosend
await TvSender.Send(request);
}
@ -425,9 +479,7 @@ namespace Ombi.Core.Engine
public async Task<ChildRequests> UpdateChildRequest(ChildRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
await TvRepository.UpdateChild(request);
await TvRepository.UpdateChild(request);
return request;
}
@ -445,16 +497,14 @@ namespace Ombi.Core.Engine
// Delete the parent
TvRepository.Db.TvRequests.Remove(parent);
}
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
await TvRepository.Db.SaveChangesAsync();
}
public async Task RemoveTvRequest(int requestId)
{
var request = await TvRepository.Get().FirstOrDefaultAsync(x => x.Id == requestId);
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
await TvRepository.Delete(request);
await TvRepository.Delete(request);
}
public async Task<bool> UserHasRequest(string userId)
@ -569,7 +619,7 @@ namespace Ombi.Core.Engine
return await AfterRequest(model.ChildRequests.FirstOrDefault());
}
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
{
foreach (var value in items)
{
@ -606,7 +656,7 @@ namespace Ombi.Core.Engine
var result = await TvSender.Send(model);
if (result.Success)
{
return new RequestEngineResult { Result = true, RequestId = model.Id};
return new RequestEngineResult { Result = true, RequestId = model.Id };
}
return new RequestEngineResult
{
@ -659,10 +709,10 @@ namespace Ombi.Core.Engine
DateTime oldestRequestedAt = await log.OrderBy(x => x.RequestDate)
.Select(x => x.RequestDate)
.FirstOrDefaultAsync();
return new RequestQuotaCountModel()
{
HasLimit = true,
HasLimit = true,
Limit = limit,
Remaining = count,
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),

View file

@ -21,6 +21,8 @@ using Ombi.Core.Authentication;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using TraktApiSharp.Objects.Get.Shows;
using TraktApiSharp.Objects.Get.Shows.Common;
namespace Ombi.Core.Engine
{
@ -40,8 +42,8 @@ namespace Ombi.Core.Engine
EmbyContentRepo = embyRepo;
}
private ITvMazeApi TvMazeApi { get; }
private IMapper Mapper { get; }
protected ITvMazeApi TvMazeApi { get; }
protected IMapper Mapper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexContentRepository PlexContentRepo { get; }
@ -99,7 +101,7 @@ namespace Ombi.Core.Engine
{
Url = e.url,
Title = e.name,
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()),
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
EpisodeNumber = e.number,
});
@ -112,7 +114,7 @@ namespace Ombi.Core.Engine
{
Url = e.url,
Title = e.name,
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()),
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
EpisodeNumber = e.number,
});
}
@ -127,6 +129,19 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> Popular(int currentlyLoaded, int amountToLoad)
{
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktShow>();
foreach (var pagesToLoad in pages)
{
var apiResult = await TraktApi.GetPopularShows(pagesToLoad.Page, ResultLimit);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated()
{
@ -135,6 +150,19 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated(int currentlyLoaded, int amountToLoad)
{
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktMostAnticipatedShow>();
foreach (var pagesToLoad in pages)
{
var apiResult = await TraktApi.GetAnticipatedShows(pagesToLoad.Page, ResultLimit);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
{
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(null, ResultLimit), DateTime.Now.AddHours(12));
@ -149,7 +177,33 @@ namespace Ombi.Core.Engine
return processed;
}
private IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches(int currentlyLoaded, int amountToLoad)
{
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktMostWatchedShow>();
foreach (var pagesToLoad in pages)
{
var apiResult = await TraktApi.GetMostWatchesShows(null, pagesToLoad.Page, ResultLimit);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> Trending(int currentlyLoaded, int amountToLoad)
{
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktTrendingShow>();
foreach (var pagesToLoad in pages)
{
var apiResult = await TraktApi.GetTrendingShows(pagesToLoad.Page, ResultLimit);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
return processed;
}
protected IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
{
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items)
@ -159,7 +213,7 @@ namespace Ombi.Core.Engine
return retVal;
}
private SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
protected SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
{
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
}

View file

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Ombi.Api.Sonarr.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.Search.V2;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Engine.V2
{
public class CalendarEngine : BaseEngine, ICalendarEngine
{
public DateTime DaysAgo => DateTime.Now.AddDays(-90);
public DateTime DaysAhead => DateTime.Now.AddDays(90);
public CalendarEngine(IPrincipal user, OmbiUserManager um, IRuleEvaluator rules, IMovieRequestRepository movieRepo,
ITvRequestRepository tvRequestRepo) : base(user, um, rules)
{
_movieRepo = movieRepo;
_tvRepo = tvRequestRepo;
}
private readonly IMovieRequestRepository _movieRepo;
private readonly ITvRequestRepository _tvRepo;
public async Task<List<CalendarViewModel>> GetCalendarData()
{
var viewModel = new List<CalendarViewModel>();
var movies = _movieRepo.GetAll().Where(x =>
x.ReleaseDate > DaysAgo && x.ReleaseDate < DaysAhead);
var episodes = _tvRepo.GetChild().SelectMany(x => x.SeasonRequests.SelectMany(e => e.Episodes
.Where(w => w.AirDate > DaysAgo && w.AirDate < DaysAhead)));
foreach (var e in episodes)
{
viewModel.Add(new CalendarViewModel
{
Title = e.Title,
Start = e.AirDate.Date,
Type = RequestType.TvShow,
BackgroundColor = GetBackgroundColor(e),
ExtraParams = new List<ExtraParams>
{
new ExtraParams { Overview = e.Season?.ChildRequest?.ParentRequest?.Overview ?? string.Empty, ProviderId = e.Season?.ChildRequest?.ParentRequest?.TvDbId ?? 0}
}
});
}
foreach (var m in movies)
{
viewModel.Add(new CalendarViewModel
{
Title = m.Title,
Start = m.ReleaseDate.Date,
BackgroundColor = GetBackgroundColor(m),
Type = RequestType.Movie,
ExtraParams = new List<ExtraParams>
{
new ExtraParams { Overview = m.Overview, ProviderId = m.TheMovieDbId}
}
});
}
return viewModel;
}
private string GetBackgroundColor(BaseRequest req)
{
if (req.Available)
{
return "#469c83";
}
if (!req.Available)
{
if (req.Denied ?? false)
{
return "red";
}
if (req.Approved)
{
// We are approved state
return "blue";
}
if (!req.Approved)
{
// Processing
return "teal";
}
}
return "gray";
}
private string GetBackgroundColor(EpisodeRequests req)
{
if (req.Available)
{
return "#469c83";
}
if (!req.Available)
{
if (req.Approved)
{
// We are approved state
return "blue";
}
if (!req.Approved)
{
// Processing
return "teal";
}
}
return "gray";
}
}
}

View file

@ -0,0 +1,11 @@
using Ombi.Core.Models.Search.V2;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Ombi.Core.Engine.V2
{
public interface ICalendarEngine
{
Task<List<CalendarViewModel>> GetCalendarData();
}
}

View file

@ -4,8 +4,10 @@ using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Helpers;
@ -17,9 +19,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Ombi.Core.Models.Search.V2;
namespace Ombi.Core.Engine
namespace Ombi.Core.Engine.V2
{
public class MovieSearchEngineV2 : BaseMediaEngine, IMovieEngineV2
{
@ -45,6 +46,198 @@ namespace Ombi.Core.Engine
return await ProcessSingleMovie(movieInfo);
}
public async Task<MovieCollectionsViewModel> GetCollection(int collectionId, string langCode = null)
{
langCode = await DefaultLanguageCode(langCode);
var collections = await MovieApi.GetCollection(langCode, collectionId);
var c = await ProcessCollection(collections);
c.Collection = c.Collection.OrderBy(x => x.ReleaseDate).ToList();
return c;
}
public async Task<int> GetTvDbId(int theMovieDbId)
{
var result = await MovieApi.GetTvExternals(theMovieDbId);
return result.tvdb_id;
}
/// <summary>
/// Get similar movies to the id passed in
/// </summary>
/// <param name="theMovieDbId"></param>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode)
{
langCode = await DefaultLanguageCode(langCode);
var result = await MovieApi.SimilarMovies(theMovieDbId, langCode);
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
/// <summary>
/// Gets popular movies.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
{
var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () =>
{
var langCode = await DefaultLanguageCode(null);
return await MovieApi.PopularMovies(langCode);
}, DateTime.Now.AddHours(12));
if (result != null)
{
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
private const int _theMovieDbMaxPageItems = 20;
/// <summary>
/// Gets popular movies by paging
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies(int currentlyLoaded, int toLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await MovieApi.PopularMovies(langCode, pagesToLoad.Page);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
return await TransformMovieResultsToResponse(results);
}
/// <summary>
/// Gets top rated movies.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
{
var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () =>
{
var langCode = await DefaultLanguageCode(null);
return await MovieApi.TopRated(langCode);
}, DateTime.Now.AddHours(12));
if (result != null)
{
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies(int currentPosition, int amountToLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await MovieApi.TopRated(langCode, pagesToLoad.Page);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
return await TransformMovieResultsToResponse(results);
}
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies(int currentPosition, int amountToLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await MovieApi.NowPlaying(langCode, pagesToLoad.Page);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
return await TransformMovieResultsToResponse(results);
}
/// <summary>
/// Gets upcoming movies.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
{
var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () =>
{
var langCode = await DefaultLanguageCode(null);
return await MovieApi.Upcoming(langCode);
}, DateTime.Now.AddHours(12));
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies(int currentPosition, int amountToLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await MovieApi.Upcoming(langCode, pagesToLoad.Page);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
return await TransformMovieResultsToResponse(results);
}
/// <summary>
/// Gets now playing movies.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
{
var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () =>
{
var langCode = await DefaultLanguageCode(null);
return await MovieApi.NowPlaying(langCode);
}, DateTime.Now.AddHours(12));
if (result != null)
{
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
IEnumerable<MovieSearchResult> movies)
{
var viewMovies = new List<SearchMovieViewModel>();
foreach (var movie in movies)
{
viewMovies.Add(await ProcessSingleMovie(movie));
}
return viewMovies;
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieSearchResult movie)
{
var viewMovie = Mapper.Map<SearchMovieViewModel>(movie);
return await ProcessSingleMovie(viewMovie);
}
private async Task<MovieFullInfoViewModel> ProcessSingleMovie(FullMovieInfo movie)
{
var viewMovie = Mapper.Map<SearchMovieViewModel>(movie);
@ -65,6 +258,50 @@ namespace Ombi.Core.Engine
return mapped;
}
private async Task<MovieCollectionsViewModel> ProcessCollection(Collections collection)
{
var viewMovie = Mapper.Map<MovieCollectionsViewModel>(collection);
foreach (var movie in viewMovie.Collection)
{
var mappedMovie = Mapper.Map<SearchMovieViewModel>(movie);
await RunSearchRules(mappedMovie);
// This requires the rules to be run first to populate the RequestId property
await CheckForSubscription(mappedMovie);
var mapped = Mapper.Map<MovieCollection>(movie);
mapped.Available = movie.Available;
mapped.RequestId = movie.RequestId;
mapped.Requested = movie.Requested;
mapped.PlexUrl = movie.PlexUrl;
mapped.EmbyUrl = movie.EmbyUrl;
mapped.Subscribed = movie.Subscribed;
mapped.ShowSubscribe = movie.ShowSubscribe;
mapped.ReleaseDate = movie.ReleaseDate;
}
return viewMovie;
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie)
{
if (viewMovie.ImdbId.IsNullOrEmpty())
{
var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id);
viewMovie.Id = showInfo.Id; // TheMovieDbId
viewMovie.ImdbId = showInfo.ImdbId;
}
viewMovie.TheMovieDbId = viewMovie.Id.ToString();
await RunSearchRules(viewMovie);
// This requires the rules to be run first to populate the RequestId property
await CheckForSubscription(viewMovie);
return viewMovie;
}
private async Task CheckForSubscription(SearchViewModel viewModel)
{
// Check if this user requested it

View file

@ -0,0 +1,154 @@
using AutoMapper;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using TraktApiSharp.Objects.Get.Shows;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Repository.Requests;
using Ombi.Core.Authentication;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Api.Trakt;
using Ombi.Api.TvMaze;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine.V2
{
public class TvSearchEngineV2 : BaseMediaEngine, ITVSearchEngineV2
{
public TvSearchEngineV2(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um,
ICacheService memCache, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub)
: base(identity, service, r, um, memCache, s, sub)
{
TvMazeApi = tvMaze;
Mapper = mapper;
PlexSettings = plexSettings;
EmbySettings = embySettings;
PlexContentRepo = repo;
TraktApi = trakt;
EmbyContentRepo = embyRepo;
}
private ITvMazeApi TvMazeApi { get; }
private IMapper Mapper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexContentRepository PlexContentRepo { get; }
private IEmbyContentRepository EmbyContentRepo { get; }
private ITraktApi TraktApi { get; }
public async Task<SearchFullInfoTvShowViewModel> GetShowInformation(int tvdbid)
{
var tvdbshow = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid);
var show = await TvMazeApi.GetTvFullInformation(tvdbshow.id);
if (show == null)
{
// We don't have enough information
return null;
}
// Setup the task so we can get the data later on if we have a IMDBID
Task<TraktShow> traktInfoTask = new Task<TraktShow>(() => null);
if (show.externals?.imdb.HasValue() ?? false)
{
traktInfoTask = TraktApi.GetTvExtendedInfo(show.externals?.imdb);
}
var mapped = Mapper.Map<SearchFullInfoTvShowViewModel>(show);
foreach (var e in show._embedded.episodes)
{
var season = mapped.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == e.season);
if (season == null)
{
var newSeason = new SeasonRequests
{
SeasonNumber = e.season,
Episodes = new List<EpisodeRequests>()
};
newSeason.Episodes.Add(new EpisodeRequests
{
Url = e.url,
Title = e.name,
AirDate = e.airstamp,
EpisodeNumber = e.number,
});
mapped.SeasonRequests.Add(newSeason);
}
else
{
// We already have the season, so just add the episode
season.Episodes.Add(new EpisodeRequests
{
Url = e.url,
Title = e.name,
AirDate = e.airstamp,
EpisodeNumber = e.number,
});
}
}
return await ProcessResult(mapped, traktInfoTask);
}
private IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
{
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items)
{
retVal.Add(ProcessResult(tvMazeSearch));
}
return retVal;
}
private SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
{
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
}
private async Task<SearchFullInfoTvShowViewModel> ProcessResult(SearchFullInfoTvShowViewModel item, Task<TraktShow> showInfoTask)
{
item.TheTvDbId = item.Id.ToString();
var oldModel = Mapper.Map<SearchTvShowViewModel>(item);
await RunSearchRules(oldModel);
item.Available = oldModel.Available;
item.FullyAvailable = oldModel.FullyAvailable;
item.PartlyAvailable = oldModel.PartlyAvailable;
item.Requested = oldModel.Requested;
item.Available = oldModel.Available;
item.Approved = oldModel.Approved;
item.SeasonRequests = oldModel.SeasonRequests;
item.RequestId = oldModel.RequestId;
return await GetExtraInfo(showInfoTask, item);
}
private async Task<SearchFullInfoTvShowViewModel> GetExtraInfo(Task<TraktShow> showInfoTask, SearchFullInfoTvShowViewModel model)
{
var result = await showInfoTask;
if(result == null)
{
return model;
}
model.Trailer = result.Trailer;
model.Certification = result.Certification;
model.Homepage = result.Homepage;
return model;
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search.V2
{
public class CalendarViewModel
{
public string Title { get; set; }
public DateTime Start { get; set; }
public string BackgroundColor { get; set; }
public RequestType Type { get; set; }
public List<ExtraParams> ExtraParams { get; set; }
public string BorderColor
{
get
{
switch (Type)
{
case RequestType.TvShow:
return "#ff0000";
case RequestType.Movie:
return "#0d5a3e";
case RequestType.Album:
return "#797979";
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
public class ExtraParams
{
public int ProviderId { get; set; }
public string Overview { get; set; }
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search.V2
{
public class MovieCollectionsViewModel
{
public string Name { get; set; }
public string Overview { get; set; }
public List<MovieCollection> Collection { get; set; }
}
public class MovieCollection : SearchViewModel
{
public int Id { get; set; }
public string Overview { get; set; }
public string PosterPath { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public override RequestType Type => RequestType.Movie;
}
}

View file

@ -8,6 +8,7 @@ namespace Ombi.Core.Models.Search.V2
public class MovieFullInfoViewModel : SearchViewModel
{
public bool Adult { get; set; }
public CollectionsViewModel BelongsToCollection { get; set; }
public string BackdropPath { get; set; }
public string OriginalLanguage { get; set; }
public int Budget { get; set; }
@ -39,8 +40,26 @@ namespace Ombi.Core.Models.Search.V2
public Similar Similar { get; set; }
public Recommendations Recommendations { get; set; }
public ExternalIds ExternalIds { get; set; }
public Keywords Keywords { get; set; }
}
public class Keywords
{
public List<KeywordsValue> KeywordsValue { get; set; }
}
public class KeywordsValue
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CollectionsViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string PosterPath { get; set; }
public string BackdropPath { get; set; }
}
public class ExternalIds
{
public string ImdbId { get; set; }

View file

@ -0,0 +1,114 @@
using Ombi.Store.Repository.Requests;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search.V2
{
public class SearchFullInfoTvShowViewModel : SearchViewModel
{
public string Title { get; set; }
public List<string> Aliases { get; set; }
public string Banner { get; set; }
public int SeriesId { get; set; }
public string Status { get; set; }
public string FirstAired { get; set; }
public string NetworkId { get; set; }
public string Runtime { get; set; }
public List<string> Genre { get; set; }
public string Overview { get; set; }
public int LastUpdated { get; set; }
public string AirsDayOfWeek { get; set; }
public string AirsTime { get; set; }
public string Rating { get; set; }
public int SiteRating { get; set; }
public NetworkViewModel Network { get; set; }
public Images Images { get; set; }
public List<CastViewModel> Cast { get; set; }
public List<CrewViewModel> Crew { get; set; }
public string Certification { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Trailer { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Homepage { get; set; }
public List<SeasonRequests> SeasonRequests { get; set; } = new List<SeasonRequests>();
/// <summary>
/// If we are requesting the entire series
/// </summary>
public bool RequestAll { get; set; }
public bool FirstSeason { get; set; }
public bool LatestSeason { get; set; }
/// <summary>
/// This is where we have EVERY Episode for that series
/// </summary>
public bool FullyAvailable { get; set; }
// We only have some episodes
public bool PartlyAvailable { get; set; }
public override RequestType Type => RequestType.TvShow;
}
public class NetworkViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public Country Country { get; set; }
}
public class Country
{
public string Name { get; set; }
public string Code { get; set; }
public string Timezone { get; set; }
}
public class Images
{
public string Medium { get; set; }
public string Original { get; set; }
}
public class CastViewModel
{
public PersonViewModel Person { get; set; }
public CharacterViewModel Character { get; set; }
public bool Self { get; set; }
public bool Voice { get; set; }
}
public class PersonViewModel
{
public int Id { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public Images Image { get; set; }
}
public class CharacterViewModel
{
public int Id { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public Images Image { get; set; }
}
public class CrewViewModel
{
public string Type { get; set; }
public PersonViewModel Person { get; set; }
}
}

View file

@ -0,0 +1,23 @@

using System.Collections.Generic;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.UI
{
/// <summary>
/// The view model for the notification settings page
/// </summary>
/// <seealso cref="GotifyNotificationSettings" />
public class GotifyNotificationViewModel : GotifySettings
{
/// <summary>
/// Gets or sets the notification templates.
/// </summary>
/// <value>
/// The notification templates.
/// </value>
public List<NotificationTemplates> NotificationTemplates { get; set; }
}
}

View file

@ -11,10 +11,9 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
<PackageReference Include="Hangfire" Version="1.6.21" />
<PackageReference Include="Hangfire" Version="1.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
</ItemGroup>

View file

@ -1,5 +1,7 @@
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
@ -10,28 +12,31 @@ namespace Ombi.Core.Rule.Rules.Request
{
public class AutoApproveRule : BaseRequestRule, IRules<BaseRequest>
{
public AutoApproveRule(IPrincipal principal)
public AutoApproveRule(IPrincipal principal, OmbiUserManager um)
{
User = principal;
_manager = um;
}
private IPrincipal User { get; }
private readonly OmbiUserManager _manager;
public Task<RuleResult> Execute(BaseRequest obj)
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiRoles.Admin))
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
{
obj.Approved = true;
return Task.FromResult(Success());
return Success();
}
if (obj.RequestType == RequestType.Movie && User.IsInRole(OmbiRoles.AutoApproveMovie))
if (obj.RequestType == RequestType.Movie && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
obj.Approved = true;
if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv))
if (obj.RequestType == RequestType.TvShow && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
obj.Approved = true;
if (obj.RequestType == RequestType.Album && User.IsInRole(OmbiRoles.AutoApproveMusic))
if (obj.RequestType == RequestType.Album && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
obj.Approved = true;
return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve
return Success(); // We don't really care, we just don't set the obj to approve
}
}
}

View file

@ -1,46 +1,52 @@
using Ombi.Store.Entities;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Rule.Rules
namespace Ombi.Core.Rule.Rules.Request
{
public class CanRequestRule : BaseRequestRule, IRules<BaseRequest>
{
public CanRequestRule(IPrincipal principal)
public CanRequestRule(IPrincipal principal, OmbiUserManager manager)
{
User = principal;
_manager = manager;
}
private IPrincipal User { get; }
private readonly OmbiUserManager _manager;
public Task<RuleResult> Execute(BaseRequest obj)
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiRoles.Admin))
return Task.FromResult(Success());
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
return Success();
if (obj.RequestType == RequestType.Movie)
{
if (User.IsInRole(OmbiRoles.RequestMovie) || User.IsInRole(OmbiRoles.AutoApproveMovie))
return Task.FromResult(Success());
return Task.FromResult(Fail("You do not have permissions to Request a Movie"));
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
return Success();
return Fail("You do not have permissions to Request a Movie");
}
if (obj.RequestType == RequestType.TvShow)
{
if (User.IsInRole(OmbiRoles.RequestTv) || User.IsInRole(OmbiRoles.AutoApproveTv))
return Task.FromResult(Success());
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestTv) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
return Success();
}
if (obj.RequestType == RequestType.Album)
{
if (User.IsInRole(OmbiRoles.RequestMusic) || User.IsInRole(OmbiRoles.AutoApproveMusic))
return Task.FromResult(Success());
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMusic) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
return Success();
}
return Task.FromResult(Fail("You do not have permissions to Request a TV Show"));
return Fail("You do not have permissions to Request a TV Show");
}
}
}

View file

@ -1,6 +1,8 @@
using System.Threading.Tasks;
using System;
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;
@ -28,8 +30,24 @@ namespace Ombi.Core.Rule.Rules.Request
{
var movie = (MovieRequests) obj;
var movieRequests = Movie.GetAll();
var found = false;
var existing = await movieRequests.FirstOrDefaultAsync(x => x.TheMovieDbId == movie.TheMovieDbId);
if (existing != null) // Do we already have a request for this?
{
found = true;
}
if (!found && movie.ImdbId.HasValue())
{
// Let's check imdbid
existing = await movieRequests.FirstOrDefaultAsync(x =>
x.ImdbId.Equals(movie.ImdbId, StringComparison.CurrentCultureIgnoreCase));
if (existing != null)
{
found = true;
}
}
if(found)
{
return Fail($"\"{obj.Title}\" has already been requested");
}

View file

@ -32,7 +32,7 @@ namespace Ombi.Core.Rule.Rules.Request
var tvContent = _plexContent.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Show);
// We need to do a check on the TVDBId
var anyTvDbMatches = await tvContent.Include(x => x.Episodes).FirstOrDefaultAsync(x => x.HasTvDb && x.TvDbId.Equals(tvRequest.Id.ToString())); // the Id on the child is the tvdbid at this point
var anyTvDbMatches = await tvContent.Include(x => x.Episodes).FirstOrDefaultAsync(x => x.HasTvDb && x.TvDbId.Equals(tvRequest.Id.ToString(), StringComparison.InvariantCultureIgnoreCase)); // the Id on the child is the tvdbid at this point
if (anyTvDbMatches == null)
{
// So we do not have a TVDB Id, that really sucks.
@ -42,7 +42,7 @@ namespace Ombi.Core.Rule.Rules.Request
&& x.ReleaseYear == tvRequest.ReleaseYear.Year.ToString());
if (titleAndYearMatch != null)
{
// We have a match! Suprise Motherfucker
// We have a match! Surprise Motherfucker
return CheckExistingContent(tvRequest, titleAndYearMatch);
}

View file

@ -2,7 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository.Requests;
@ -13,6 +13,18 @@ namespace Ombi.Core.Rule.Rules.Search
{
public static void CheckForUnairedEpisodes(SearchTvShowViewModel search)
{
foreach (var season in search.SeasonRequests)
{
// If we have all the episodes for this season, then this season is available
if (season.Episodes.All(x => x.Available))
{
season.SeasonAvailable = true;
}
}
if(search.SeasonRequests.Any(x => x.Episodes.Any(e => e.Available)))
{
search.PartlyAvailable = true;
}
if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available)))
{
search.FullyAvailable = true;
@ -24,7 +36,7 @@ namespace Ombi.Core.Rule.Rules.Search
if (!airedButNotAvailable)
{
var unairedEpisodes = search.SeasonRequests.Any(x =>
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date));
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date || c.AirDate != DateTime.MinValue));
if (unairedEpisodes)
{
search.FullyAvailable = true;
@ -34,28 +46,36 @@ namespace Ombi.Core.Rule.Rules.Search
}
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<PlexEpisode> allEpisodes, EpisodeRequests episode,
SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb)
SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb, ILogger log)
{
PlexEpisode epExists = null;
if (useImdb)
try
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ImdbId == item.ImdbId.ToString());
}
if (useTheMovieDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TheMovieDbId == item.TheMovieDbId.ToString());
}
if (useImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ImdbId == item.ImdbId);
}
if (useTvDb)
if (useTheMovieDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TheMovieDbId == item.TheMovieDbId);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId);
}
}
catch (Exception e)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId.ToString());
log.LogError(e, "Exception thrown when attempting to check if something is available");
}
if (epExists != null)
@ -71,21 +91,21 @@ namespace Ombi.Core.Rule.Rules.Search
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ImdbId == item.ImdbId.ToString());
x.Series.ImdbId == item.ImdbId);
}
if (useTheMovieDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TheMovieDbId == item.TheMovieDbId.ToString());
x.Series.TheMovieDbId == item.TheMovieDbId);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId.ToString());
x.Series.TvDbId == item.TvDbId);
}
if (epExists != null)

View file

@ -3,6 +3,8 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
@ -11,12 +13,14 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class EmbyAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{
public EmbyAvailabilityRule(IEmbyContentRepository repo)
public EmbyAvailabilityRule(IEmbyContentRepository repo, ISettingsService<EmbySettings> s)
{
EmbyContentRepository = repo;
EmbySettings = s;
}
private IEmbyContentRepository EmbyContentRepository { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
public async Task<RuleResult> Execute(SearchViewModel obj)
{
@ -60,7 +64,19 @@ namespace Ombi.Core.Rule.Rules.Search
if (item != null)
{
obj.Available = true;
obj.EmbyUrl = item.Url;
var s = await EmbySettings.GetSettingsAsync();
if (s.Enable)
{
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
if ((server?.ServerHostname ?? string.Empty).HasValue())
{
obj.EmbyUrl = $"{server.ServerHostname}#!/itemdetails.html?id={item.EmbyId}";
}
else
{
obj.EmbyUrl = $"https://app.emby.media/#!/itemdetails.html?id={item.EmbyId}";
}
}
if (obj.Type == RequestType.TvShow)
{

View file

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
@ -29,7 +30,6 @@ namespace Ombi.Core.Rule.Rules.Search
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;
@ -41,22 +41,11 @@ namespace Ombi.Core.Rule.Rules.Search
}
if (obj.Type == RequestType.TvShow)
{
//var tvRequests = Tv.GetRequest(obj.Id);
//if (tvRequests != null) // Do we already have a request for this?
//{
// obj.Requested = true;
// obj.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
// obj.Available = tvRequests.ChildRequests.Any(x => x.Available);
// return Task.FromResult(Success());
//}
var request = (SearchTvShowViewModel)obj;
var tvRequests = Tv.GetRequest(obj.Id);
if (tvRequests != null) // Do we already have a request for this?
{
request.RequestId = tvRequests.Id;
request.Requested = true;
request.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
@ -87,11 +76,11 @@ namespace Ombi.Core.Rule.Rules.Search
}
}
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Available)))
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Available && e.AirDate > DateTime.MinValue)))
{
request.FullyAvailable = true;
}
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available)))
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available && e.AirDate > DateTime.MinValue)))
{
request.PartlyAvailable = true;
}

View file

@ -1,5 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
@ -10,12 +11,14 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class PlexAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{
public PlexAvailabilityRule(IPlexContentRepository repo)
public PlexAvailabilityRule(IPlexContentRepository repo, ILogger<PlexAvailabilityRule> log)
{
PlexContentRepository = repo;
Log = log;
}
private IPlexContentRepository PlexContentRepository { get; }
private ILogger Log { get; }
public async Task<RuleResult> Execute(SearchViewModel obj)
{
@ -72,7 +75,7 @@ namespace Ombi.Core.Rule.Rules.Search
{
foreach (var episode in season.Episodes)
{
await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb);
await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb, Log);
}
}

View file

@ -49,7 +49,6 @@ namespace Ombi.Core.Senders
{
try
{
var cpSettings = await CouchPotatoSettings.GetSettingsAsync();
//var watcherSettings = await WatcherSettings.GetSettingsAsync();
var radarrSettings = await RadarrSettings.GetSettingsAsync();
@ -76,7 +75,7 @@ namespace Ombi.Core.Senders
}
catch (Exception e)
{
Log.LogError(e, "Error when seing movie to DVR app, added to the request queue");
Log.LogError(e, "Error when sending movie to DVR app, added to the request queue");
// Check if already in request quee
var existingQueue = await _requestQueuRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);

View file

@ -16,6 +16,7 @@ using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Remotion.Linq.Parsing.Structure.IntermediateModel;
namespace Ombi.Core.Senders
{
@ -57,7 +58,7 @@ namespace Ombi.Core.Senders
var sonarr = await SonarrSettings.GetSettingsAsync();
if (sonarr.Enabled)
{
var result = await SendToSonarr(model);
var result = await SendToSonarr(model, sonarr);
if (result != null)
{
return new SenderResult
@ -109,7 +110,7 @@ namespace Ombi.Core.Senders
catch (Exception e)
{
Logger.LogError(e, "Exception thrown when sending a movie to DVR app, added to the request queue");
// Check if already in request quee
// Check if already in request queue
var existingQueue = await _requestQueueRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);
if (existingQueue != null)
{
@ -134,7 +135,7 @@ namespace Ombi.Core.Senders
return new SenderResult
{
Success = false,
Message = "Something wen't wrong!"
Message = "Something went wrong!"
};
}
@ -150,13 +151,8 @@ namespace Ombi.Core.Senders
/// <param name="s"></param>
/// <param name="model"></param>
/// <returns></returns>
public async Task<NewSeries> SendToSonarr(ChildRequests model)
public async Task<NewSeries> SendToSonarr(ChildRequests model, SonarrSettings s)
{
var s = await SonarrSettings.GetSettingsAsync();
if (!s.Enabled)
{
return null;
}
if (string.IsNullOrEmpty(s.ApiKey))
{
return null;
@ -319,10 +315,19 @@ namespace Ombi.Core.Senders
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber);
var sonarrEpCount = sonarrSeason.Count();
var sonarrEpisodeList = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber).ToList();
var sonarrEpCount = sonarrEpisodeList.Count;
var ourRequestCount = season.Episodes.Count;
var ourEpisodes = season.Episodes.Select(x => x.EpisodeNumber).ToList();
var unairedEpisodes = sonarrEpisodeList.Where(x => x.airDateUtc > DateTime.UtcNow).Select(x => x.episodeNumber).ToList();
//// Check if we have requested all the latest episodes, if we have then monitor
//// NOTE, not sure if needed since ombi ui displays future episodes anyway...
//ourEpisodes.AddRange(unairedEpisodes);
//var distinctEpisodes = ourEpisodes.Distinct().ToList();
//var missingEpisodes = Enumerable.Range(distinctEpisodes.Min(), distinctEpisodes.Count).Except(distinctEpisodes);
var existingSeason =
result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
if (existingSeason == null)
@ -332,7 +337,7 @@ namespace Ombi.Core.Senders
}
if (sonarrEpCount == ourRequestCount)
if (sonarrEpCount == ourRequestCount /*|| !missingEpisodes.Any()*/)
{
// We have the same amount of requests as all of the episodes in the season.

View file

@ -33,6 +33,7 @@ using Ombi.Api.CouchPotato;
using Ombi.Api.DogNzb;
using Ombi.Api.FanartTv;
using Ombi.Api.Github;
using Ombi.Api.Gotify;
using Ombi.Api.Lidarr;
using Ombi.Api.Mattermost;
using Ombi.Api.Notifications;
@ -51,16 +52,15 @@ using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Sonarr;
using Ombi.Store.Repository.Requests;
using Ombi.Updater;
using PlexContentCacher = Ombi.Schedule.Jobs.Plex;
using Ombi.Api.Telegram;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Demo;
using Ombi.Core.Engine.V2;
using Ombi.Core.Processor;
using Ombi.Schedule.Jobs.Lidarr;
using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Schedule.Jobs.SickRage;
using Ombi.Schedule.Processor;
using Ombi.Store.Entities;
namespace Ombi.DependencyInjection
{
@ -95,12 +95,16 @@ namespace Ombi.DependencyInjection
services.AddTransient<IMassEmailSender, MassEmailSender>();
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
services.AddTransient<IVoteEngine, VoteEngine>();
services.AddTransient<IDemoMovieSearchEngine, DemoMovieSearchEngine>();
services.AddTransient<IDemoTvSearchEngine, DemoTvSearchEngine>();
}
public static void RegisterEnginesV2(this IServiceCollection services)
{
services.AddTransient<IMultiSearchEngine, MultiSearchEngine>();
services.AddTransient<IMovieEngineV2, MovieSearchEngineV2>();
services.AddTransient<ITVSearchEngineV2, TvSearchEngineV2>();
services.AddTransient<ICalendarEngine, CalendarEngine>();
}
public static void RegisterHttp(this IServiceCollection services)
@ -127,6 +131,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IOmbiService, OmbiService>();
services.AddTransient<IFanartTvApi, FanartTvApi>();
services.AddTransient<IPushoverApi, PushoverApi>();
services.AddTransient<IGotifyApi, GotifyApi>();
services.AddTransient<IMattermostApi, MattermostApi>();
services.AddTransient<ICouchPotatoApi, CouchPotatoApi>();
services.AddTransient<IDogNzbApi, DogNzbApi>();
@ -139,28 +144,28 @@ namespace Ombi.DependencyInjection
}
public static void RegisterStore(this IServiceCollection services) {
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>();
services.AddEntityFrameworkSqlite().AddDbContext<SettingsContext>();
services.AddEntityFrameworkSqlite().AddDbContext<ExternalContext>();
services.AddDbContext<OmbiContext>();
services.AddDbContext<SettingsContext>();
services.AddDbContext<ExternalContext>();
services.AddScoped<IOmbiContext, OmbiContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddScoped<ISettingsContext, SettingsContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddScoped<IExternalContext, ExternalContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
services.AddTransient<ISettingsResolver, SettingsResolver>();
services.AddTransient<IPlexContentRepository, PlexServerContentRepository>();
services.AddTransient<IEmbyContentRepository, EmbyContentRepository>();
services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddScoped<ISettingsRepository, SettingsJsonRepository>();
services.AddScoped<ISettingsResolver, SettingsResolver>();
services.AddScoped<IPlexContentRepository, PlexServerContentRepository>();
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddTransient<ITvRequestRepository, TvRequestRepository>();
services.AddTransient<IMovieRequestRepository, MovieRequestRepository>();
services.AddTransient<IMusicRequestRepository, MusicRequestRepository>();
services.AddTransient<IAuditRepository, AuditRepository>();
services.AddTransient<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddTransient<ITokenRepository, TokenRepository>();
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>));
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
services.AddTransient(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
services.AddScoped<ITvRequestRepository, TvRequestRepository>();
services.AddScoped<IMovieRequestRepository, MovieRequestRepository>();
services.AddScoped<IMusicRequestRepository, MusicRequestRepository>();
services.AddScoped<IAuditRepository, AuditRepository>();
services.AddScoped<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddScoped<ITokenRepository, TokenRepository>();
services.AddScoped(typeof(ISettingsService<>), typeof(SettingsService<>));
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
}
public static void RegisterServices(this IServiceCollection services)
{
@ -168,7 +173,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<INotificationService, NotificationService>();
services.AddTransient<IEmailProvider, GenericEmailProvider>();
services.AddTransient<INotificationHelper, NotificationHelper>();
services.AddTransient<ICacheService, CacheService>();
services.AddSingleton<ICacheService, CacheService>();
services.AddTransient<IDiscordNotification, DiscordNotification>();
services.AddTransient<IEmailNotification, EmailNotification>();
@ -177,6 +182,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ISlackNotification, SlackNotification>();
services.AddTransient<IMattermostNotification, MattermostNotification>();
services.AddTransient<IPushoverNotification, PushoverNotification>();
services.AddTransient<IGotifyNotification, GotifyNotification>();
services.AddTransient<ITelegramNotification, TelegramNotification>();
services.AddTransient<IMobileNotification, MobileNotification>();
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();

View file

@ -0,0 +1,28 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Helpers.Tests
{
[TestFixture]
public class EmbyHelperTests
{
[TestCaseSource(nameof(UrlData))]
public string TestUrl(string mediaId, string url)
{
return EmbyHelper.GetEmbyMediaUrl(mediaId, url);
}
public static IEnumerable<TestCaseData> UrlData
{
get
{
var mediaId = 1;
yield return new TestCaseData(mediaId.ToString(), "http://google.com").Returns($"http://google.com/#!/itemdetails.html?id={mediaId}").SetName("EmbyHelper_GetMediaUrl_WithCustomDomain_WithoutTrailingSlash");
yield return new TestCaseData(mediaId.ToString(), "http://google.com/").Returns($"http://google.com/#!/itemdetails.html?id={mediaId}").SetName("EmbyHelper_GetMediaUrl_WithCustomDomain");
yield return new TestCaseData(mediaId.ToString(), "https://google.com/").Returns($"https://google.com/#!/itemdetails.html?id={mediaId}").SetName("EmbyHelper_GetMediaUrl_WithCustomDomain_Https");
}
}
}
}

View file

@ -0,0 +1,41 @@
using NUnit.Framework;
using System.Collections.Generic;
namespace Ombi.Helpers.Tests
{
[TestFixture]
public class HtmlHelperTests
{
[TestCaseSource(nameof(HtmlData))]
public string RemoveHtmlTests(string input)
{
return HtmlHelper.RemoveHtml(input);
}
public static IEnumerable<TestCaseData> HtmlData
{
get
{
yield return new TestCaseData("<h1>hi</h1>").Returns("hi").SetName("Simple Html");
yield return new TestCaseData("<html><body><head></head><h1>hi</h1></body></html>").Returns("hi").SetName("Nested text inside Html");
yield return new TestCaseData("there is no html here").Returns("there is no html here").SetName("No Html");
yield return new TestCaseData("there is <b>some</b> html here").Returns("there is some html here").SetName("Html in middle");
yield return new TestCaseData("<a>there</a> <u>is</u> <b>lots</b> <i>html</i> <span>here</span>").Returns("there is lots html here").SetName("Html in everywhere");
yield return new TestCaseData("there is <span class=\"abc\">some</span> html here").Returns("there is some html here").SetName("Html in with classes");
yield return new TestCaseData("there is <span id=\"sometag\">some</span> html here").Returns("there is some html here").SetName("Html in with attribute");
yield return new TestCaseData("there is <span data-tag=\"sometag\" class=\"abc\">some</span> html here").Returns("there is some html here").SetName("Html in with attribute and class");
}
}
public static IEnumerable<TestCaseData> OtherData
{
get
{
foreach (var data in HtmlData)
{
yield return data;
}
yield return new TestCaseData("xyz").Returns("xyz").SetName("More Tests");
}
}
}
}

View file

@ -8,8 +8,8 @@
<ItemGroup>
<PackageReference Include="nunit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,120 @@
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Ombi.Helpers.Tests
{
[TestFixture]
public class PaginationHelperTests
{
[TestCaseSource(nameof(TestPageData))]
public void TestPaginationPages(int currentlyLoaded, int toLoad, int maxItemsPerPage, int[] expectedPages)
{
var result = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, maxItemsPerPage);
var pages = result.Select(x => x.Page).ToArray();
Assert.That(pages.Length, Is.EqualTo(expectedPages.Length), "Did not contain the correct amount of pages");
for (var i = 0; i < pages.Length; i++)
{
Assert.That(pages[i], Is.EqualTo(expectedPages[i]));
}
}
public static IEnumerable<TestCaseData> TestPageData
{
get
{
yield return new TestCaseData(0, 10, 20, new [] { 1 }).SetName("Pagination_Load_First_Page");
yield return new TestCaseData(20, 10, 20, new [] { 2 }).SetName("Pagination_Load_Second_Page");
yield return new TestCaseData(0, 20, 20, new [] { 1 }).SetName("Pagination_Load_Full_First_Page");
yield return new TestCaseData(20, 20, 20, new [] { 2 }).SetName("Pagination_Load_Full_Second_Page");
yield return new TestCaseData(10, 20, 20, new [] { 1, 2 }).SetName("Pagination_Load_Half_First_Page_And_Half_Second_Page");
yield return new TestCaseData(19, 20, 20, new[] { 1, 2 }).SetName("Pagination_Load_End_First_Page_And_Most_Second_Page");
yield return new TestCaseData(19, 40, 20, new[] { 1, 2, 3 }).SetName("Pagination_Load_End_First_Page_And_Most_Second_And_Third_Page");
yield return new TestCaseData(10, 10, 20, new[] { 1 }).SetName("Pagination_Load_Half_First_Page");
yield return new TestCaseData(10, 9, 20, new[] { 1 }).SetName("Pagination_Load_LessThan_Half_First_Page");
yield return new TestCaseData(20, 10, 20, new[] { 2 }).SetName("Pagination_Load_Half_Second_Page");
yield return new TestCaseData(20, 9, 20, new[] { 2 }).SetName("Pagination_Load_LessThan_Half_Second_Page");
yield return new TestCaseData(30, 10, 20, new[] { 2 }).SetName("Pagination_Load_All_Second_Page_With_Half_Take");
yield return new TestCaseData(49, 1, 50, new[] { 1 }).SetName("Pagination_Load_49_OutOf_50");
yield return new TestCaseData(49, 1, 100,new[] { 1 }).SetName("Pagination_Load_50_OutOf_100");
}
}
[TestCaseSource(nameof(CurrentPositionTestData))]
public void TestCurrentPositionOfPagination(int currentlyLoaded, int toLoad, int maxItemsPerPage, int expectedTake, int expectedSkip)
{
var result = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, maxItemsPerPage);
var first = result.FirstOrDefault();
Assert.That(first.Take, Is.EqualTo(expectedTake));
Assert.That(first.Skip, Is.EqualTo(expectedSkip));
}
public static IEnumerable<TestCaseData> CurrentPositionTestData
{
get
{
yield return new TestCaseData(0, 10, 20, 10, 0).SetName("PaginationPosition_Load_First_Half_Of_Page");
yield return new TestCaseData(10, 10, 20, 10, 10).SetName("PaginationPosition_Load_EndHalf_First_Page");
yield return new TestCaseData(19, 1, 20, 1, 19).SetName("PaginationPosition_Load_LastItem_Of_First_Page");
yield return new TestCaseData(20, 20, 300, 20, 20).SetName("PaginationPosition_Load_Full_Second_Page");
}
}
[TestCaseSource(nameof(CurrentPositionMultiplePagesTestData))]
public void TestCurrentPositionOfPaginationWithMultiplePages(int currentlyLoaded, int toLoad, int maxItemsPerPage, List<MultiplePagesTestData> data)
{
var result = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, maxItemsPerPage);
foreach (var r in result)
{
// get result data for this page
var expectedPage = data.FirstOrDefault(x => x.Page == r.Page);
Assert.That(r.Take, Is.EqualTo(expectedPage.ExpectedTake));
Assert.That(r.Skip, Is.EqualTo(expectedPage.ExpectedSkip));
}
}
public static IEnumerable<TestCaseData> CurrentPositionMultiplePagesTestData
{
get
{
yield return new TestCaseData(10, 20, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(1, 10, 10), new MultiplePagesTestData(2, 10, 0) })
.SetName("PaginationPosition_Load_SecondHalf_FirstPage_FirstHalf_SecondPage");
yield return new TestCaseData(0, 40, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(1, 20, 0), new MultiplePagesTestData(2, 20, 0) })
.SetName("PaginationPosition_Load_Full_First_And_SecondPage");
yield return new TestCaseData(35, 15, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(2, 5, 15), new MultiplePagesTestData(3, 10, 0) })
.SetName("PaginationPosition_Load_EndSecondPage_Beginning_ThirdPage");
yield return new TestCaseData(18, 22, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(1, 2, 18), new MultiplePagesTestData(2, 20, 0) })
.SetName("PaginationPosition_Load_EndFirstPage_Full_SecondPage");
yield return new TestCaseData(38, 4, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(2, 2, 18), new MultiplePagesTestData(3, 2, 0) })
.SetName("PaginationPosition_Load_EndSecondPage_Some_ThirdPage");
yield return new TestCaseData(15, 20, 10, new List<MultiplePagesTestData> { new MultiplePagesTestData(2, 5, 5), new MultiplePagesTestData(3, 10, 0), new MultiplePagesTestData(4, 5, 0) })
.SetName("PaginationPosition_Load_EndSecondPage_All_ThirdPage_Some_ForthPage");
yield return new TestCaseData(24, 12, 12, new List<MultiplePagesTestData> { new MultiplePagesTestData(3, 12, 0) })
.SetName("PaginationPosition_Load_ThirdPage_Of_12");
yield return new TestCaseData(12, 12, 12, new List<MultiplePagesTestData> { new MultiplePagesTestData(2, 12, 0) })
.SetName("PaginationPosition_Load_SecondPage_Of_12");
yield return new TestCaseData(40, 20, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(3, 20, 0) })
.SetName("PaginationPosition_Load_FullThird_Page");
yield return new TestCaseData(240, 12, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(13, 12, 0) })
.SetName("PaginationPosition_Load_Page_13");
}
}
public class MultiplePagesTestData
{
public MultiplePagesTestData(int page, int take, int skip)
{
Page = page;
ExpectedTake = take;
ExpectedSkip = skip;
}
public int Page { get; set; }
public int ExpectedTake { get; set; }
public int ExpectedSkip { get; set; }
}
}
}

View file

@ -0,0 +1,115 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Ombi.Helpers.Tests
{
[TestFixture]
public class UriHelperTests
{
[TestCaseSource(nameof(UrlData))]
public string ReturnUri(string uri)
{
return UriHelper.ReturnUri(uri).ToString();
}
public static IEnumerable<TestCaseData> UrlData
{
get
{
yield return new TestCaseData("https://google.com/").Returns($"https://google.com/").SetName("ReturnUri_With_HttpScheme");
yield return new TestCaseData("google.com/").Returns($"http://google.com/").SetName("ReturnUri_HttpScheme_Not_Provided");
yield return new TestCaseData("http://google.com:9090/").Returns($"http://google.com:9090/").SetName("ReturnUri_WithPort");
yield return new TestCaseData("https://google.com/").Returns($"https://google.com/").SetName("ReturnUri_With_HttpsScheme");
yield return new TestCaseData("https://hi.google.com/").Returns($"https://hi.google.com/").SetName("ReturnUri_With_SubDomain");
yield return new TestCaseData("https://google.com/hi").Returns($"https://google.com/hi").SetName("ReturnUri_With_Path");
yield return new TestCaseData("https://hi.google.com/hi").Returns($"https://hi.google.com/hi").SetName("ReturnUri_With_Path_And_SubDomain");
}
}
[TestCaseSource(nameof(UrlWithPortData))]
public string ReturnUriWithPort(string uri, int port)
{
return UriHelper.ReturnUri(uri, port).ToString();
}
public static IEnumerable<TestCaseData> UrlWithPortData
{
get
{
yield return new TestCaseData("https://google.com", 443).Returns($"https://google.com/").SetName("ReturnUri_With_HttpsPort");
yield return new TestCaseData("https://google.com/", 123).Returns($"https://google.com:123/").SetName("ReturnUri_With_HttpScheme_With_Port");
yield return new TestCaseData("google.com/", 80).Returns($"http://google.com/").SetName("ReturnUri_HttpScheme_Not_Provided_With_Port");
yield return new TestCaseData("https://google.com/", 7000).Returns($"https://google.com:7000/").SetName("ReturnUri_With_HttpsScheme_With_Port");
yield return new TestCaseData("https://hi.google.com/", 1).Returns($"https://hi.google.com:1/").SetName("ReturnUri_With_SubDomain_With_Port");
yield return new TestCaseData("https://google.com/hi", 443).Returns($"https://google.com/hi").SetName("ReturnUri_With_Path_With_Port");
yield return new TestCaseData("https://hi.google.com/hi", 443).Returns($"https://hi.google.com/hi").SetName("ReturnUri_With_Path_And_SubDomain_With_Port");
}
}
[TestCaseSource(nameof(UrlWithPortWithSSLData))]
public string ReturnUriWithPortAndSSL(string uri, int port, bool ssl)
{
return UriHelper.ReturnUri(uri, port, ssl).ToString();
}
public static IEnumerable<TestCaseData> UrlWithPortWithSSLData
{
get
{
foreach (var d in UrlWithPortData)
{
var expected = (string)d.ExpectedResult;
var args = d.Arguments.ToList();
args.Add(true);
var newExpected = expected.Replace("http://", "https://");
if (args.Contains(80))
{
newExpected = expected;
}
d.TestName += "_With_SSL";
yield return new TestCaseData(args.ToArray()).Returns(newExpected).SetName(d.TestName);
}
}
}
[TestCaseSource(nameof(UrlWithPortWithSSLDataCasing))]
public string ReturnUriWithPortAndSSLCasing(string uri, int port, bool ssl)
{
return UriHelper.ReturnUri(uri, port, ssl).ToString();
}
public static IEnumerable<TestCaseData> UrlWithPortWithSSLDataCasing
{
get
{
foreach (var d in UrlWithPortData)
{
if (d.TestName.Contains("_Path_"))
{
continue;
}
var expected = (string)d.ExpectedResult;
var args = d.Arguments.ToList();
for (int i = 0; i < args.Count; i++)
{
if(args[i] is string)
{
args[i] = ((string)args[i]).ToUpper();
}
}
args.Add(true);
var newExpected = expected.Replace("http://", "https://");
if (args.Contains(80))
{
newExpected = expected;
}
d.TestName += "_With_SSL_ToUpper";
yield return new TestCaseData(args.ToArray()).Returns(newExpected).SetName(d.TestName);
}
}
}
}
}

View file

@ -28,7 +28,7 @@ namespace Ombi.Helpers
return result;
}
using (await _mutex.LockAsync())
//using (await _mutex.LockAsync())
{
if (_memoryCache.TryGetValue(cacheKey, out result))
{

View file

@ -1,46 +0,0 @@
using System;
using System.Security.Claims;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Ombi.Helpers
{
public class ClaimConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(System.Security.Claims.Claim));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var claim = (System.Security.Claims.Claim)value;
JObject jo = new JObject();
jo.Add("Type", claim.Type);
jo.Add("Value", IsJson(claim.Value) ? new JRaw(claim.Value) : new JValue(claim.Value));
jo.Add("ValueType", claim.ValueType);
jo.Add("Issuer", claim.Issuer);
jo.Add("OriginalIssuer", claim.OriginalIssuer);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string type = (string)jo["Type"];
JToken token = jo["Value"];
string value = token.Type == JTokenType.String ? (string)token : token.ToString(Formatting.None);
string valueType = (string)jo["ValueType"];
string issuer = (string)jo["Issuer"];
string originalIssuer = (string)jo["OriginalIssuer"];
return new Claim(type, value, valueType, issuer, originalIssuer);
}
private bool IsJson(string val)
{
return (val != null &&
(val.StartsWith("[") && val.EndsWith("]")) ||
(val.StartsWith("{") && val.EndsWith("}")));
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ombi.Config
{
public class DemoLists
{
public bool Enabled { get; set; }
public int[] Movies { get; set; }
public int[] TvShows { get; set; }
}
}

View file

@ -0,0 +1,13 @@
namespace Ombi.Helpers
{
public class DemoSingleton
{
private static DemoSingleton instance;
private DemoSingleton() { }
public static DemoSingleton Instance => instance ?? (instance = new DemoSingleton());
public bool Demo { get; set; }
}
}

View file

@ -1,9 +1,4 @@
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Helpers
namespace Ombi.Helpers
{
public class EmbyHelper
{
@ -11,6 +6,10 @@ namespace Ombi.Helpers
{
if (customerServerUrl.HasValue())
{
if(!customerServerUrl.EndsWith("/"))
{
return $"{customerServerUrl}/#!/itemdetails.html?id={mediaId}";
}
return $"{customerServerUrl}#!/itemdetails.html?id={mediaId}";
}
else

View file

@ -14,6 +14,5 @@ namespace Ombi.Helpers
var step2 = Regex.Replace(step1, @"\s{2,}", " ");
return step2;
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ombi.Helpers
{
@ -21,5 +22,31 @@ namespace Ombi.Helpers
{
return new HashSet<T>(source, comparer);
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
{
return source.Shuffle(new Random());
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (rng == null) throw new ArgumentNullException(nameof(rng));
return source.ShuffleIterator(rng);
}
private static IEnumerable<T> ShuffleIterator<T>(
this IEnumerable<T> source, Random rng)
{
var buffer = source.ToList();
for (int i = 0; i < buffer.Count; i++)
{
int j = rng.Next(i, buffer.Count);
yield return buffer[j];
buffer[j] = buffer[i];
}
}
}
}

View file

@ -32,6 +32,7 @@ namespace Ombi.Helpers
public static EventId MattermostNotification => new EventId(4004);
public static EventId PushoverNotification => new EventId(4005);
public static EventId TelegramNotifcation => new EventId(4006);
public static EventId GotifyNotification => new EventId(4007);
public static EventId TvSender => new EventId(5000);
public static EventId SonarrSender => new EventId(5001);

View file

@ -10,5 +10,6 @@
Slack = 5,
Mattermost = 6,
Mobile = 7,
Gotify = 8,
}
}

View file

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

View file

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Helpers
{
public static class PaginationHelper
{
public static List<PagesToLoad> GetNextPages(int currentlyLoaded, int toTake, int maxItemsPerPage)
{
var result = new List<PagesToLoad>();
var firstPage = currentlyLoaded / maxItemsPerPage + 1;
var startPos = currentlyLoaded % maxItemsPerPage + 1;
var lastItemIndex = currentlyLoaded + toTake - 1;
var lastPage = lastItemIndex / maxItemsPerPage + 1;
var stopPos = lastItemIndex % maxItemsPerPage + 1;
while (currentlyLoaded > maxItemsPerPage)
{
currentlyLoaded -= maxItemsPerPage;
}
if ((currentlyLoaded % maxItemsPerPage) == 0 && (currentlyLoaded % toTake) == 0)
{
currentlyLoaded = 0;
}
var page1 = new PagesToLoad { Page = firstPage, Skip = currentlyLoaded, Take = toTake };
if (toTake + startPos - 1 > maxItemsPerPage)
{
page1.Take = maxItemsPerPage - startPos + 1;
result.Add(page1);
for (var i = firstPage + 1; i < lastPage; i++)
{
var nextPage = new PagesToLoad { Page = i, Skip = 0, Take = maxItemsPerPage };
result.Add(nextPage);
}
var pageN = new PagesToLoad { Page = lastPage, Skip = 0, Take = stopPos };
result.Add(pageN);
}
else
{
if (page1.Skip + page1.Take > maxItemsPerPage)
{
page1.Skip = 0;
}
result.Add(page1);
}
return result;
}
}
public class PagesToLoad
{
public int Page { get; set; }
public int Take { get; set; }
public int Skip { get; set; }
}
}

View file

@ -6,46 +6,44 @@ namespace Ombi.Helpers
{
private const string Https = "Https";
private const string Http = "Http";
public static Uri ReturnUri(this string val)
{
if (val == null)
{
throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly.");
}
try
var uri = new UriBuilder();
if (val.StartsWith("http://", StringComparison.Ordinal))
{
var uri = new UriBuilder();
if (val.StartsWith("http://", StringComparison.Ordinal))
{
uri = new UriBuilder(val);
}
else if (val.StartsWith("https://", StringComparison.Ordinal))
{
uri = new UriBuilder(val);
}
else if (val.Contains(":"))
{
var split = val.Split(':', '/');
int port;
int.TryParse(split[1], out port);
uri = split.Length == 3
? new UriBuilder(Http, split[0], port, "/" + split[2])
: new UriBuilder(Http, split[0], port);
}
else
{
uri = new UriBuilder(Http, val);
}
return uri.Uri;
uri = new UriBuilder(val);
}
catch (Exception exception)
else if (val.StartsWith("https://", StringComparison.Ordinal))
{
throw new Exception(exception.Message, exception);
uri = new UriBuilder(val);
}
else if (val.Contains(":"))
{
var split = val.Split(':', '/');
int port;
int.TryParse(split[1], out port);
uri = split.Length == 3
? new UriBuilder(Http, split[0], port, "/" + split[2])
: new UriBuilder(Http, split[0], port);
}
else
{
if(val.EndsWith("/"))
{
// Remove a trailing slash, since the URIBuilder adds one
val = val.Remove(val.Length - 1, 1);
}
uri = new UriBuilder(Http, val);
}
return uri.Uri;
}
/// <summary>
@ -64,37 +62,40 @@ namespace Ombi.Helpers
{
throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly.");
}
try
{
var uri = new UriBuilder();
var uri = new UriBuilder();
if (val.StartsWith("http://", StringComparison.Ordinal))
{
var split = val.Split('/');
uri = split.Length >= 4 ? new UriBuilder(Http, split[2], port, "/" + split[3]) : new UriBuilder(new Uri($"{val}:{port}"));
}
else if (val.StartsWith("https://", StringComparison.Ordinal))
{
var split = val.Split('/');
uri = split.Length >= 4
? new UriBuilder(Https, split[2], port, "/" + split[3])
: new UriBuilder(Https, split[2], port);
}
else if (ssl)
{
uri = new UriBuilder(Https, val, port);
}
else
{
uri = new UriBuilder(Http, val, port);
}
return uri.Uri;
}
catch (Exception exception)
if (val.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
{
throw new Exception(exception.Message, exception);
var split = val.Split('/');
uri = split.Length >= 4 ? new UriBuilder(Http, split[2], port, "/" + split[3]) : new UriBuilder(new Uri($"{val}:{port}"));
}
else if (val.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
{
var split = val.Split('/');
uri = split.Length >= 4
? new UriBuilder(Https, split[2], port, "/" + split[3])
: new UriBuilder(Https, split[2], port);
}
else if ((ssl || port == 443) && port != 80)
{
if (val.EndsWith("/"))
{
// Remove a trailing slash, since the URIBuilder adds one
val = val.Remove(val.Length - 1, 1);
}
uri = new UriBuilder(Https, val, port);
}
else
{
if (val.EndsWith("/"))
{
// Remove a trailing slash, since the URIBuilder adds one
val = val.Remove(val.Length - 1, 1);
}
uri = new UriBuilder(Http, val, port);
}
return uri.Uri;
}
public static Uri ReturnUriWithSubDir(this string val, int port, bool ssl, string subDir)
@ -112,7 +113,7 @@ namespace Ombi.Helpers
return uriBuilder.Uri;
}
}
public class ApplicationSettingsException : Exception

View file

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AutoMapper;
using AutoMapper.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -12,9 +11,9 @@ namespace Ombi.Mapping
{
public static IServiceCollection AddOmbiMappingProfile(this IServiceCollection services)
{
System.Reflection.Assembly ass = typeof(AutoMapperProfile).GetTypeInfo().Assembly;
Assembly ass = typeof(AutoMapperProfile).GetTypeInfo().Assembly;
var assemblies = new List<Type>();
foreach (System.Reflection.TypeInfo ti in ass.DefinedTypes)
foreach (TypeInfo ti in ass.DefinedTypes)
{
if (ti.ImplementedInterfaces.Contains(typeof(IProfileConfiguration)))
{

View file

@ -4,6 +4,8 @@ using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
using Ombi.TheMovieDbApi.Models;
using Keywords = Ombi.Core.Models.Search.V2.Keywords;
using KeywordsValue = Ombi.Api.TheMovieDb.Models.KeywordsValue;
namespace Ombi.Mapping.Profiles
{
@ -86,6 +88,22 @@ namespace Ombi.Mapping.Profiles
CreateMap<Ombi.Api.TheMovieDb.Models.FullMovieCast, Ombi.Core.Models.Search.V2.FullMovieCastViewModel>().ReverseMap();
CreateMap<Ombi.Api.TheMovieDb.Models.FullMovieCrew, Ombi.Core.Models.Search.V2.FullMovieCrewViewModel>().ReverseMap();
CreateMap<Ombi.Api.TheMovieDb.Models.ExternalIds, Ombi.Core.Models.Search.V2.ExternalIds>().ReverseMap();
CreateMap<BelongsToCollection, Ombi.Core.Models.Search.V2.CollectionsViewModel>().ReverseMap();
CreateMap<Api.TheMovieDb.Models.Keywords, Ombi.Core.Models.Search.V2.Keywords>().ReverseMap();
CreateMap<KeywordsValue, Ombi.Core.Models.Search.V2.KeywordsValue>().ReverseMap();
CreateMap<Collections, Ombi.Core.Models.Search.V2.MovieCollectionsViewModel>()
.ForMember(x => x.Name, o => o.MapFrom(s => s.name))
.ForMember(x => x.Overview, o => o.MapFrom(s => s.overview))
.ForMember(x => x.Collection, o => o.MapFrom(s => s.parts));
CreateMap<Part, MovieCollection>()
.ForMember(x => x.Id, o => o.MapFrom(s => s.id))
.ForMember(x => x.Overview, o => o.MapFrom(s => s.overview))
.ForMember(x => x.PosterPath, o => o.MapFrom(s => s.poster_path))
.ForMember(x => x.Title, o => o.MapFrom(s => s.title));
CreateMap<SearchMovieViewModel, MovieCollection>().ReverseMap();
}
}
}

View file

@ -19,6 +19,7 @@ namespace Ombi.Mapping.Profiles
CreateMap<UpdateSettingsViewModel, UpdateSettings>().ReverseMap();
CreateMap<MobileNotificationsViewModel, MobileNotificationSettings>().ReverseMap();
CreateMap<NewsletterNotificationViewModel, NewsletterSettings>().ReverseMap();
CreateMap<GotifyNotificationViewModel, GotifySettings>().ReverseMap();
}
}
}

View file

@ -0,0 +1,82 @@
using System.Globalization;
using AutoMapper;
using Ombi.Api.TvMaze.Models.V2;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
using Ombi.Helpers;
namespace Ombi.Mapping.Profiles
{
public class TvProfileV2 : Profile
{
public TvProfileV2()
{
CreateMap<FullSearch, SearchFullInfoTvShowViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.externals.thetvdb))
.ForMember(dest => dest.FirstAired, opts => opts.MapFrom(src => src.premiered))
.ForMember(dest => dest.ImdbId, opts => opts.MapFrom(src => src.externals.imdb))
.ForMember(dest => dest.Network, opts => opts.MapFrom(src => src.network.name))
.ForMember(dest => dest.NetworkId, opts => opts.MapFrom(src => src.network.id.ToString()))
.ForMember(dest => dest.Overview, opts => opts.MapFrom(src => src.summary.RemoveHtml()))
.ForMember(dest => dest.Rating,
opts => opts.MapFrom(src => src.rating.average.ToString(CultureInfo.CurrentUICulture)))
.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.runtime.ToString()))
.ForMember(dest => dest.SeriesId, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Network, opts => opts.MapFrom(src => src.network))
.ForMember(dest => dest.Images, opts => opts.MapFrom(src => src.image))
.ForMember(dest => dest.Cast, opts => opts.MapFrom(src => src._embedded.cast))
.ForMember(dest => dest.Crew, opts => opts.MapFrom(src => src._embedded.crew))
.ForMember(dest => dest.Banner,
opts => opts.MapFrom(src =>
!string.IsNullOrEmpty(src.image.medium)
? src.image.medium.Replace("http", "https")
: string.Empty))
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status));
CreateMap<Network, NetworkViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Country, opts => opts.MapFrom(src => src.country))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name));
CreateMap<Api.TvMaze.Models.V2.Country, Core.Models.Search.V2.Country>()
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Code, opts => opts.MapFrom(src => src.code))
.ForMember(dest => dest.Timezone, opts => opts.MapFrom(src => src.timezone));
CreateMap<Api.TvMaze.Models.V2.Image, Images>()
.ForMember(dest => dest.Medium, opts => opts.MapFrom(src => src.medium))
.ForMember(dest => dest.Original, opts => opts.MapFrom(src => src.original));
CreateMap<Api.TvMaze.Models.V2.Cast, CastViewModel>()
.ForMember(dest => dest.Character, opts => opts.MapFrom(src => src.character))
.ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.person))
.ForMember(dest => dest.Voice, opts => opts.MapFrom(src => src.voice))
.ForMember(dest => dest.Self, opts => opts.MapFrom(src => src.self));
CreateMap<Api.TvMaze.Models.V2.Person, PersonViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Image, opts => opts.MapFrom(src => src.image))
.ForMember(dest => dest.Url, opts => opts.MapFrom(src => src.url));
CreateMap<Api.TvMaze.Models.V2.Crew, CrewViewModel>()
.ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.person))
.ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.type));
CreateMap<Api.TvMaze.Models.V2.Cast, CastViewModel>()
.ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.person))
.ForMember(dest => dest.Self, opts => opts.MapFrom(src => src.self))
.ForMember(dest => dest.Voice, opts => opts.MapFrom(src => src.voice))
.ForMember(dest => dest.Character, opts => opts.MapFrom(src => src.character));
CreateMap<Api.TvMaze.Models.V2.Character, CharacterViewModel>()
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Url, opts => opts.MapFrom(src => src.url))
.ForMember(dest => dest.Image, opts => opts.MapFrom(src => src.image));
CreateMap<SearchTvShowViewModel, SearchFullInfoTvShowViewModel>().ReverseMap();
}
}
}

View file

@ -13,7 +13,7 @@ namespace Ombi.Notifications.Templates
if (string.IsNullOrEmpty(_templateLocation))
{
#if DEBUG
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates",
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.2", "Templates",
"BasicTemplate.html");
#else
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html");

View file

@ -5,10 +5,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="Nunit" Version="3.11.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.0.1"></packagereference>
<PackageReference Include="Moq" Version="4.10.0" />
</ItemGroup>

View file

@ -0,0 +1,116 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Gotify;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
namespace Ombi.Notifications.Agents
{
public class GotifyNotification : BaseNotification<GotifySettings>, IGotifyNotification
{
public GotifyNotification(IGotifyApi api, ISettingsService<GotifySettings> sn, ILogger<GotifyNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
{
Api = api;
Logger = log;
}
public override string NotificationName => "GotifyNotification";
private IGotifyApi Api { get; }
private ILogger<GotifyNotification> Logger { get; }
protected override bool ValidateConfiguration(GotifySettings settings)
{
return settings.Enabled && !string.IsNullOrEmpty(settings.BaseUrl) && !string.IsNullOrEmpty(settings.ApplicationToken);
}
protected override async Task NewRequest(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.NewRequest);
}
protected override async Task NewIssue(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.Issue);
}
protected override async Task IssueComment(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.IssueComment);
}
protected override async Task IssueResolved(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.IssueResolved);
}
protected override async Task AddedToRequestQueue(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
}
protected override async Task RequestDeclined(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestDeclined);
}
protected override async Task RequestApproved(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestApproved);
}
protected override async Task AvailableRequest(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestAvailable);
}
protected override async Task Send(NotificationMessage model, GotifySettings settings)
{
try
{
await Api.PushAsync(settings.BaseUrl, settings.ApplicationToken, model.Subject, model.Message, settings.Priority);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.GotifyNotification, e, "Failed to send Gotify notification");
}
}
protected override async Task Test(NotificationOptions model, GotifySettings settings)
{
var message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
private async Task Run(NotificationOptions model, GotifySettings settings, NotificationType type)
{
var parsed = await LoadTemplate(NotificationAgent.Gotify, type, model);
if (parsed.Disabled)
{
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Gotify}");
return;
}
var notification = new NotificationMessage
{
Message = parsed.Message,
};
await Send(notification, settings);
}
}
}

View file

@ -0,0 +1,6 @@
namespace Ombi.Notifications.Agents
{
public interface IGotifyNotification : INotification
{
}
}

View file

@ -52,7 +52,7 @@ namespace Ombi.Notifications.Agents
private void AddOtherInformation(NotificationOptions model, NotificationMessage notification,
NotificationMessageContent parsed)
{
notification.Other.Add("image", parsed.Image);
notification.Other.Add("image", parsed?.Image ?? string.Empty);
notification.Other.Add("title", model.RequestType == RequestType.Movie ? MovieRequest.Title : TvRequest.Title);
}

View file

@ -26,8 +26,6 @@ namespace Ombi.Notifications
MovieRepository = movie;
TvRepository = tv;
CustomizationSettings = customization;
Settings.ClearCache();
CustomizationSettings.ClearCache();
RequestSubscription = sub;
_log = log;
AlbumRepository = album;
@ -55,14 +53,12 @@ namespace Ombi.Notifications
public async Task NotifyAsync(NotificationOptions model)
{
Settings.ClearCache();
var configuration = await GetConfiguration();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationOptions model, Settings.Settings.Models.Settings settings)
{
Settings.ClearCache();
if (settings == null) await NotifyAsync(model);
var notificationSettings = (T)settings;

View file

@ -4,7 +4,9 @@ using EnsureThat;
using MailKit.Net.Smtp;
using Microsoft.Extensions.Logging;
using MimeKit;
using MimeKit.Utils;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Notifications.Templates;
using Ombi.Settings.Settings.Models;
@ -36,6 +38,15 @@ namespace Ombi.Notifications
var customization = await CustomizationSettings.GetSettingsAsync();
var html = email.LoadTemplate(model.Subject, model.Message, null, customization.Logo);
var messageId = MimeUtils.GenerateMessageId();
if (customization.ApplicationUrl.HasValue())
{
if (Uri.TryCreate(customization.ApplicationUrl, UriKind.RelativeOrAbsolute, out var url))
{
messageId = MimeUtils.GenerateMessageId(url.IdnHost);
}
}
var textBody = string.Empty;
@ -49,7 +60,8 @@ namespace Ombi.Notifications
var message = new MimeMessage
{
Body = body.ToMessageBody(),
Subject = model.Subject
Subject = model.Subject,
MessageId = messageId
};
message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress));
message.To.Add(new MailboxAddress(model.To, model.To));

View file

@ -17,7 +17,7 @@ namespace Ombi.Notifications
public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s, UserNotificationPreferences pref)
{
LoadIssues(opts);
RequestId = req.Id.ToString();
string title;
if (req == null)
{
@ -68,6 +68,7 @@ namespace Ombi.Notifications
{
LoadIssues(opts);
RequestId = req.Id.ToString();
string title;
if (req == null)
{
@ -114,6 +115,7 @@ namespace Ombi.Notifications
public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s, UserNotificationPreferences pref)
{
LoadIssues(opts);
RequestId = req.Id.ToString();
string title;
if (req == null)
{
@ -239,6 +241,7 @@ namespace Ombi.Notifications
public string UserPreference { get; set; }
public string DenyReason { get; set; }
public string AvailableDate { get; set; }
public string RequestId { get; set; }
// System Defined
private string LongDate => DateTime.Now.ToString("D");
@ -275,6 +278,7 @@ namespace Ombi.Notifications
{nameof(UserPreference),UserPreference},
{nameof(DenyReason),DenyReason},
{nameof(AvailableDate),AvailableDate},
{nameof(RequestId),RequestId},
};
}
}

View file

@ -10,11 +10,12 @@
<ItemGroup>
<PackageReference Include="Ensure.That" Version="7.0.0-pre32" />
<PackageReference Include="MailKit" Version="2.0.5" />
<PackageReference Include="MailKit" Version="2.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
<ProjectReference Include="..\Ombi.Api.Gotify\Ombi.Api.Gotify.csproj" />
<ProjectReference Include="..\Ombi.Api.Mattermost\Ombi.Api.Mattermost.csproj" />
<ProjectReference Include="..\Ombi.Api.Notifications\Ombi.Api.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj" />

View file

@ -44,12 +44,12 @@ namespace Ombi.Schedule.Tests
new Issues
{
Status = IssueStatus.Resolved,
ResovledDate = DateTime.Now.AddDays(-5).AddHours(-1)
ResovledDate = DateTime.UtcNow.AddDays(-5).AddHours(-8)
}
};
Settings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new IssueSettings { DeleteIssues = true, DaysAfterResolvedToDelete = 5 });
Repo.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Issues>(issues));
Repo.Setup(x => x.GetAll()).Returns(issues.AsQueryable());
await Job.Start();
Assert.That(issues.First().Status, Is.EqualTo(IssueStatus.Deleted));
@ -57,7 +57,7 @@ namespace Ombi.Schedule.Tests
}
[Test]
public async Task DoesNot_Delete_AnyIssues()
public async Task DoesNot_Delete_AllIssues()
{
var issues = new List<Issues>()
{
@ -81,5 +81,31 @@ namespace Ombi.Schedule.Tests
Assert.That(issues[1].Status, Is.EqualTo(IssueStatus.Deleted));
Repo.Verify(x => x.SaveChangesAsync(), Times.Once);
}
[Test]
public async Task DoesNot_Delete_AnyIssues()
{
var issues = new List<Issues>()
{
new Issues
{
Status = IssueStatus.Resolved,
ResovledDate = DateTime.Now.AddDays(-2)
},
new Issues
{
Status = IssueStatus.Resolved,
ResovledDate = DateTime.Now.AddDays(-4)
}
};
Settings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new IssueSettings { DeleteIssues = true, DaysAfterResolvedToDelete = 5 });
Repo.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Issues>(issues));
await Job.Start();
Assert.That(issues[0].Status, Is.Not.EqualTo(IssueStatus.Deleted));
Assert.That(issues[1].Status, Is.Not.EqualTo(IssueStatus.Deleted));
Repo.Verify(x => x.SaveChangesAsync(), Times.Once);
}
}
}

View file

@ -6,11 +6,12 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="MockQueryable.Moq" Version="1.1.0" />
<PackageReference Include="Moq" Version="4.10.0" />
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="Nunit" Version="3.11.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.0.1"></packagereference>
</ItemGroup>
<ItemGroup>

View file

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Castle.Components.DictionaryAdapter;
using Hangfire;
using Moq;
using MockQueryable.Moq;
using NUnit.Framework;
using Ombi.Core.Notifications;
using Ombi.Schedule.Jobs.Plex;
@ -68,7 +69,6 @@ namespace Ombi.Schedule.Tests
}
[Test]
[Ignore("EF IAsyncQueryProvider")]
public async Task ProcessTv_ShouldMark_Episode_Available_WhenInPlex()
{
var request = new ChildRequests
@ -90,21 +90,25 @@ namespace Ombi.Schedule.Tests
}
}
}
},
RequestedUser = new OmbiUser
{
Email = "abc"
}
};
_tv.Setup(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable());
_tv.Setup(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable().BuildMock().Object);
_repo.Setup(x => x.GetAllEpisodes()).Returns(new List<PlexEpisode>
{
new PlexEpisode
{
Series = new PlexServerContent
{
ImdbId = 1.ToString(),
TvDbId = 1.ToString(),
},
EpisodeNumber = 1,
SeasonNumber = 2
}
}.AsQueryable);
}.AsQueryable().BuildMock().Object);
_repo.Setup(x => x.Include(It.IsAny<IQueryable<PlexEpisode>>(),It.IsAny<Expression<Func<PlexEpisode, PlexServerContent>>>()));
await Checker.Start();

View file

@ -81,7 +81,6 @@ namespace Ombi.Schedule
RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
RecurringJob.AddOrUpdate(() => _resender.Start(), JobSettingsHelper.ResendFailedRequests(s));
RecurringJob.AddOrUpdate(() => _mediaDatabaseRefresh.Start(), JobSettingsHelper.MediaDatabaseRefresh(s));
}

View file

@ -28,7 +28,6 @@ namespace Ombi.Schedule.Jobs.Emby
_repo = repo;
_episodeSync = epSync;
_metadata = metadata;
_settings.ClearCache();
}
private readonly ILogger<EmbyContentSync> _logger;

View file

@ -49,7 +49,6 @@ namespace Ombi.Schedule.Jobs.Emby
_settings = s;
_repo = repo;
_avaliabilityChecker = checker;
_settings.ClearCache();
}
private readonly ISettingsService<EmbySettings> _settings;

View file

@ -50,8 +50,6 @@ namespace Ombi.Schedule.Jobs.Emby
_log = log;
_embySettings = embySettings;
_userManagementSettings = ums;
_userManagementSettings.ClearCache();
_embySettings.ClearCache();
}
private readonly IEmbyApi _api;

View file

@ -1,19 +1,16 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr;
using Ombi.Api.Radarr;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Ombi.Schedule.Jobs.Lidarr
@ -29,7 +26,6 @@ namespace Ombi.Schedule.Jobs.Lidarr
_ctx = ctx;
_job = job;
_availability = availability;
_lidarrSettings.ClearCache();
}
private readonly ISettingsService<LidarrSettings> _lidarrSettings;

View file

@ -1,19 +1,16 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr;
using Ombi.Api.Radarr;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Ombi.Schedule.Jobs.Lidarr
@ -29,7 +26,6 @@ namespace Ombi.Schedule.Jobs.Lidarr
_ctx = ctx;
_job = background;
_albumSync = album;
_lidarrSettings.ClearCache();
}
private readonly ISettingsService<LidarrSettings> _lidarrSettings;

View file

@ -28,9 +28,10 @@ namespace Ombi.Schedule.Jobs.Ombi
return;
}
var now = DateTime.Now.AddDays(-settings.DaysAfterResolvedToDelete).Date;
var today = DateTime.UtcNow.Date;
var resolved = _issuesRepository.GetAll().Where(x => x.Status == IssueStatus.Resolved);
var toDelete = resolved.Where(x => x.ResovledDate.HasValue && x.ResovledDate.Value.Date <= now);
var toDelete = resolved.Where(x => x.ResovledDate.HasValue && (today - x.ResovledDate.Value.Date).TotalDays >= settings.DaysAfterResolvedToDelete);
foreach (var d in toDelete)
{

View file

@ -10,28 +10,23 @@ using Ombi.Schedule.Jobs.Emby;
using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Store.Repository;
namespace Ombi.Schedule.Jobs.Plex
namespace Ombi.Schedule.Jobs.Ombi
{
public class MediaDatabaseRefresh : IMediaDatabaseRefresh
{
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log, IPlexApi plexApi,
IPlexContentRepository plexRepo, IPlexContentSync c, IEmbyContentRepository embyRepo, IEmbyContentSync embySync)
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log,
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IEmbyContentSync embySync)
{
_settings = s;
_log = log;
_api = plexApi;
_plexRepo = plexRepo;
_plexContentSync = c;
_embyRepo = embyRepo;
_embyContentSync = embySync;
_settings.ClearCache();
}
private readonly ISettingsService<PlexSettings> _settings;
private readonly ILogger _log;
private readonly IPlexApi _api;
private readonly IPlexContentRepository _plexRepo;
private readonly IPlexContentSync _plexContentSync;
private readonly IEmbyContentRepository _embyRepo;
private readonly IEmbyContentSync _embyContentSync;

View file

@ -16,6 +16,7 @@ using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Api.TvMaze;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Notifications;
using Ombi.Notifications.Models;
@ -36,7 +37,7 @@ namespace Ombi.Schedule.Jobs.Ombi
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log,
ILidarrApi lidarrApi, IRepository<LidarrAlbumCache> albumCache, ISettingsService<LidarrSettings> lidarrSettings,
ISettingsService<OmbiSettings> ombiSettings)
ISettingsService<OmbiSettings> ombiSettings, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings)
{
_plex = plex;
_emby = emby;
@ -49,16 +50,13 @@ namespace Ombi.Schedule.Jobs.Ombi
_emailSettings = emailSettings;
_newsletterSettings = newsletter;
_userManager = um;
_emailSettings.ClearCache();
_customizationSettings.ClearCache();
_newsletterSettings.ClearCache();
_log = log;
_lidarrApi = lidarrApi;
_lidarrAlbumRepository = albumCache;
_lidarrSettings = lidarrSettings;
_ombiSettings = ombiSettings;
_ombiSettings.ClearCache();
_lidarrSettings.ClearCache();
_plexSettings = plexSettings;
_embySettings = embySettings;
}
private readonly IPlexContentRepository _plex;
@ -77,6 +75,8 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly ILidarrApi _lidarrApi;
private readonly IRepository<LidarrAlbumCache> _lidarrAlbumRepository;
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
private readonly ISettingsService<PlexSettings> _plexSettings;
private readonly ISettingsService<EmbySettings> _embySettings;
public async Task Start(NewsletterSettings settings, bool test)
{
@ -132,6 +132,8 @@ namespace Ombi.Schedule.Jobs.Ombi
_log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count());
_log.LogInformation("Emby Episodes to send: {0}", embyEpisodesToSend.Count());
var plexSettings = await _plexSettings.GetSettingsAsync();
var embySettings = await _embySettings.GetSettingsAsync();
var body = string.Empty;
if (test)
{
@ -140,11 +142,11 @@ namespace Ombi.Schedule.Jobs.Ombi
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet();
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
var lidarr = lidarrContent.OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
body = await BuildHtml(plexm, embym, plext, embyt, lidarr, settings);
body = await BuildHtml(plexm, embym, plext, embyt, lidarr, settings, embySettings, plexSettings);
}
else
{
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, lidarrContentAlbumsToSend, settings);
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, lidarrContentAlbumsToSend, settings, embySettings, plexSettings);
if (body.IsNullOrEmpty())
{
return;
@ -333,7 +335,8 @@ namespace Ombi.Schedule.Jobs.Ombi
}
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend,
HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, HashSet<LidarrAlbumCache> albums, NewsletterSettings settings)
HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, HashSet<LidarrAlbumCache> albums, NewsletterSettings settings, EmbySettings embySettings,
PlexSettings plexSettings)
{
var ombiSettings = await _ombiSettings.GetSettingsAsync();
var sb = new StringBuilder();
@ -349,8 +352,16 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
sb.Append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
await ProcessPlexMovies(plexMovies, sb, ombiSettings.DefaultLanguageCode);
await ProcessEmbyMovies(embyMovies, sb, ombiSettings.DefaultLanguageCode);
if (plexSettings.Enable)
{
await ProcessPlexMovies(plexMovies, sb, ombiSettings.DefaultLanguageCode);
}
if (embySettings.Enable)
{
await ProcessEmbyMovies(embyMovies, sb, ombiSettings.DefaultLanguageCode);
}
sb.Append("</tr>");
sb.Append("</table>");
sb.Append("</td>");
@ -367,8 +378,16 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
sb.Append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
await ProcessPlexTv(plexEpisodes, sb);
await ProcessEmbyTv(embyEp, sb);
if (plexSettings.Enable)
{
await ProcessPlexTv(plexEpisodes, sb);
}
if (embySettings.Enable)
{
await ProcessEmbyTv(embyEp, sb);
}
sb.Append("</tr>");
sb.Append("</table>");
sb.Append("</td>");

View file

@ -5,18 +5,13 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Hangfire;
using Hangfire.Console;
using Hangfire.Server;
using Microsoft.Extensions.Logging;
using Ombi.Api.Service;
using Ombi.Api.Service.Models;
using Ombi.Core.Processor;
using Ombi.Core.Settings;
using Ombi.Helpers;
@ -40,7 +35,6 @@ namespace Ombi.Schedule.Jobs.Ombi
Settings = s;
_processProvider = proc;
_appConfig = appConfig;
Settings.ClearCache();
}
private ILogger<OmbiAutomaticUpdater> Logger { get; }

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.Extensions.Logging;
using Ombi.Api.Emby;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Api.TvMaze;
@ -21,7 +22,8 @@ namespace Ombi.Schedule.Jobs.Ombi
{
public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo,
ILogger<RefreshMetadata> log, ITvMazeApi tvApi, ISettingsService<PlexSettings> plexSettings,
IMovieDbApi movieApi, ISettingsService<EmbySettings> embySettings, IPlexAvailabilityChecker plexAvailability, IEmbyAvaliabilityChecker embyAvaliability)
IMovieDbApi movieApi, ISettingsService<EmbySettings> embySettings, IPlexAvailabilityChecker plexAvailability, IEmbyAvaliabilityChecker embyAvaliability,
IEmbyApi embyApi)
{
_plexRepo = plexRepo;
_embyRepo = embyRepo;
@ -32,6 +34,7 @@ namespace Ombi.Schedule.Jobs.Ombi
_embySettings = embySettings;
_plexAvailabilityChecker = plexAvailability;
_embyAvaliabilityChecker = embyAvaliability;
_embyApi = embyApi;
}
private readonly IPlexContentRepository _plexRepo;
@ -43,6 +46,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly ITvMazeApi _tvApi;
private readonly ISettingsService<PlexSettings> _plexSettings;
private readonly ISettingsService<EmbySettings> _embySettings;
private readonly IEmbyApi _embyApi;
public async Task Start()
{
@ -54,11 +58,11 @@ namespace Ombi.Schedule.Jobs.Ombi
{
await StartPlex();
}
var embySettings = await _embySettings.GetSettingsAsync();
if (embySettings.Enable)
{
await StartEmby();
await StartEmby(embySettings);
}
}
catch (Exception e)
@ -123,9 +127,9 @@ namespace Ombi.Schedule.Jobs.Ombi
await StartPlexTv(allTv);
}
private async Task StartEmby()
private async Task StartEmby(EmbySettings s)
{
await StartEmbyMovies();
await StartEmbyMovies(s);
await StartEmbyTv();
}
@ -158,7 +162,7 @@ namespace Ombi.Schedule.Jobs.Ombi
_plexRepo.UpdateWithoutSave(show);
}
tvCount++;
if (tvCount >= 20)
if (tvCount >= 75)
{
await _plexRepo.SaveChangesAsync();
tvCount = 0;
@ -198,7 +202,7 @@ namespace Ombi.Schedule.Jobs.Ombi
_embyRepo.UpdateWithoutSave(show);
}
tvCount++;
if (tvCount >= 20)
if (tvCount >= 75)
{
await _embyRepo.SaveChangesAsync();
tvCount = 0;
@ -229,7 +233,7 @@ namespace Ombi.Schedule.Jobs.Ombi
_plexRepo.UpdateWithoutSave(movie);
}
movieCount++;
if (movieCount >= 20)
if (movieCount >= 75)
{
await _plexRepo.SaveChangesAsync();
movieCount = 0;
@ -239,31 +243,56 @@ namespace Ombi.Schedule.Jobs.Ombi
await _plexRepo.SaveChangesAsync();
}
private async Task StartEmbyMovies()
private async Task StartEmbyMovies(EmbySettings settings)
{
var allMovies = _embyRepo.GetAll().Where(x =>
x.Type == EmbyMediaType.Movie && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue()));
int movieCount = 0;
foreach (var movie in allMovies)
{
var hasImdb = movie.ImdbId.HasValue();
var hasTheMovieDb = movie.TheMovieDbId.HasValue();
movie.ImdbId.HasValue();
movie.TheMovieDbId.HasValue();
// Movies don't really use TheTvDb
if (!hasImdb)
// Check if it even has 1 ID
if (!movie.HasImdb && !movie.HasTheMovieDb)
{
var imdbId = await GetImdbId(hasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty);
// Ok this sucks,
// The only think I can think that has happened is that we scanned Emby before Emby has got the metadata
// So let's recheck emby to see if they have got the metadata now
_log.LogInformation($"Movie {movie.Title} does not have a ImdbId or TheMovieDbId, so rechecking emby");
foreach (var server in settings.Servers)
{
_log.LogInformation($"Checking server {server.Name} for upto date metadata");
var movieInfo = await _embyApi.GetMovieInformation(movie.EmbyId, server.ApiKey, server.AdministratorId,
server.FullUri);
if (movieInfo.ProviderIds?.Imdb.HasValue() ?? false)
{
movie.ImdbId = movieInfo.ProviderIds.Imdb;
}
if (movieInfo.ProviderIds?.Tmdb.HasValue() ?? false)
{
movie.TheMovieDbId = movieInfo.ProviderIds.Tmdb;
}
}
}
if (!movie.HasImdb)
{
var imdbId = await GetImdbId(movie.HasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty);
movie.ImdbId = imdbId;
_embyRepo.UpdateWithoutSave(movie);
}
if (!hasTheMovieDb)
if (!movie.HasTheMovieDb)
{
var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title, true);
var id = await GetTheMovieDbId(false, movie.HasImdb, string.Empty, movie.ImdbId, movie.Title, true);
movie.TheMovieDbId = id;
_embyRepo.UpdateWithoutSave(movie);
}
movieCount++;
if (movieCount >= 20)
if (movieCount >= 75)
{
await _embyRepo.SaveChangesAsync();
movieCount = 0;

View file

@ -20,8 +20,6 @@ namespace Ombi.Schedule.Jobs.Ombi
_email = provider;
_templates = template;
_customizationSettings = c;
email.ClearCache();
_customizationSettings.ClearCache();
}
private readonly ISettingsService<EmailNotificationSettings> _emailSettings;

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