refactor(newsletter): ♻️ Media servers + newsletter refactoring (#4463)

* Abstract media servers content into interfaces

* Media server entities into abstract classes

* Abstract media server content repository

* First pass at newsletter refactoring

* Minor code clean up

* Attempt at abstracting repositories (WIP)

* Fixed cast issue

* Corrected the other properties

* A step towards newsletter refactoring

* Clean up leftovers

* Fix broken episodes db interaction

* Save absolute URL for Plex content

Let's be consistent with Emby and Jellyfin

* Fix broken integration with Plex libraries

* Fix error when multiple media servers configured

* Fix newsletter being sent if no movies or episodes

* Fix broken tests

* Remove unneccesary logs

* Expose stored media server URL

No need to recalculate it
+ Plex URL was broken due to an earlier change

* Remove unused variable

* Remove obsolete tests

URLs are now fetched from database directly

* Retro-compatibility for Plex content URL

Solves URL for media synced before absolute URL was saved in PlexServerContent

* chore: added some obsoletes

* fix: removed the unsub link when not present

Co-authored-by: tidusjar <tidusjar@gmail.com>
pull/4485/head
sephrat 3 years ago committed by GitHub
parent 32ee4e88ec
commit 0ff0a704ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -929,7 +929,7 @@
<e p="EmbyConfiguration.cs" t="Include" />
<e p="EmbyConnectUser.cs" t="Include" />
<e p="EmbyItemContainer.cs" t="Include" />
<e p="EmbyMediaType.cs" t="Include" />
<e p="MediaType.cs" t="Include" />
<e p="EmbyPolicy.cs" t="Include" />
<e p="EmbySystemInfo.cs" t="Include" />
<e p="EmbyUser.cs" t="Include" />
@ -1876,7 +1876,7 @@
<e p="PlexAvailabilityChecker.cs" t="Include" />
<e p="PlexContentSync.cs" t="Include" />
<e p="PlexEpisodeSync.cs" t="Include" />
<e p="PlexMediaType.cs" t="Include" />
<e p="MediaType.cs" t="Include" />
<e p="PlexRecentlyAddedSync.cs" t="Include" />
<e p="PlexUserImporter.cs" t="Include" />
</e>

@ -1,10 +0,0 @@
namespace Ombi.Api.Emby.Models
{
public enum EmbyMediaType
{
Movie = 0,
Series = 1,
Music = 2,
Episode = 3
}
}

@ -1,10 +0,0 @@
namespace Ombi.Api.Jellyfin.Models
{
public enum JellyfinMediaType
{
Movie = 0,
Series = 1,
Music = 2,
Episode = 3
}
}

@ -182,11 +182,11 @@ namespace Ombi.Core.Tests.Rule.Request
{
new PlexServerContent
{
Type = PlexMediaTypeEntity.Show,
Type = MediaType.Series,
TheMovieDbId = "1",
Title = "Test",
ReleaseYear = "2001",
Episodes = new List<PlexEpisode>
Episodes = new List<IMediaServerEpisode>
{
new PlexEpisode
{

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
@ -18,12 +19,14 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup()
{
ContextMock = new Mock<IEmbyContentRepository>();
LoggerMock = new Mock<ILogger<EmbyAvailabilityRule>>();
SettingsMock = new Mock<ISettingsService<EmbySettings>>();
Rule = new EmbyAvailabilityRule(ContextMock.Object, SettingsMock.Object);
Rule = new EmbyAvailabilityRule(ContextMock.Object, LoggerMock.Object, SettingsMock.Object);
}
private EmbyAvailabilityRule Rule { get; set; }
private Mock<IEmbyContentRepository> ContextMock { get; set; }
private Mock<ILogger<EmbyAvailabilityRule>> LoggerMock { get; set; }
private Mock<ISettingsService<EmbySettings>> SettingsMock { get; set; }
[Test]
@ -44,66 +47,6 @@ 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/",
ServerId = "8"
}
}
});
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/web/index.html#!/item?id=1&serverId=8"));
}
[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,
ServerId = "8"
}
}
});
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/web/index.html#!/item?id=1&serverId=8"));
}
[Test]
public async Task Movie_ShouldBe_NotAvailable_WhenNotFoundInEmby()
{

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
@ -18,12 +19,14 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup()
{
ContextMock = new Mock<IJellyfinContentRepository>();
LoggerMock = new Mock<ILogger<JellyfinAvailabilityRule>>();
SettingsMock = new Mock<ISettingsService<JellyfinSettings>>();
Rule = new JellyfinAvailabilityRule(ContextMock.Object, SettingsMock.Object);
Rule = new JellyfinAvailabilityRule(ContextMock.Object, LoggerMock.Object, SettingsMock.Object);
}
private JellyfinAvailabilityRule Rule { get; set; }
private Mock<IJellyfinContentRepository> ContextMock { get; set; }
private Mock<ILogger<JellyfinAvailabilityRule>> LoggerMock { get; set; }
private Mock<ISettingsService<JellyfinSettings>> SettingsMock { get; set; }
[Test]
@ -44,36 +47,6 @@ 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 JellyfinSettings
{
Enable = true,
Servers = new List<JellyfinServers>
{
new JellyfinServers
{
ServerHostname = "http://test.com/",
ServerId = "8"
}
}
});
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new JellyfinContent
{
ProviderId = "123",
JellyfinId = 1.ToString(),
});
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.That(search.JellyfinUrl, Is.EqualTo("http://test.com/web/index.html#!/details?id=1&serverId=8"));
}
[Test]
public async Task Movie_Uses_Default_Url_When()
{

@ -30,26 +30,26 @@ namespace Ombi.Core.Engine
public IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(DateTime from, DateTime to)
{
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie && x.AddedAt > from && x.AddedAt < to);
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie && x.AddedAt > from && x.AddedAt < to);
var jellyfinMovies = _jellyfin.GetAll().Where(x => x.Type == JellyfinMediaType.Movie && x.AddedAt > from && x.AddedAt < to);
var plexMovies = _plex.GetAll().Where(x => x.Type == MediaType.Movie && x.AddedAt > from && x.AddedAt < to);
var embyMovies = _emby.GetAll().Where(x => x.Type == MediaType.Movie && x.AddedAt > from && x.AddedAt < to);
var jellyfinMovies = _jellyfin.GetAll().Where(x => x.Type == MediaType.Movie && x.AddedAt > from && x.AddedAt < to);
return GetRecentlyAddedMovies(plexMovies, embyMovies, jellyfinMovies).Take(30);
}
public IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies()
{
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie);
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie);
var jellyfinMovies = _jellyfin.GetAll().Where(x => x.Type == JellyfinMediaType.Movie);
var plexMovies = _plex.GetAll().Where(x => x.Type == MediaType.Movie);
var embyMovies = _emby.GetAll().Where(x => x.Type == MediaType.Movie);
var jellyfinMovies = _jellyfin.GetAll().Where(x => x.Type == MediaType.Movie);
return GetRecentlyAddedMovies(plexMovies, embyMovies, jellyfinMovies);
}
public IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason)
{
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show && x.AddedAt > from && x.AddedAt < to);
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series && x.AddedAt > from && x.AddedAt < to);
var jellyfinTv = _jellyfin.GetAll().Include(x => x.Episodes).Where(x => x.Type == JellyfinMediaType.Series && x.AddedAt > from && x.AddedAt < to);
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == MediaType.Series && x.AddedAt > from && x.AddedAt < to);
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == MediaType.Series && x.AddedAt > from && x.AddedAt < to);
var jellyfinTv = _jellyfin.GetAll().Include(x => x.Episodes).Where(x => x.Type == MediaType.Series && x.AddedAt > from && x.AddedAt < to);
return GetRecentlyAddedTv(plexTv, embyTv, jellyfinTv, groupBySeason).Take(30);
}
@ -57,9 +57,9 @@ namespace Ombi.Core.Engine
public IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(bool groupBySeason)
{
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show);
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series);
var jellyfinTv = _jellyfin.GetAll().Include(x => x.Episodes).Where(x => x.Type == JellyfinMediaType.Series);
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == MediaType.Series);
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == MediaType.Series);
var jellyfinTv = _jellyfin.GetAll().Include(x => x.Episodes).Where(x => x.Type == MediaType.Series);
return GetRecentlyAddedTv(plexTv, embyTv, jellyfinTv, groupBySeason);
}
@ -76,7 +76,7 @@ namespace Ombi.Core.Engine
{
continue;
}
if (p.Type == PlexMediaTypeEntity.Movie)
if (p.Type == MediaType.Movie)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
@ -114,7 +114,7 @@ namespace Ombi.Core.Engine
{
continue;
}
if (e.Type == EmbyMediaType.Movie)
if (e.Type == MediaType.Movie)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
@ -152,7 +152,7 @@ namespace Ombi.Core.Engine
{
continue;
}
if (e.Type == JellyfinMediaType.Movie)
if (e.Type == MediaType.Movie)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{

@ -32,7 +32,7 @@ namespace Ombi.Core.Rule.Rules.Request
{
var tvRequest = (ChildRequests) obj;
var tvContent = _plexContent.GetAll().Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show);
var tvContent = _plexContent.GetAll().Include(x => x.Episodes).Where(x => x.Type == MediaType.Series);
// We need to do a check on the TVDBId
var anyMovieDbMatches = await tvContent.FirstOrDefaultAsync(x => x.TheMovieDbId.Length > 0 && x.TheMovieDbId == tvRequest.Id.ToString());
if (anyMovieDbMatches == null)

@ -46,10 +46,10 @@ 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, ILogger log)
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<IMediaServerEpisode> allEpisodes, EpisodeRequests episode,
SeasonRequests season, IMediaServerContent item, bool useTheMovieDb, bool useTvDb, ILogger log)
{
PlexEpisode epExists = null;
IMediaServerEpisode epExists = null;
try
{
@ -79,66 +79,6 @@ namespace Ombi.Core.Rule.Rules.Search
log.LogError(e, "Exception thrown when attempting to check if something is available");
}
if (epExists != null)
{
episode.Available = true;
}
}
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<EmbyEpisode> allEpisodes, EpisodeRequests episode,
SeasonRequests season, EmbyContent item, bool useTheMovieDb, bool useTvDb)
{
EmbyEpisode epExists = null;
if (useImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
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);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId);
}
if (epExists != null)
{
episode.Available = true;
}
}
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<JellyfinEpisode> allEpisodes, EpisodeRequests episode,
SeasonRequests season, JellyfinContent item, bool useTheMovieDb, bool useTvDb)
{
JellyfinEpisode epExists = null;
if (useImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
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);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId);
}
if (epExists != null)
{
episode.Available = true;

@ -1,6 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
@ -13,13 +14,15 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class EmbyAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{
public EmbyAvailabilityRule(IEmbyContentRepository repo, ISettingsService<EmbySettings> s)
public EmbyAvailabilityRule(IEmbyContentRepository repo, ILogger<EmbyAvailabilityRule> log, ISettingsService<EmbySettings> s)
{
EmbyContentRepository = repo;
Log = log;
EmbySettings = s;
}
private IEmbyContentRepository EmbyContentRepository { get; }
private ILogger Log { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
public async Task<RuleResult> Execute(SearchViewModel obj)
@ -64,19 +67,7 @@ namespace Ombi.Core.Rule.Rules.Search
if (item != null)
{
obj.Available = true;
var s = await EmbySettings.GetSettingsAsync();
if (s.Enable)
{
var server = s.Servers.FirstOrDefault();
if ((server?.ServerHostname ?? string.Empty).HasValue())
{
obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, server?.ServerHostname);
}
else
{
obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, null);
}
}
obj.EmbyUrl = item.Url;
if (obj.Type == RequestType.TvShow)
{
@ -89,7 +80,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);
}
}
}

@ -1,6 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
@ -13,13 +14,15 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class JellyfinAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{
public JellyfinAvailabilityRule(IJellyfinContentRepository repo, ISettingsService<JellyfinSettings> s)
public JellyfinAvailabilityRule(IJellyfinContentRepository repo, ILogger<JellyfinAvailabilityRule> log, ISettingsService<JellyfinSettings> s)
{
JellyfinContentRepository = repo;
Log = log;
JellyfinSettings = s;
}
private IJellyfinContentRepository JellyfinContentRepository { get; }
private ILogger Log { get; }
private ISettingsService<JellyfinSettings> JellyfinSettings { get; }
public async Task<RuleResult> Execute(SearchViewModel obj)
@ -78,20 +81,7 @@ namespace Ombi.Core.Rule.Rules.Search
useTheMovieDb = true;
}
obj.Available = true;
var s = await JellyfinSettings.GetSettingsAsync();
if (s.Enable)
{
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
if ((server?.ServerHostname ?? string.Empty).HasValue())
{
obj.JellyfinUrl = JellyfinHelper.GetJellyfinMediaUrl(item.JellyfinId, server?.ServerId, server?.ServerHostname);
}
else
{
var firstServer = s.Servers?.FirstOrDefault();
obj.JellyfinUrl = JellyfinHelper.GetJellyfinMediaUrl(item.JellyfinId, firstServer.ServerId, firstServer.FullUri);
}
}
obj.JellyfinUrl = item.Url;
if (obj.Type == RequestType.TvShow)
{
@ -104,7 +94,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);
}
}
}

@ -33,7 +33,7 @@ namespace Ombi.Core.Rule.Rules.Search
var useId = false;
var useTvDb = false;
PlexMediaTypeEntity type = ConvertType(obj.Type);
MediaType type = ConvertType(obj.Type);
if (obj.ImdbId.HasValue())
{
@ -90,9 +90,17 @@ namespace Ombi.Core.Rule.Rules.Search
useTheMovieDb = true;
}
obj.Available = true;
obj.PlexUrl = PlexHelper.BuildPlexMediaUrl(item.Url, host);
if (item.Url.StartsWith("http"))
{
obj.PlexUrl = item.Url;
}
else
{
// legacy content
obj.PlexUrl = PlexHelper.BuildPlexMediaUrl(item.Url, host);
}
obj.Quality = item.Quality;
if (obj.Type == RequestType.TvShow)
{
var search = (SearchTvShowViewModel)obj;
@ -115,12 +123,12 @@ namespace Ombi.Core.Rule.Rules.Search
return Success();
}
private PlexMediaTypeEntity ConvertType(RequestType type) =>
private MediaType ConvertType(RequestType type) =>
type switch
{
RequestType.Movie => PlexMediaTypeEntity.Movie,
RequestType.TvShow => PlexMediaTypeEntity.Show,
_ => PlexMediaTypeEntity.Movie,
RequestType.Movie => MediaType.Movie,
RequestType.TvShow => MediaType.Series,
_ => MediaType.Movie,
};
}
}

@ -104,11 +104,11 @@ namespace Ombi.Helpers
return new ProviderId();
}
public static string GetPlexMediaUrl(string machineId, int mediaId)
public static string GetPlexMediaUrl(string machineId, int mediaId, string plexHost)
{
var url =
$"web/#!/server/{machineId}/details?key=%2flibrary%2Fmetadata%2F{mediaId}";
return url;
return BuildPlexMediaUrl(url, plexHost);
}
public static string BuildPlexMediaUrl(string savedUrl, string plexHost)

@ -30,6 +30,7 @@ namespace Ombi.Notifications.Templates
private const string TableLocation = "{@RECENTLYADDED}";
private const string IntroText = "{@INTRO}";
private const string Unsubscribe = "{@UNSUBSCRIBE}";
private const string UnsubscribeText = "{@UNSUBSCRIBETEXT}";
public string LoadTemplate(string subject, string intro, string tableHtml, string logo, string unsubscribeLink)
@ -41,6 +42,7 @@ namespace Ombi.Notifications.Templates
sb.Replace(DateKey, DateTime.Now.ToString("f"));
sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo);
sb.Replace(Unsubscribe, string.IsNullOrEmpty(unsubscribeLink) ? string.Empty : unsubscribeLink);
sb.Replace(UnsubscribeText, string.IsNullOrEmpty(unsubscribeLink) ? string.Empty : "Unsubscrible");
return sb.ToString();
}

@ -453,7 +453,7 @@
<tbody>
<tr>
<td class="content-block powered-by" valign="top" align="center" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;">
<a href="{@UNSUBSCRIBE}" style="font-weight: 400; font-size: 12px; text-align: center; color: #ff761b;">Unsubscribe</a>
<a href="{@UNSUBSCRIBE}" style="font-weight: 400; font-size: 12px; text-align: center; color: #ff761b;">{@UNSUBSCRIBETEXT}</a>
</td>
</tr>
<tr>

@ -124,7 +124,7 @@ namespace Ombi.Schedule.Jobs.Emby
var tvDbId = child.ParentRequest.TvDbId;
var imdbId = child.ParentRequest.ImdbId;
IQueryable<EmbyEpisode> seriesEpisodes = null;
IQueryable<IMediaServerEpisode> seriesEpisodes = null;
if (useImdb)
{
seriesEpisodes = embyEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());

@ -16,7 +16,7 @@ using Ombi.Schedule.Jobs.Ombi;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
using EmbyMediaType = Ombi.Store.Entities.EmbyMediaType;
using MediaType = Ombi.Store.Entities.MediaType;
namespace Ombi.Schedule.Jobs.Emby
{
@ -165,10 +165,10 @@ namespace Ombi.Schedule.Jobs.Emby
ImdbId = tvShow.ProviderIds?.Imdb,
TheMovieDbId = tvShow.ProviderIds?.Tmdb,
Title = tvShow.Name,
Type = EmbyMediaType.Series,
Type = MediaType.Series,
EmbyId = tvShow.Id,
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname),
AddedAt = DateTime.UtcNow
AddedAt = DateTime.UtcNow,
});
}
else
@ -255,10 +255,10 @@ namespace Ombi.Schedule.Jobs.Emby
ImdbId = movieInfo.ProviderIds.Imdb,
TheMovieDbId = movieInfo.ProviderIds?.Tmdb,
Title = movieInfo.Name,
Type = EmbyMediaType.Movie,
Type = MediaType.Movie,
EmbyId = movieInfo.Id,
Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname),
AddedAt = DateTime.UtcNow,
AddedAt = DateTime.UtcNow
});
}
else

@ -151,7 +151,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
var tvDbId = child.ParentRequest.TvDbId;
var imdbId = child.ParentRequest.ImdbId;
IQueryable<JellyfinEpisode> seriesEpisodes = null;
IQueryable<IMediaServerEpisode> seriesEpisodes = null;
if (useImdb)
{
seriesEpisodes = jellyfinEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());

@ -14,7 +14,7 @@ using Ombi.Schedule.Jobs.Ombi;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
using JellyfinMediaType = Ombi.Store.Entities.JellyfinMediaType;
using MediaType = Ombi.Store.Entities.MediaType;
namespace Ombi.Schedule.Jobs.Jellyfin
{
@ -143,7 +143,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
ImdbId = tvShow.ProviderIds?.Imdb,
TheMovieDbId = tvShow.ProviderIds?.Tmdb,
Title = tvShow.Name,
Type = JellyfinMediaType.Series,
Type = MediaType.Series,
JellyfinId = tvShow.Id,
Url = JellyfinHelper.GetJellyfinMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname),
AddedAt = DateTime.UtcNow
@ -223,10 +223,10 @@ namespace Ombi.Schedule.Jobs.Jellyfin
ImdbId = movieInfo.ProviderIds.Imdb,
TheMovieDbId = movieInfo.ProviderIds?.Tmdb,
Title = movieInfo.Name,
Type = JellyfinMediaType.Movie,
Type = MediaType.Movie,
JellyfinId = movieInfo.Id,
Url = JellyfinHelper.GetJellyfinMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname),
AddedAt = DateTime.UtcNow,
AddedAt = DateTime.UtcNow
});
}
else

@ -5,7 +5,8 @@ namespace Ombi.Schedule.Jobs.Ombi
{
public abstract class HtmlTemplateGenerator
{
protected virtual void AddBackgroundInsideTable(StringBuilder sb, string url)
protected StringBuilder sb;
protected virtual void AddBackgroundInsideTable(string url)
{
sb.Append("<td align=\"center\" valign=\"top\" width=\"500\" height=\"252\" class=\"media-card\" style=\"font-size: 14px; font-family: 'Open Sans', Helvetica, Arial, sans-serif; vertical-align: top; padding: 3px; width: 500px; min-width: 500px; max-width: 500px; height: 252px; max-height: 252px; \">");
sb.AppendFormat("<table class=\"card-bg\" width=\"500\" height=\"252\" background=\"url(0)\" bgcolor=\"#1f1f1f\" style=\"background-image: url(0); border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 500px; background-color: #1f1f1f; background-position: center; background-size: cover; background-repeat: no-repeat; background-clip: padding-box; border: 2px solid rgba(255, 118, 27, .4); height: 252px; max-height: 252px; \">", url);
@ -14,14 +15,14 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("<table class=\"bg-tint\" width=\"100%\" bgcolor=\"rgba(0, 0, 0, .6)\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: rgba(0, 0, 0, .6); \">");
}
protected virtual void AddPosterInsideTable(StringBuilder sb, string url)
protected virtual void AddPosterInsideTable(string url)
{
sb.Append("<tr>");
sb.Append("<td class=\"poster-container\" width=\"150\" height=\"225\" valign=\"top\" style=\"ont-family: sans-serif; font-size: 14px; vertical-align: top; width: 150px; min-width: 150px; height: 225px; max-height: 225px; min-height: 225px; \">");
sb.AppendFormat("<table class=\"poster-img\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">", url);
}
protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url)
protected virtual void AddMediaServerUrl(string mediaurl, string url)
{
if (url.HasValue())
{
@ -41,14 +42,14 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("</td>");
}
protected virtual void AddInfoTable(StringBuilder sb)
protected virtual void AddInfoTable()
{
sb.Append(
"<td class=\"movie-info\" height=\"227\" valign=\"top\" align=\"left\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; padding-left: 4px; text-align: left; height: 227px; \">");
sb.Append("<table style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; height: 100%; \">");
}
protected virtual void AddTitle(StringBuilder sb, string url, string title)
protected virtual void AddTitle( string url, string title)
{
sb.Append("<tr class=\"title\" valign=\"top\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 22px;line-height: 24px;vertical-align: top;max-width: 320px;display: block;height: 50px;min-height: 50px;max-height: 50px; \">");
sb.Append("<td>");
@ -59,7 +60,7 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("</tr>");
}
protected virtual void AddParagraph(StringBuilder sb, string text)
protected virtual void AddParagraph(string text)
{
sb.Append("<tr class=\"description\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif;height: 130px;max-height: 130px;max-width: 320px;overflow: hidden;text-overflow: ellipsis;display: block;font-size: 14px !important;text-align: justify;\" valign=\"top\">");
sb.Append("<td style=\"font-family: sans-serif; font-size: 14px; vertical-align: top; \">");
@ -68,7 +69,7 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("</tr>");
}
protected virtual void AddTvParagraph(StringBuilder sb, string episodes, string summary)
protected virtual void AddTvParagraph(string episodes, string summary)
{
sb.Append("<tr class=\"description\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif;height: 130px;max-height: 130px;max-width: 320px;overflow: hidden;text-overflow: ellipsis;display: block;font-size: 14px !important;text-align: justify;\" valign=\"top\">");
sb.Append("<td style=\"font-family: sans-serif; font-size: 14px; vertical-align: top; \">");
@ -78,7 +79,7 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("</tr>");
}
protected virtual void AddGenres(StringBuilder sb, string text)
protected virtual void AddGenres(string text)
{
sb.Append("<tr class=\"meta\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; max-width: 300px; min-width: 300px; padding: 3px 7px; margin-top: 10px; line-height: 1; text-align: left; white-space: nowrap; vertical-align: middle; background-color: rgba(255, 118, 27, 0.5); color: #fff; border-radius: 2px; overflow: hidden; display: block; font-size: 0.9rem;\" align=\"left\" valign=\"middle\" bgcolor=\"rgba(255, 118, 27, 0.5)\">");
sb.Append("<td style=\"font-family: sans-serif; font-size: 14px; vertical-align: top; \">");

File diff suppressed because it is too large Load Diff

@ -116,12 +116,12 @@ namespace Ombi.Schedule.Jobs.Ombi
{
// Ensure we check that we have not linked this item to a request
var allMovies = await _plexRepo.GetAll().Where(x =>
x.Type == PlexMediaTypeEntity.Movie && x.RequestId == null && (x.TheMovieDbId == null || x.ImdbId == null)).ToListAsync();
x.Type == MediaType.Movie && x.RequestId == null && (x.TheMovieDbId == null || x.ImdbId == null)).ToListAsync();
await StartPlexMovies(allMovies);
// Now Tv
var allTv = await _plexRepo.GetAll().Where(x =>
x.Type == PlexMediaTypeEntity.Show && x.RequestId == null && (x.TheMovieDbId == null || x.ImdbId == null || x.TvDbId == null)).ToListAsync();
x.Type == MediaType.Series && x.RequestId == null && (x.TheMovieDbId == null || x.ImdbId == null || x.TvDbId == null)).ToListAsync();
await StartPlexTv(allTv);
}
@ -178,7 +178,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private async Task StartEmbyTv()
{
var allTv = await _embyRepo.GetAll().Where(x =>
x.Type == EmbyMediaType.Series && (x.TheMovieDbId == null || x.ImdbId == null || x.TvDbId == null)).ToListAsync();
x.Type == MediaType.Series && (x.TheMovieDbId == null || x.ImdbId == null || x.TvDbId == null)).ToListAsync();
foreach (var show in allTv)
{
@ -213,7 +213,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private async Task StartJellyfinTv()
{
var allTv = await _jellyfinRepo.GetAll().Where(x =>
x.Type == JellyfinMediaType.Series && (x.TheMovieDbId == null || x.ImdbId == null || x.TvDbId == null)).ToListAsync();
x.Type == MediaType.Series && (x.TheMovieDbId == null || x.ImdbId == null || x.TvDbId == null)).ToListAsync();
foreach (var show in allTv)
{
@ -278,7 +278,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private async Task StartEmbyMovies(EmbySettings settings)
{
var allMovies = await _embyRepo.GetAll().Where(x =>
x.Type == EmbyMediaType.Movie && (x.TheMovieDbId == null || x.ImdbId == null)).ToListAsync();
x.Type == MediaType.Movie && (x.TheMovieDbId == null || x.ImdbId == null)).ToListAsync();
foreach (var movie in allMovies)
{
movie.ImdbId.HasValue();
@ -333,7 +333,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private async Task StartJellyfinMovies(JellyfinSettings settings)
{
var allMovies = await _jellyfinRepo.GetAll().Where(x =>
x.Type == JellyfinMediaType.Movie && (x.TheMovieDbId == null || x.ImdbId == null)).ToListAsync();
x.Type == MediaType.Movie && (x.TheMovieDbId == null || x.ImdbId == null)).ToListAsync();
foreach (var movie in allMovies)
{
movie.ImdbId.HasValue();

@ -86,7 +86,7 @@ namespace Ombi.Schedule.Jobs.Plex
var tvDbId = child.ParentRequest.TvDbId;
var imdbId = child.ParentRequest.ImdbId;
IQueryable<PlexEpisode> seriesEpisodes = null;
IQueryable<IMediaServerEpisode> seriesEpisodes = null;
if (useImdb)
{
seriesEpisodes = plexEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());
@ -105,8 +105,7 @@ namespace Ombi.Schedule.Jobs.Plex
{
// Let's try and match the series by name
seriesEpisodes = plexEpisodes.Where(x =>
x.Series.Title == child.Title &&
x.Series.ReleaseYear == child.ParentRequest.ReleaseDate.Year.ToString());
x.Series.Title == child.Title);
}

@ -226,7 +226,7 @@ namespace Ombi.Schedule.Jobs.Plex
await Repo.SaveChangesAsync();
if (content.Metadata != null)
{
var episodesAdded = await EpisodeSync.ProcessEpsiodes(content.Metadata, allEps);
var episodesAdded = await EpisodeSync.ProcessEpsiodes(content.Metadata, (IQueryable<PlexEpisode>)allEps);
episodesProcessed.AddRange(episodesAdded.Select(x => x.Id));
}
}
@ -301,7 +301,7 @@ namespace Ombi.Schedule.Jobs.Plex
{
var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title
&& x.ReleaseYear == movie.year.ToString()
&& x.Type == PlexMediaTypeEntity.Movie);
&& x.Type == MediaType.Movie);
// The rating key keeps changing
//var existing = await Repo.GetByKey(movie.ratingKey);
if (existing != null)
@ -349,9 +349,9 @@ namespace Ombi.Schedule.Jobs.Plex
AddedAt = DateTime.Now,
Key = movie.ratingKey,
ReleaseYear = movie.year.ToString(),
Type = PlexMediaTypeEntity.Movie,
Type = MediaType.Movie,
Title = movie.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey),
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey, servers.ServerHostname),
Seasons = new List<PlexSeasonsContent>(),
Quality = movie.Media?.FirstOrDefault()?.videoResolution ?? string.Empty
};
@ -411,7 +411,7 @@ namespace Ombi.Schedule.Jobs.Plex
// Let's try and match
var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title
&& x.ReleaseYear == show.year.ToString()
&& x.Type == PlexMediaTypeEntity.Show);
&& x.Type == MediaType.Series);
// Just double check the rating key, since this is our unique constraint
var existingKey = await Repo.GetByKey(show.ratingKey);
@ -463,7 +463,7 @@ namespace Ombi.Schedule.Jobs.Plex
Repo.DeleteWithoutSave(existingContent);
// Because we have changed the rating key, we need to change all children too
var episodeToChange = Repo.GetAllEpisodes().Where(x => x.GrandparentKey == oldKey);
var episodeToChange = Repo.GetAllEpisodes().Cast<PlexEpisode>().Where(x => x.GrandparentKey == oldKey);
if (episodeToChange.Any())
{
foreach (var e in episodeToChange)
@ -553,9 +553,9 @@ namespace Ombi.Schedule.Jobs.Plex
AddedAt = DateTime.Now,
Key = show.ratingKey,
ReleaseYear = show.year.ToString(),
Type = PlexMediaTypeEntity.Show,
Type = MediaType.Series,
Title = show.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey, servers.ServerHostname),
Seasons = new List<PlexSeasonsContent>()
};
await GetProviderIds(showMetadata, item);
@ -567,19 +567,19 @@ namespace Ombi.Schedule.Jobs.Plex
if (item.ImdbId.HasValue())
{
existingImdb = await Repo.GetAll().AnyAsync(x =>
x.ImdbId == item.ImdbId && x.Type == PlexMediaTypeEntity.Show);
x.ImdbId == item.ImdbId && x.Type == MediaType.Series);
}
if (item.TheMovieDbId.HasValue())
{
existingMovieDbId = await Repo.GetAll().AnyAsync(x =>
x.TheMovieDbId == item.TheMovieDbId && x.Type == PlexMediaTypeEntity.Show);
x.TheMovieDbId == item.TheMovieDbId && x.Type == MediaType.Series);
}
if (item.TvDbId.HasValue())
{
existingTvDbId = await Repo.GetAll().AnyAsync(x =>
x.TvDbId == item.TvDbId && x.Type == PlexMediaTypeEntity.Show);
x.TvDbId == item.TvDbId && x.Type == MediaType.Series);
}
if (existingImdb || existingTvDbId || existingMovieDbId)

@ -113,7 +113,7 @@ namespace Ombi.Schedule.Jobs.Plex
{
var currentPosition = 0;
var resultCount = settings.EpisodeBatchSize == 0 ? 150 : settings.EpisodeBatchSize;
var currentEpisodes = _repo.GetAllEpisodes();
var currentEpisodes = _repo.GetAllEpisodes().Cast<PlexEpisode>();
var episodes = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition, resultCount);
_log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Total Epsiodes found for {episodes.MediaContainer.librarySectionTitle} = {episodes.MediaContainer.totalSize}");

@ -1,4 +1,5 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Ombi.Helpers;
using Ombi.Store.Entities;
@ -42,20 +43,20 @@ namespace Ombi.Store.Context
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<PlexServerContent>().HasMany(x => x.Episodes)
.WithOne(x => x.Series)
builder.Entity<PlexServerContent>().HasMany(x => (ICollection<PlexEpisode>) x.Episodes)
.WithOne(x => (PlexServerContent) x.Series)
.HasPrincipalKey(x => x.Key)
.HasForeignKey(x => x.GrandparentKey);
builder.Entity<EmbyEpisode>()
.HasOne(p => p.Series)
.WithMany(b => b.Episodes)
.HasOne(p => (EmbyContent) p.Series)
.WithMany(b => (ICollection<EmbyEpisode>) b.Episodes)
.HasPrincipalKey(x => x.EmbyId)
.HasForeignKey(p => p.ParentId);
builder.Entity<JellyfinEpisode>()
.HasOne(p => p.Series)
.WithMany(b => b.Episodes)
.HasOne(p => (JellyfinContent) p.Series)
.WithMany(b => (ICollection<JellyfinEpisode>) b.Episodes)
.HasPrincipalKey(x => x.JellyfinId)
.HasForeignKey(p => p.ParentId);

@ -32,40 +32,13 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("EmbyContent")]
public class EmbyContent : Entity
public class EmbyContent : MediaServerContent
{
public string Title { get; set; }
/// <summary>
/// OBSOLETE, Cannot delete due to DB migration issues with SQLite
/// </summary>
[Obsolete("Cannot delete due to DB migration issues with SQLite")]
public string ProviderId { get; set; }
public string EmbyId { get; set; }
public EmbyMediaType Type { get; set; }
public DateTime AddedAt { get; set; }
public string ImdbId { get; set; }
public string TheMovieDbId { get; set; }
public string TvDbId { get; set; }
public string Url { get; set; }
public ICollection<EmbyEpisode> Episodes { get; set; }
[NotMapped]
public bool HasImdb => !string.IsNullOrEmpty(ImdbId);
[NotMapped]
public bool HasTvDb => !string.IsNullOrEmpty(TvDbId);
[NotMapped]
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Emby;
}
public enum EmbyMediaType
{
Movie = 0,
Series = 1,
Music = 2
}
}

@ -26,18 +26,16 @@
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore.Metadata;
using System.Linq;
namespace Ombi.Store.Entities
{
[Table("EmbyEpisode")]
public class EmbyEpisode : Entity
public class EmbyEpisode : MediaServerEpisode
{
public string Title { get; set; }
public string EmbyId { get; set; }
public int EpisodeNumber { get; set; }
public int SeasonNumber { get; set; }
public string ParentId { get; set; }
/// <summary>
/// NOT USED
@ -47,7 +45,23 @@ namespace Ombi.Store.Entities
public string TvDbId { get; set; }
public string ImdbId { get; set; }
public string TheMovieDbId { get; set; }
[NotMapped]
public EmbyContent EmbySeries
{
get => (EmbyContent)Series;
set => Series = value;
}
public override IMediaServerContent SeriesIsIn(ICollection<IMediaServerContent> content)
{
return content.OfType<EmbyContent>().FirstOrDefault(
x => x.EmbyId == this.EmbySeries.EmbyId);
}
public override bool IsIn(IMediaServerContent content)
{
return content.Episodes.Cast<EmbyEpisode>().Any(x => x.EmbyId == this.EmbyId);
}
public EmbyContent Series { get; set; }
}
}

@ -2,7 +2,7 @@
namespace Ombi.Store.Entities
{
public abstract class Entity
public abstract class Entity: IEntity
{
[Key]
public int Id { get; set; }

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace Ombi.Store.Entities
{
public interface IEntity
{
[Key]
public int Id { get; set; }
}
}

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
public interface IMediaServerContent: IEntity
{
public string Title { get; set; }
public string ImdbId { get; set; }
public string TvDbId { get; set; }
public string TheMovieDbId { get; set; }
public MediaType Type { get; set; }
public RecentlyAddedType RecentlyAddedType{ get; }
public string Url { get; set; }
public ICollection<IMediaServerEpisode> Episodes { get; set; }
public DateTime AddedAt { get; set; }
[NotMapped]
public bool HasImdb => !string.IsNullOrEmpty(ImdbId);
[NotMapped]
public bool HasTvDb => !string.IsNullOrEmpty(TvDbId);
[NotMapped]
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
}
public interface IMediaServerEpisode
{
public int EpisodeNumber { get; set; }
public int SeasonNumber { get; set; }
public string Title { get; set; }
/// <summary>
/// The Season key
/// </summary>
/// <value>
/// The parent key.
/// </value>
public IMediaServerContent Series { get; set; }
public IMediaServerContent SeriesIsIn(ICollection<IMediaServerContent> content);
public bool IsIn(IMediaServerContent content);
}
public enum MediaType
{
Movie = 0,
Series = 1,
Music = 2,
Episode = 3
}
}

@ -32,40 +32,14 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("JellyfinContent")]
public class JellyfinContent : Entity
public class JellyfinContent : MediaServerContent
{
public string Title { get; set; }
/// <summary>
/// OBSOLETE, Cannot delete due to DB migration issues with SQLite
/// </summary>
[Obsolete("Cannot delete due to DB migration issues with SQLite")]
public string ProviderId { get; set; }
public string JellyfinId { get; set; }
public JellyfinMediaType Type { get; set; }
public DateTime AddedAt { get; set; }
public string ImdbId { get; set; }
public string TheMovieDbId { get; set; }
public string TvDbId { get; set; }
public string Url { get; set; }
public ICollection<JellyfinEpisode> Episodes { get; set; }
[NotMapped]
public bool HasImdb => !string.IsNullOrEmpty(ImdbId);
[NotMapped]
public bool HasTvDb => !string.IsNullOrEmpty(TvDbId);
[NotMapped]
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Jellyfin;
}
public enum JellyfinMediaType
{
Movie = 0,
Series = 1,
Music = 2
}
}

@ -26,18 +26,17 @@
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Ombi.Store.Entities
{
[Table("JellyfinEpisode")]
public class JellyfinEpisode : Entity
public class JellyfinEpisode : MediaServerEpisode
{
public string Title { get; set; }
public string JellyfinId { get; set; }
public int EpisodeNumber { get; set; }
public int SeasonNumber { get; set; }
public string ParentId { get; set; }
/// <summary>
/// NOT USED
@ -47,7 +46,22 @@ namespace Ombi.Store.Entities
public string TvDbId { get; set; }
public string ImdbId { get; set; }
public string TheMovieDbId { get; set; }
public JellyfinContent Series { get; set; }
[NotMapped]
public JellyfinContent JellyfinSeries
{
get => (JellyfinContent)Series;
set => Series = value;
}
public override IMediaServerContent SeriesIsIn(ICollection<IMediaServerContent> content)
{
return content.OfType<JellyfinContent>().FirstOrDefault(
x => x.JellyfinId == this.JellyfinSeries.JellyfinId);
}
public override bool IsIn(IMediaServerContent content)
{
return content.Episodes.Cast<JellyfinEpisode>().Any(x => x.JellyfinId == this.JellyfinId);
}
}
}

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations.Schema;
using Ombi.Store.Repository;
namespace Ombi.Store.Entities
{
public abstract class MediaServerContent: Entity, IMediaServerContent
{
public string Title { get; set; }
public string ImdbId { get; set; }
public string TvDbId { get; set; }
public string TheMovieDbId { get; set; }
public MediaType Type { get; set; }
public string Url { get; set; }
public ICollection<IMediaServerEpisode> Episodes { get; set; }
public DateTime AddedAt { get; set; }
[NotMapped]
public bool HasImdb => !string.IsNullOrEmpty(ImdbId);
[NotMapped]
public bool HasTvDb => !string.IsNullOrEmpty(TvDbId);
[NotMapped]
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
[NotMapped]
public abstract RecentlyAddedType RecentlyAddedType { get; }
}
public abstract class MediaServerEpisode: Entity, IMediaServerEpisode
{
public int EpisodeNumber { get; set; }
public int SeasonNumber { get; set; }
public string Title { get; set; }
public IMediaServerContent Series { get; set; }
public abstract IMediaServerContent SeriesIsIn(ICollection<IMediaServerContent> content);
public abstract bool IsIn(IMediaServerContent content);
}
}

@ -1,30 +1,38 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
namespace Ombi.Store.Entities
{
[Table("PlexEpisode")]
public class PlexEpisode : Entity
public class PlexEpisode : MediaServerEpisode
{
public int EpisodeNumber { get; set; }
public int SeasonNumber { get; set; }
public int Key { get; set; } // RatingKey
public string Title { get; set; }
/// <summary>
/// The Season key
/// </summary>
/// <value>
/// The parent key.
/// </value>
public int ParentKey { get; set; }
/// <summary>
/// The Series key
/// </summary>
/// <value>
/// The grandparent key.
/// </value>
public int GrandparentKey { get; set; }
[NotMapped]
public PlexServerContent PlexSeries
{
get => (PlexServerContent)Series;
set => Series = value;
}
public override IMediaServerContent SeriesIsIn(ICollection<IMediaServerContent> content)
{
return content.OfType<PlexServerContent>().FirstOrDefault(
x => x.Key == this.PlexSeries.Key);
}
public override bool IsIn(IMediaServerContent content)
{
return content.Episodes.Cast<PlexEpisode>().Any(x => x.Key == this.Key);
}
public PlexServerContent Series { get; set; }
}
}

@ -32,37 +32,20 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("PlexServerContent")]
public class PlexServerContent : Entity
public class PlexServerContent : MediaServerContent
{
public string Title { get; set; }
public string ReleaseYear { get; set; }
public string ImdbId { get; set; }
public string TvDbId { get; set; }
public string TheMovieDbId { get; set; }
public PlexMediaTypeEntity Type { get; set; }
public string Url { get; set; }
public ICollection<PlexEpisode> Episodes { get; set; }
public ICollection<PlexSeasonsContent> Seasons { get; set; }
/// <summary>
/// Plex's internal ID for this item
/// </summary>
public int Key { get; set; }
public DateTime AddedAt { get; set; }
public string Quality { get; set; }
public int? RequestId { get; set; }
[NotMapped]
public bool HasImdb => !string.IsNullOrEmpty(ImdbId);
[NotMapped]
public bool HasTvDb => !string.IsNullOrEmpty(TvDbId);
[NotMapped]
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Plex;
}
[Table("PlexSeasonsContent")]
@ -73,10 +56,4 @@ namespace Ombi.Store.Entities
public int SeasonKey { get; set; }
public int ParentKey { get; set; }
}
public enum PlexMediaTypeEntity
{
Movie = 0,
Show = 1
}
}

@ -35,17 +35,13 @@ using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public class EmbyContentRepository : ExternalRepository<EmbyContent>, IEmbyContentRepository
public class EmbyContentRepository : MediaServerContentRepository<EmbyContent>, IEmbyContentRepository
{
public EmbyContentRepository(ExternalContext db):base(db)
{
Db = db;
}
private ExternalContext Db { get; }
public async Task<EmbyContent> GetByImdbId(string imdbid)
{
return await Db.EmbyContent.FirstOrDefaultAsync(x => x.ImdbId == imdbid);
@ -69,20 +65,20 @@ namespace Ombi.Store.Repository
return await Db.EmbyContent./*Include(x => x.Seasons).*/FirstOrDefaultAsync(x => x.EmbyId == embyId);
}
public async Task Update(EmbyContent existingContent)
public override async Task Update(IMediaServerContent existingContent)
{
Db.EmbyContent.Update(existingContent);
Db.EmbyContent.Update((EmbyContent)existingContent);
await InternalSaveChanges();
}
public IQueryable<EmbyEpisode> GetAllEpisodes()
public override IQueryable<IMediaServerEpisode> GetAllEpisodes()
{
return Db.EmbyEpisode.AsQueryable();
}
public async Task<EmbyEpisode> Add(EmbyEpisode content)
public override async Task<IMediaServerEpisode> Add(IMediaServerEpisode content)
{
await Db.EmbyEpisode.AddAsync(content);
await Db.EmbyEpisode.AddAsync((EmbyEpisode)content);
await InternalSaveChanges();
return content;
}
@ -91,16 +87,17 @@ namespace Ombi.Store.Repository
return await Db.EmbyEpisode.FirstOrDefaultAsync(x => x.EmbyId == key);
}
public async Task AddRange(IEnumerable<EmbyEpisode> content)
public override async Task AddRange(IEnumerable<IMediaServerEpisode> content)
{
Db.EmbyEpisode.AddRange(content);
Db.EmbyEpisode.AddRange((IEnumerable<EmbyEpisode>)content);
await InternalSaveChanges();
}
public void UpdateWithoutSave(EmbyContent existingContent)
public override void UpdateWithoutSave(IMediaServerContent existingContent)
{
Db.EmbyContent.Update(existingContent);
Db.EmbyContent.Update((EmbyContent)existingContent);
}
public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Emby;
}
}

@ -6,19 +6,16 @@ using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface IEmbyContentRepository : IRepository<EmbyContent>
public interface IEmbyContentRepository : IMediaServerContentRepository<EmbyContent>
{
Task<EmbyContent> GetByEmbyId(string embyId);
Task<EmbyEpisode> GetEpisodeByEmbyId(string key);
// TODO: merge these with IJellyfinContentRepository
IQueryable<EmbyContent> Get();
Task<EmbyContent> GetByTheMovieDbId(string mov);
Task<EmbyContent> GetByTvDbId(string tv);
Task<EmbyContent> GetByImdbId(string imdbid);
Task<EmbyContent> GetByEmbyId(string embyId);
Task Update(EmbyContent existingContent);
IQueryable<EmbyEpisode> GetAllEpisodes();
Task<EmbyEpisode> Add(EmbyEpisode content);
Task<EmbyEpisode> GetEpisodeByEmbyId(string key);
Task AddRange(IEnumerable<EmbyEpisode> content);
void UpdateWithoutSave(EmbyContent existingContent);
}
}

@ -9,7 +9,7 @@ using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface IExternalRepository<T> where T : Entity
public interface IExternalRepository<T> where T : IEntity
{
Task<T> Find(object key);
IQueryable<T> GetAll();
@ -25,6 +25,5 @@ namespace Ombi.Store.Repository
where TEntity : class;
Task ExecuteSql(string sql);
DbSet<T> _db { get; }
}
}

@ -6,19 +6,16 @@ using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface IJellyfinContentRepository : IRepository<JellyfinContent>
public interface IJellyfinContentRepository : IMediaServerContentRepository<JellyfinContent>
{
Task<JellyfinContent> GetByJellyfinId(string jellyfinId);
Task<JellyfinEpisode> GetEpisodeByJellyfinId(string key);
// TODO: merge these with IEmbyContentRepository
IQueryable<JellyfinContent> Get();
Task<JellyfinContent> GetByTheMovieDbId(string mov);
Task<JellyfinContent> GetByTvDbId(string tv);
Task<JellyfinContent> GetByImdbId(string imdbid);
Task<JellyfinContent> GetByJellyfinId(string jellyfinId);
Task Update(JellyfinContent existingContent);
IQueryable<JellyfinEpisode> GetAllEpisodes();
Task<JellyfinEpisode> Add(JellyfinEpisode content);
Task<JellyfinEpisode> GetEpisodeByJellyfinId(string key);
Task AddRange(IEnumerable<JellyfinEpisode> content);
void UpdateWithoutSave(JellyfinContent existingContent);
}
}

@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface IMediaServerContentRepository<Content> : IExternalRepository<Content>
where Content : IMediaServerContent
{
RecentlyAddedType RecentlyAddedType{ get; }
Task Update(IMediaServerContent existingContent);
IQueryable<IMediaServerEpisode> GetAllEpisodes();
Task<IMediaServerEpisode> Add(IMediaServerEpisode content);
Task AddRange(IEnumerable<IMediaServerEpisode> content);
void UpdateWithoutSave(IMediaServerContent existingContent);
}
}

@ -8,23 +8,18 @@ using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface IPlexContentRepository : IExternalRepository<PlexServerContent>
public interface IPlexContentRepository : IMediaServerContentRepository<PlexServerContent>
{
Task<bool> ContentExists(string providerId);
Task<PlexServerContent> Get(string providerId, ProviderType type);
Task<PlexServerContent> GetByType(string providerId, ProviderType type, PlexMediaTypeEntity plexType);
Task<PlexServerContent> GetByType(string providerId, ProviderType type, MediaType mediaType);
Task<PlexServerContent> GetByKey(int key);
Task Update(PlexServerContent existingContent);
IQueryable<PlexEpisode> GetAllEpisodes();
Task<PlexEpisode> Add(PlexEpisode content);
Task<PlexEpisode> GetEpisodeByKey(int key);
Task AddRange(IEnumerable<PlexEpisode> content);
IEnumerable<PlexServerContent> GetWhereContentByCustom(Expression<Func<PlexServerContent, bool>> predicate);
Task<PlexServerContent> GetFirstContentByCustom(Expression<Func<PlexServerContent, bool>> predicate);
Task DeleteEpisode(PlexEpisode content);
void DeleteWithoutSave(PlexServerContent content);
void DeleteWithoutSave(PlexEpisode content);
Task UpdateRange(IEnumerable<PlexServerContent> existingContent);
void UpdateWithoutSave(PlexServerContent existingContent);
}
}

@ -10,7 +10,7 @@ using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface IRepository<T> where T : Entity
public interface IRepository<T> where T : IEntity
{
Task<T> Find(object key);
Task<T> Find(object key, CancellationToken cancellationToken);
@ -27,6 +27,5 @@ namespace Ombi.Store.Repository
where TEntity : class;
Task ExecuteSql(string sql);
DbSet<T> _db { get; }
}
}

@ -35,17 +35,13 @@ using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public class JellyfinContentRepository : ExternalRepository<JellyfinContent>, IJellyfinContentRepository
public class JellyfinContentRepository : MediaServerContentRepository<JellyfinContent>, IJellyfinContentRepository
{
public JellyfinContentRepository(ExternalContext db):base(db)
{
Db = db;
}
private ExternalContext Db { get; }
public async Task<JellyfinContent> GetByImdbId(string imdbid)
{
return await Db.JellyfinContent.FirstOrDefaultAsync(x => x.ImdbId == imdbid);
@ -69,20 +65,20 @@ namespace Ombi.Store.Repository
return await Db.JellyfinContent./*Include(x => x.Seasons).*/FirstOrDefaultAsync(x => x.JellyfinId == jellyfinId);
}
public async Task Update(JellyfinContent existingContent)
public override async Task Update(IMediaServerContent existingContent)
{
Db.JellyfinContent.Update(existingContent);
Db.JellyfinContent.Update((JellyfinContent)existingContent);
await InternalSaveChanges();
}
public IQueryable<JellyfinEpisode> GetAllEpisodes()
public override IQueryable<IMediaServerEpisode> GetAllEpisodes()
{
return Db.JellyfinEpisode.AsQueryable();
}
public async Task<JellyfinEpisode> Add(JellyfinEpisode content)
public override async Task<IMediaServerEpisode> Add(IMediaServerEpisode content)
{
await Db.JellyfinEpisode.AddAsync(content);
await Db.JellyfinEpisode.AddAsync((JellyfinEpisode)content);
await InternalSaveChanges();
return content;
}
@ -91,16 +87,17 @@ namespace Ombi.Store.Repository
return await Db.JellyfinEpisode.FirstOrDefaultAsync(x => x.JellyfinId == key);
}
public async Task AddRange(IEnumerable<JellyfinEpisode> content)
public override async Task AddRange(IEnumerable<IMediaServerEpisode> content)
{
Db.JellyfinEpisode.AddRange(content);
Db.JellyfinEpisode.AddRange((IEnumerable<JellyfinEpisode>)content);
await InternalSaveChanges();
}
public void UpdateWithoutSave(JellyfinContent existingContent)
public override void UpdateWithoutSave(IMediaServerContent existingContent)
{
Db.JellyfinContent.Update(existingContent);
Db.JellyfinContent.Update((JellyfinContent)existingContent);
}
public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Jellyfin;
}
}

@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Store.Context;
using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public abstract class MediaServerContentRepository<T> : ExternalRepository<T>, IMediaServerContentRepository<T> where T : MediaServerContent
{
protected ExternalContext Db { get; }
public abstract RecentlyAddedType RecentlyAddedType { get; }
public MediaServerContentRepository(ExternalContext db) : base(db)
{
Db = db;
}
public abstract Task Update(IMediaServerContent existingContent);
public abstract IQueryable<IMediaServerEpisode> GetAllEpisodes();
public abstract Task<IMediaServerEpisode> Add(IMediaServerEpisode content);
public abstract Task AddRange(IEnumerable<IMediaServerEpisode> content);
public abstract void UpdateWithoutSave(IMediaServerContent existingContent);
}
}

@ -37,17 +37,13 @@ using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public class PlexServerContentRepository : ExternalRepository<PlexServerContent>, IPlexContentRepository
public class PlexServerContentRepository : MediaServerContentRepository<PlexServerContent>, IPlexContentRepository
{
public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Plex;
public PlexServerContentRepository(ExternalContext db) : base(db)
{
Db = db;
}
private ExternalContext Db { get; }
public async Task<bool> ContentExists(string providerId)
{
var any = await Db.PlexServerContent.AnyAsync(x => x.ImdbId == providerId);
@ -79,16 +75,16 @@ namespace Ombi.Store.Repository
return null;
}
public async Task<PlexServerContent> GetByType(string providerId, ProviderType type, PlexMediaTypeEntity plexType)
public async Task<PlexServerContent> GetByType(string providerId, ProviderType type, MediaType mediaType)
{
switch (type)
{
case ProviderType.ImdbId:
return await Db.PlexServerContent.FirstOrDefaultAsync(x => x.ImdbId == providerId && x.Type == plexType);
return await Db.PlexServerContent.FirstOrDefaultAsync(x => x.ImdbId == providerId && x.Type == mediaType);
case ProviderType.TheMovieDbId:
return await Db.PlexServerContent.FirstOrDefaultAsync(x => x.TheMovieDbId == providerId && x.Type == plexType);
return await Db.PlexServerContent.FirstOrDefaultAsync(x => x.TheMovieDbId == providerId && x.Type == mediaType);
case ProviderType.TvDbId:
return await Db.PlexServerContent.FirstOrDefaultAsync(x => x.TvDbId == providerId && x.Type == plexType);
return await Db.PlexServerContent.FirstOrDefaultAsync(x => x.TvDbId == providerId && x.Type == mediaType);
default:
break;
}
@ -114,14 +110,14 @@ namespace Ombi.Store.Repository
.FirstOrDefaultAsync(predicate);
}
public async Task Update(PlexServerContent existingContent)
public override async Task Update(IMediaServerContent existingContent)
{
Db.PlexServerContent.Update(existingContent);
Db.PlexServerContent.Update((PlexServerContent)existingContent);
await InternalSaveChanges();
}
public void UpdateWithoutSave(PlexServerContent existingContent)
public override void UpdateWithoutSave(IMediaServerContent existingContent)
{
Db.PlexServerContent.Update(existingContent);
Db.PlexServerContent.Update((PlexServerContent)existingContent);
}
public async Task UpdateRange(IEnumerable<PlexServerContent> existingContent)
@ -130,7 +126,7 @@ namespace Ombi.Store.Repository
await InternalSaveChanges();
}
public IQueryable<PlexEpisode> GetAllEpisodes()
public override IQueryable<IMediaServerEpisode> GetAllEpisodes()
{
return Db.PlexEpisode.Include(x => x.Series).AsQueryable();
}
@ -145,9 +141,9 @@ namespace Ombi.Store.Repository
Db.PlexEpisode.Remove(content);
}
public async Task<PlexEpisode> Add(PlexEpisode content)
public override async Task<IMediaServerEpisode> Add(IMediaServerEpisode content)
{
await Db.PlexEpisode.AddAsync(content);
await Db.PlexEpisode.AddAsync((PlexEpisode)content);
await InternalSaveChanges();
return content;
}
@ -162,10 +158,11 @@ namespace Ombi.Store.Repository
{
return await Db.PlexEpisode.FirstOrDefaultAsync(x => x.Key == key);
}
public async Task AddRange(IEnumerable<PlexEpisode> content)
public override async Task AddRange(IEnumerable<IMediaServerEpisode> content)
{
Db.PlexEpisode.AddRange(content);
Db.PlexEpisode.AddRange((IEnumerable<PlexEpisode>)content);
await InternalSaveChanges();
}
}
}
Loading…
Cancel
Save