diff --git a/CHANGELOG.md b/CHANGELOG.md index ea279eae7..8bfe7e633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,62 @@ # Changelog +## (unreleased) + +### **New Features** + +- Added the ability to send newsletter out to users that are not in Ombi. [Jamie] + +- Added the ability to turn off TV or Movies from the newsletter. [Jamie] + +- Update about.component.html. [Jamie] + +- Update about.component.html. [Jamie] + +- Added random versioning prefix to the translations so the users don't have to clear the cache. [Jamie] + +- Added more information to the about page. [Jamie] + +- Changed let to const to adhere to linting. [Anojh] + +- Update _Layout.cshtml. [goldenpipes] + +- Update _Layout.cshtml. [goldenpipes] + +- Changed the TV Request API. We now only require the TvDbId and the seasons and episodes that you want to request. This should make integration regarding TV a lot easier. [Jamie] + +### **Fixes** + +- Emby improvments on the way we sync/cache the data. [Jamie] + +- Memory improvements. [Jamie] + +- Made some improvements to the Sonarr Sync job #2127. [Jamie] + +- Turn off Server GC to hopefully help with #2127. [Jamie Rees] + +- Fixed #2109. [Jamie] + +- Fixed #2101. [Jamie] + +- Fixed #2105. [Jamie] + +- Fixed some styling on the issues detail page. [Jamie] + +- Fixed #2116. [Jamie] + +- Limit the amount of FileSystemWatchers being spawned. [Jamie] + +- Fixed the issue where Emby connect users could not log in #2115. [Jamie] + +- Had to update some base styles since currently some styling does not look right... [Anojh] + +- Adding wrappers and classes for LC and toggling active style for UI elements. [Anojh] + +- Fixed a little bug in the newsletter. [Jamie] + +- Fixed the issue where movies were not appearing in the newsletter for users with Emby #2111. [Jamie] + + ## v3.0.3111 (2018-03-27) ### **New Features** @@ -732,7 +789,7 @@ - Switch to use a single HTTPClient rather than a new one every request !dev. [tidusjar] -- Fix non-admin rights (#1820) [Rob Gkemeijer] +- Fix non-admin rights (#1820) [Rob Gökemeijer] - Fix duplicated "Requests" element ID on new Issues link (#1817) [Shoghi Cervantes] @@ -2582,7 +2639,7 @@ - WIP hide tv request options based on admin settings. [Matt McHughes] -- Set meta charset to be utf-8. [Madeleine Schnemann] +- Set meta charset to be utf-8. [Madeleine Schönemann] - F#552: updated labels text. [Jim MacKenize] @@ -2939,7 +2996,7 @@ - Fixed issues from the merge. [tidusjar] -- Stupid &$(* merge. [tidusjar] +- Stupid &$(*£ merge. [tidusjar] - Angular. [tidusjar] diff --git a/appveyor.yml b/appveyor.yml index c513c650e..9d97046d5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ version: 3.0.{build} configuration: Release os: Visual Studio 2017 environment: - nodejs_version: "7.8.0" + nodejs_version: "Current" install: # Get the latest stable version of Node.js or io.js diff --git a/src/Ombi.Api.Emby/EmbyApi.cs b/src/Ombi.Api.Emby/EmbyApi.cs index 126b3c700..3af6d0dd5 100644 --- a/src/Ombi.Api.Emby/EmbyApi.cs +++ b/src/Ombi.Api.Emby/EmbyApi.cs @@ -77,13 +77,19 @@ namespace Ombi.Api.Emby request.AddJsonBody(body); - request.AddHeader("Accept", "application/json"); - request.AddContentHeader("Content-Type", "application/json"); + AddEmbyHeaders(request); var obj = await Api.Request(request); return obj; } + private static void AddEmbyHeaders(Request request) + { + request.AddHeader("Accept", "application/json"); + request.AddHeader("X-Application", $"Ombi/{AssemblyHelper.GetRuntimeVersion()}"); + request.AddContentHeader("Content-Type", "application/json"); + } + public async Task> GetCollection(string mediaId, string apiKey, string userId, string baseUrl) { var request = new Request($"emby/users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get); diff --git a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs index a7f20ac40..0633d641e 100644 --- a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs @@ -25,7 +25,7 @@ namespace Ombi.Core.Tests.Rule.Search [Test] public async Task Movie_ShouldBe_Available_WhenFoundInEmby() { - ContextMock.Setup(x => x.Get(It.IsAny())).ReturnsAsync(new EmbyContent + ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new EmbyContent { ProviderId = "123" }); @@ -39,7 +39,7 @@ namespace Ombi.Core.Tests.Rule.Search [Test] public async Task Movie_ShouldBe_NotAvailable_WhenNotFoundInEmby() { - ContextMock.Setup(x => x.Get(It.IsAny())).Returns(Task.FromResult(default(EmbyContent))); + ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).Returns(Task.FromResult(default(EmbyContent))); var search = new SearchMovieViewModel(); var result = await Rule.Execute(search); diff --git a/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs index 30f415aad..28eb066d4 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Store.Entities.Requests; @@ -9,7 +10,7 @@ namespace Ombi.Core.Engine.Interfaces { Task RemoveTvRequest(int requestId); - Task RequestTvShow(SearchTvShowViewModel tv); + Task RequestTvShow(TvRequestViewModel tv); Task DenyChildRequest(int requestId); Task> SearchTvRequest(string search); Task>>> SearchTvRequestTree(string search); diff --git a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs index 59be359f8..8782ea028 100644 --- a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs +++ b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs @@ -152,7 +152,9 @@ namespace Ombi.Core.Engine model.Add(new RecentlyAddedMovieModel { Id = emby.Id, - ImdbId = emby.ProviderId, + ImdbId = emby.ImdbId, + TheMovieDbId = emby.TheMovieDbId, + TvDbId = emby.TvDbId, AddedAt = emby.AddedAt, Title = emby.Title, }); @@ -211,7 +213,9 @@ namespace Ombi.Core.Engine model.Add(new RecentlyAddedTvModel { Id = emby.Id, - ImdbId = emby.ProviderId, + ImdbId = emby.ImdbId, + TvDbId = emby.TvDbId, + TheMovieDbId = emby.TheMovieDbId, AddedAt = emby.AddedAt, Title = emby.Title, EpisodeNumber = episode.EpisodeNumber, diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index f8fc33e9b..aaa2d353d 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -43,13 +43,13 @@ namespace Ombi.Core.Engine private IAuditRepository Audit { get; } private readonly IRepository _requestLog; - public async Task RequestTvShow(SearchTvShowViewModel tv) + public async Task RequestTvShow(TvRequestViewModel tv) { var user = await GetUser(); var tvBuilder = new TvShowRequestBuilder(TvApi); (await tvBuilder - .GetShowInfo(tv.Id)) + .GetShowInfo(tv.TvDbId)) .CreateTvList(tv) .CreateChild(tv, user.Id); @@ -78,9 +78,9 @@ namespace Ombi.Core.Engine } } - await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tv.Title}", Username); + await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username); - var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.Id); + var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId); if (existingRequest != null) { // Remove requests we already have, we just want new ones @@ -127,7 +127,7 @@ namespace Ombi.Core.Engine var newRequest = tvBuilder.CreateNewRequest(tv); return await AddRequest(newRequest.NewRequest); } - + public async Task> GetRequests(int count, int position) { var shouldHide = await HideFromOtherUsers(); diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs index 0d5d46c54..1f92536b8 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Ombi.Api.TvMaze; using Ombi.Api.TvMaze.Models; +using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Helpers; using Ombi.Store.Entities; @@ -23,7 +24,7 @@ namespace Ombi.Core.Helpers private ITvMazeApi TvApi { get; } public ChildRequests ChildRequest { get; set; } - public List TvRequests { get; protected set; } + public List TvRequests { get; protected set; } public string PosterPath { get; protected set; } public DateTime FirstAir { get; protected set; } public TvRequests NewRequest { get; protected set; } @@ -33,7 +34,7 @@ namespace Ombi.Core.Helpers { ShowInfo = await TvApi.ShowLookupByTheTvDbId(id); - DateTime.TryParse(ShowInfo.premiered, out DateTime dt); + DateTime.TryParse(ShowInfo.premiered, out var dt); FirstAir = dt; @@ -43,37 +44,29 @@ namespace Ombi.Core.Helpers return this; } - public TvShowRequestBuilder CreateChild(SearchTvShowViewModel model, string userId) + public TvShowRequestBuilder CreateChild(TvRequestViewModel model, string userId) { ChildRequest = new ChildRequests { - Id = model.Id, + Id = model.TvDbId, RequestType = RequestType.TvShow, RequestedDate = DateTime.UtcNow, Approved = false, RequestedUserId = userId, SeasonRequests = new List(), - Title = model.Title, + Title = ShowInfo.name, SeriesType = ShowInfo.type.Equals("Animation", StringComparison.CurrentCultureIgnoreCase) ? SeriesType.Anime : SeriesType.Standard }; return this; } - public TvShowRequestBuilder CreateTvList(SearchTvShowViewModel tv) + public TvShowRequestBuilder CreateTvList(TvRequestViewModel tv) { - TvRequests = new List(); + TvRequests = new List(); // Only have the TV requests we actually requested and not everything - foreach (var season in tv.SeasonRequests) + foreach (var season in tv.Seasons) { - for (int i = season.Episodes.Count - 1; i >= 0; i--) - { - if (!season.Episodes[i].Requested) - { - season.Episodes.RemoveAt(i); // Remove the episode since it's not requested - } - } - if (season.Episodes.Any()) { TvRequests.Add(season); @@ -81,11 +74,10 @@ namespace Ombi.Core.Helpers } return this; - } - public async Task BuildEpisodes(SearchTvShowViewModel tv) + public async Task BuildEpisodes(TvRequestViewModel tv) { if (tv.RequestAll) { @@ -173,26 +165,68 @@ namespace Ombi.Core.Helpers else { // It's a custom request - ChildRequest.SeasonRequests = TvRequests; + var seasonRequests = new List(); + var episodes = await TvApi.EpisodeLookup(ShowInfo.id); + foreach (var ep in episodes) + { + var existingSeasonRequest = seasonRequests.FirstOrDefault(x => x.SeasonNumber == ep.season); + if (existingSeasonRequest != null) + { + var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season); + var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.number) ?? false; + if (requestedSeason != null && requestedEpisode) + { + // We already have this, let's just add the episodes to it + existingSeasonRequest.Episodes.Add(new EpisodeRequests + { + EpisodeNumber = ep.number, + AirDate = FormatDate(ep.airdate), + Title = ep.name, + Url = ep.url, + }); + } + } + else + { + var newRequest = new SeasonRequests {SeasonNumber = ep.season}; + var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season); + var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.number) ?? false; + if (requestedSeason != null && requestedEpisode) + { + newRequest.Episodes.Add(new EpisodeRequests + { + EpisodeNumber = ep.number, + AirDate = FormatDate(ep.airdate), + Title = ep.name, + Url = ep.url, + }); + seasonRequests.Add(newRequest); + } + } + } + + foreach (var s in seasonRequests) + { + ChildRequest.SeasonRequests.Add(s); + } } return this; } - public TvShowRequestBuilder CreateNewRequest(SearchTvShowViewModel tv) + public TvShowRequestBuilder CreateNewRequest(TvRequestViewModel tv) { NewRequest = new TvRequests { - Id = tv.Id, Overview = ShowInfo.summary.RemoveHtml(), PosterPath = PosterPath, Title = ShowInfo.name, ReleaseDate = FirstAir, Status = ShowInfo.status, ImdbId = ShowInfo.externals?.imdb ?? string.Empty, - TvDbId = tv.Id, + TvDbId = tv.TvDbId, ChildRequests = new List(), - TotalSeasons = tv.SeasonRequests.Count() + TotalSeasons = tv.Seasons.Count() }; NewRequest.ChildRequests.Add(ChildRequest); diff --git a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs new file mode 100644 index 000000000..78f9edd6d --- /dev/null +++ b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Ombi.Core.Models.Requests +{ + public class TvRequestViewModel + { + public bool RequestAll { get; set; } + public bool LatestSeason { get; set; } + public bool FirstSeason { get; set; } + public int TvDbId { get; set; } + public List Seasons { get; set; } = new List(); + } + + public class SeasonsViewModel + { + public int SeasonNumber { get; set; } + public List Episodes { get; set; } = new List(); + } + + public class EpisodesViewModel + { + public int EpisodeNumber { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs index 74b537352..8ac96701d 100644 --- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs @@ -23,20 +23,20 @@ namespace Ombi.Core.Rule.Rules.Search EmbyContent item = null; if (obj.ImdbId.HasValue()) { - item = await EmbyContentRepository.Get(obj.ImdbId); + item = await EmbyContentRepository.GetByImdbId(obj.ImdbId); } if (item == null) { if (obj.TheMovieDbId.HasValue()) { - item = await EmbyContentRepository.Get(obj.TheMovieDbId); + item = await EmbyContentRepository.GetByTheMovieDbId(obj.TheMovieDbId); } if (item == null) { if (obj.TheTvDbId.HasValue()) { - item = await EmbyContentRepository.Get(obj.TheTvDbId); + item = await EmbyContentRepository.GetByTvDbId(obj.TheTvDbId); } } } diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index 8c18fba4c..fb5dd976b 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -29,11 +29,19 @@ namespace Ombi.Notifications ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; RequestedUser = req?.RequestedUser?.UserName; - UserName = req?.RequestedUser?.UserName; + if (UserName.IsNullOrEmpty()) + { + // Can be set if it's an issue + UserName = req?.RequestedUser?.UserName; + } + Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName; Title = title; RequestedDate = req?.RequestedDate.ToString("D"); - Type = req?.RequestType.ToString(); + if (Type.IsNullOrEmpty()) + { + Type = req?.RequestType.ToString(); + } Overview = req?.Overview; Year = req?.ReleaseDate.Year.ToString(); PosterImage = req?.RequestType == RequestType.Movie ? @@ -65,11 +73,19 @@ namespace Ombi.Notifications ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; RequestedUser = req?.RequestedUser?.UserName; - UserName = req?.RequestedUser?.UserName; + if (UserName.IsNullOrEmpty()) + { + // Can be set if it's an issue + UserName = req?.RequestedUser?.UserName; + } Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName; Title = title; RequestedDate = req?.RequestedDate.ToString("D"); - Type = req?.RequestType.ToString(); + if (Type.IsNullOrEmpty()) + { + Type = req?.RequestType.ToString(); + } + Overview = req?.ParentRequest.Overview; Year = req?.ParentRequest.ReleaseDate.Year.ToString(); PosterImage = req?.RequestType == RequestType.Movie ? @@ -128,6 +144,7 @@ namespace Ombi.Notifications IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty; NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty; UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty; + Type = opts.Substitutes.TryGetValue("RequestType", out val) ? val : string.Empty; } // User Defined diff --git a/src/Ombi.Schedule/JobSetup.cs b/src/Ombi.Schedule/JobSetup.cs index 85b842d27..b328e6daf 100644 --- a/src/Ombi.Schedule/JobSetup.cs +++ b/src/Ombi.Schedule/JobSetup.cs @@ -1,4 +1,5 @@ -using Hangfire; +using System; +using Hangfire; using Ombi.Core.Settings; using Ombi.Schedule.Jobs; using Ombi.Schedule.Jobs.Couchpotato; @@ -12,7 +13,7 @@ using Ombi.Settings.Settings.Models; namespace Ombi.Schedule { - public class JobSetup : IJobSetup + public class JobSetup : IJobSetup, IDisposable { public JobSetup(IPlexContentSync plexContentSync, IRadarrSync radarrSync, IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter, @@ -65,5 +66,36 @@ namespace Ombi.Schedule RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s)); RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s)); } + + + private bool _disposed; + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _plexContentSync?.Dispose(); + _radarrSync?.Dispose(); + _updater?.Dispose(); + _plexUserImporter?.Dispose(); + _embyContentSync?.Dispose(); + _embyUserImporter?.Dispose(); + _sonarrSync?.Dispose(); + _cpCache?.Dispose(); + _srSync?.Dispose(); + _jobSettings?.Dispose(); + _refreshMetadata?.Dispose(); + _newsletter?.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs index af5f58cb4..59b5adc96 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs @@ -70,7 +70,16 @@ namespace Ombi.Schedule.Jobs.Emby foreach (var movie in movies) { - var embyContent = await _repo.Get(movie.ImdbId); + EmbyContent embyContent = null; + if (movie.TheMovieDbId > 0) + { + embyContent = await _repo.GetByTheMovieDbId(movie.TheMovieDbId.ToString()); + } + else if(movie.ImdbId.HasValue()) + { + embyContent = await _repo.GetByImdbId(movie.ImdbId); + } + if (embyContent == null) { // We don't have this yet @@ -112,8 +121,20 @@ namespace Ombi.Schedule.Jobs.Emby foreach (var child in tv) { - var tvDbId = child.ParentRequest.TvDbId; - var seriesEpisodes = embyEpisodes.Where(x => x.Series.ProviderId == tvDbId.ToString()); + IQueryable seriesEpisodes; + if (child.ParentRequest.TvDbId > 0) + { + seriesEpisodes = embyEpisodes.Where(x => x.Series.TvDbId == child.ParentRequest.TvDbId.ToString()); + } + else if(child.ParentRequest.ImdbId.HasValue()) + { + seriesEpisodes = embyEpisodes.Where(x => x.Series.ImdbId == child.ParentRequest.ImdbId); + } + else + { + continue; + } + foreach (var season in child.SeasonRequests) { foreach (var episode in season.Episodes) diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs index 9d054ea7c..99d4f5a85 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs @@ -38,12 +38,21 @@ namespace Ombi.Schedule.Jobs.Emby public async Task Start() { - var embySettings = await _settings.GetSettingsAsync(); + var embySettings = await _settings.GetSettingsAsync(); if (!embySettings.Enable) return; foreach (var server in embySettings.Servers) - await StartServerCache(server); + { + try + { + await StartServerCache(server); + } + catch (Exception e) + { + _logger.LogError(e, "Exception when caching Emby for server {0}", server.Name); + } + } // Episodes BackgroundJob.Enqueue(() => _episodeSync.Start()); @@ -55,8 +64,11 @@ namespace Ombi.Schedule.Jobs.Emby if (!ValidateSettings(server)) return; + await _repo.ExecuteSql("DELETE FROM EmbyEpisode"); + await _repo.ExecuteSql("DELETE FROM EmbyContent"); + var movies = await _api.GetAllMovies(server.ApiKey, server.AdministratorId, server.FullUri); - var mediaToAdd = new List(); + var mediaToAdd = new HashSet(); foreach (var movie in movies.Items) { if (movie.Type.Equals("boxset", StringComparison.CurrentCultureIgnoreCase)) @@ -96,7 +108,9 @@ namespace Ombi.Schedule.Jobs.Emby if (existingTv == null) mediaToAdd.Add(new EmbyContent { - ProviderId = tvInfo.ProviderIds.Tvdb, + TvDbId = tvInfo.ProviderIds?.Tvdb, + ImdbId = tvInfo.ProviderIds?.Imdb, + TheMovieDbId = tvInfo.ProviderIds?.Tmdb, Title = tvInfo.Name, Type = EmbyMediaType.Series, EmbyId = tvShow.Id, @@ -110,18 +124,14 @@ namespace Ombi.Schedule.Jobs.Emby private async Task ProcessMovies(MovieInformation movieInfo, ICollection content) { - if (string.IsNullOrEmpty(movieInfo.ProviderIds.Imdb)) - { - Log.Error("Provider Id on movie {0} is null", movieInfo.Name); - return; - } // Check if it exists var existingMovie = await _repo.GetByEmbyId(movieInfo.Id); if (existingMovie == null) content.Add(new EmbyContent { - ProviderId = movieInfo.ProviderIds.Imdb, + ImdbId = movieInfo.ProviderIds.Imdb, + TheMovieDbId = movieInfo.ProviderIds?.Tmdb, Title = movieInfo.Name, Type = EmbyMediaType.Movie, EmbyId = movieInfo.Id, diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs index 749abd761..df00a37e6 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs @@ -85,10 +85,10 @@ namespace Ombi.Schedule.Jobs.Emby } var epInfo = await _api.GetEpisodeInformation(ep.Id, server.ApiKey, server.AdministratorId, server.FullUri); - if (epInfo?.ProviderIds?.Tvdb == null) - { - continue; - } + //if (epInfo?.ProviderIds?.Tvdb == null) + //{ + // continue; + //} // Let's make sure we have the parent request, stop those pesky forign key errors, // Damn me having data integrity @@ -109,7 +109,9 @@ namespace Ombi.Schedule.Jobs.Emby EpisodeNumber = ep.IndexNumber, SeasonNumber = ep.ParentIndexNumber, ParentId = ep.SeriesId, - ProviderId = epInfo.ProviderIds.Tvdb, + TvDbId = epInfo.ProviderIds.Tvdb, + TheMovieDbId = epInfo.ProviderIds.Tmdb, + ImdbId = epInfo.ProviderIds.Imdb, Title = ep.Name, AddedAt = DateTime.UtcNow }); diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index c780509b8..1a8b21fbd 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using MailKit; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Ombi.Api.TheMovieDb; @@ -100,18 +101,18 @@ namespace Ombi.Schedule.Jobs.Ombi var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10); var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10); var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10); - body = await BuildHtml(plexm, embym, plext, embyt); + body = await BuildHtml(plexm, embym, plext, embyt, settings); } else { - body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend); + body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); if (body.IsNullOrEmpty()) { return; } } - + if (!test) { // Get the users to send it to @@ -120,6 +121,15 @@ namespace Ombi.Schedule.Jobs.Ombi { return; } + + foreach (var emails in settings.ExternalEmails) + { + users.Add(new OmbiUser + { + UserName = emails, + Email = emails + }); + } var emailTasks = new List(); foreach (var user in users) { @@ -229,20 +239,20 @@ namespace Ombi.Schedule.Jobs.Ombi return resolver.ParseMessage(template, curlys); } - private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, IQueryable plexEpisodes, IQueryable embyEp) + private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, IQueryable plexEpisodes, IQueryable embyEp, NewsletterSettings settings) { var sb = new StringBuilder(); var plexMovies = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie); var embyMovies = embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie); - if (plexMovies.Any() || embyMovies.Any()) + if ((plexMovies.Any() || embyMovies.Any()) && !settings.DisableMovies) { sb.Append("

New Movies:



"); await ProcessPlexMovies(plexMovies, sb); await ProcessEmbyMovies(embyMovies, sb); } - if (plexEpisodes.Any() || embyEp.Any()) + if ((plexEpisodes.Any() || embyEp.Any()) && !settings.DisableTv) { sb.Append("

New Episodes:



"); await ProcessPlexTv(plexEpisodes, sb); @@ -259,19 +269,11 @@ namespace Ombi.Schedule.Jobs.Ombi var ordered = plexContentToSend.OrderByDescending(x => x.AddedAt); foreach (var content in ordered) { - if (content.TheMovieDbId.IsNullOrEmpty()) + int.TryParse(content.TheMovieDbId, out var movieDbId); + if (movieDbId <= 0) { - // Maybe we should try the ImdbId? - if (content.ImdbId.HasValue()) - { - var findResult = await _movieApi.Find(content.ImdbId, ExternalSource.imdb_id); - - var movieId = findResult.movie_results?[0]?.id ?? 0; - content.TheMovieDbId = movieId.ToString(); - } + continue; } - - int.TryParse(content.TheMovieDbId, out var movieDbId); var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId); if (info == null) { @@ -300,8 +302,21 @@ namespace Ombi.Schedule.Jobs.Ombi var ordered = embyContent.OrderByDescending(x => x.AddedAt); foreach (var content in ordered) { - int.TryParse(content.ProviderId, out var movieDbId); - var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId); + var theMovieDbId = content.TheMovieDbId; + if (!content.TheMovieDbId.HasValue()) + { + var imdbId = content.ImdbId; + var findResult = await _movieApi.Find(imdbId, ExternalSource.imdb_id); + var result = findResult.movie_results?.FirstOrDefault(); + if (result == null) + { + continue; + } + + theMovieDbId = result.id.ToString(); + } + + var info = await _movieApi.GetMovieInformationWithExtraInfo(int.Parse(theMovieDbId)); if (info == null) { continue; @@ -505,7 +520,11 @@ namespace Ombi.Schedule.Jobs.Ombi { try { - int.TryParse(t.ProviderId, out var tvdbId); + if (!t.TvDbId.HasValue()) + { + continue; + } + int.TryParse(t.TvDbId, out var tvdbId); var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId); if (info == null) { diff --git a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs index 225efb7d3..9d073facf 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs @@ -10,7 +10,6 @@ using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.Store.Entities; using Ombi.Store.Repository; -using Ombi.Store.Repository.Requests; namespace Ombi.Schedule.Jobs.Ombi { @@ -44,6 +43,7 @@ namespace Ombi.Schedule.Jobs.Ombi if (settings.Enable) { await StartPlex(); + await StartEmby(); } } catch (Exception e) @@ -61,6 +61,12 @@ namespace Ombi.Schedule.Jobs.Ombi await StartPlexTv(); } + private async Task StartEmby() + { + await StartEmbyMovies(); + await StartEmbyTv(); + } + private async Task StartPlexTv() { var allTv = _plexRepo.GetAll().Where(x => @@ -101,6 +107,46 @@ namespace Ombi.Schedule.Jobs.Ombi await _plexRepo.SaveChangesAsync(); } + private async Task StartEmbyTv() + { + var allTv = _embyRepo.GetAll().Where(x => + x.Type == EmbyMediaType.Series && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue() || !x.TvDbId.HasValue())); + var tvCount = 0; + foreach (var show in allTv) + { + var hasImdb = show.ImdbId.HasValue(); + var hasTheMovieDb = show.TheMovieDbId.HasValue(); + var hasTvDbId = show.TvDbId.HasValue(); + + if (!hasTheMovieDb) + { + var id = await GetTheMovieDbId(hasTvDbId, hasImdb, show.TvDbId, show.ImdbId, show.Title); + show.TheMovieDbId = id; + } + + if (!hasImdb) + { + var id = await GetImdbId(hasTheMovieDb, hasTvDbId, show.Title, show.TheMovieDbId, show.TvDbId); + show.ImdbId = id; + _embyRepo.UpdateWithoutSave(show); + } + + if (!hasTvDbId) + { + var id = await GetTvDbId(hasTheMovieDb, hasImdb, show.TheMovieDbId, show.ImdbId, show.Title); + show.TvDbId = id; + _embyRepo.UpdateWithoutSave(show); + } + tvCount++; + if (tvCount >= 20) + { + await _embyRepo.SaveChangesAsync(); + tvCount = 0; + } + } + await _embyRepo.SaveChangesAsync(); + } + private async Task StartPlexMovies() { var allMovies = _plexRepo.GetAll().Where(x => @@ -135,7 +181,41 @@ namespace Ombi.Schedule.Jobs.Ombi await _plexRepo.SaveChangesAsync(); } - private async Task GetTheMovieDbId(bool hasTvDbId, bool hasImdb, string tvdbID, string imdbId, string title) + private async Task StartEmbyMovies() + { + 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(); + // Movies don't really use TheTvDb + + if (!hasImdb) + { + var imdbId = await GetImdbId(hasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty); + movie.ImdbId = imdbId; + _embyRepo.UpdateWithoutSave(movie); + } + if (!hasTheMovieDb) + { + var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title); + movie.TheMovieDbId = id; + _embyRepo.UpdateWithoutSave(movie); + } + movieCount++; + if (movieCount >= 20) + { + await _embyRepo.SaveChangesAsync(); + movieCount = 0; + } + } + + await _embyRepo.SaveChangesAsync(); + } + + private async Task GetTheMovieDbId(bool hasTvDbId, bool hasImdb, string tvdbID, string imdbId, string title) { _log.LogInformation("The Media item {0} does not have a TheMovieDbId, searching for TheMovieDbId", title); FindResult result = null; diff --git a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs index 8e13d6f9e..5ee55d167 100644 --- a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -43,14 +45,15 @@ namespace Ombi.Schedule.Jobs.Sonarr var series = await _api.GetSeries(settings.ApiKey, settings.FullUri); if (series != null) { - var sonarrSeries = series as IList ?? series.ToList(); + var sonarrSeries = series as ImmutableHashSet ?? series.ToImmutableHashSet(); var ids = sonarrSeries.Select(x => x.tvdbId); await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache"); - var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToList(); + var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToImmutableHashSet(); await _ctx.SonarrCache.AddRangeAsync(entites); - + entites.Clear(); + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache"); foreach (var s in sonarrSeries) { @@ -67,10 +70,10 @@ namespace Ombi.Schedule.Jobs.Sonarr TvDbId = s.tvdbId, HasFile = episode.hasFile })); + _log.LogDebug("Commiting the transaction"); + await _ctx.SaveChangesAsync(); } - _log.LogDebug("Commiting the transaction"); - await _ctx.SaveChangesAsync(); } } catch (Exception e) diff --git a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs index ea60d932c..f3053876b 100644 --- a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs +++ b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs @@ -46,8 +46,7 @@ namespace Ombi.Schedule.Processor if (masterBranch) { latestRelease = doc.DocumentNode.Descendants("h2") - .FirstOrDefault(x => x.InnerText == "(unreleased)"); - // TODO: Change this to InnterText != "(unreleased)" once we go live and it's not a prerelease + .FirstOrDefault(x => x.InnerText != "(unreleased)"); } else { diff --git a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs index 380e2d743..f043cd74c 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs @@ -1,7 +1,12 @@ -namespace Ombi.Settings.Settings.Models.Notifications +using System.Collections.Generic; + +namespace Ombi.Settings.Settings.Models.Notifications { public class NewsletterSettings : Settings { + public bool DisableTv { get; set; } + public bool DisableMovies { get; set; } public bool Enabled { get; set; } + public List ExternalEmails { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/EmbyContent.cs b/src/Ombi.Store/Entities/EmbyContent.cs index d9d6e6983..1d3f57f13 100644 --- a/src/Ombi.Store/Entities/EmbyContent.cs +++ b/src/Ombi.Store/Entities/EmbyContent.cs @@ -36,11 +36,18 @@ namespace Ombi.Store.Entities { public string Title { get; set; } + /// + /// 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 ICollection Episodes { get; set; } } diff --git a/src/Ombi.Store/Entities/EmbyEpisode.cs b/src/Ombi.Store/Entities/EmbyEpisode.cs index 150829240..e4e5b6a4b 100644 --- a/src/Ombi.Store/Entities/EmbyEpisode.cs +++ b/src/Ombi.Store/Entities/EmbyEpisode.cs @@ -39,8 +39,14 @@ namespace Ombi.Store.Entities public int EpisodeNumber { get; set; } public int SeasonNumber { get; set; } public string ParentId { get; set; } + /// + /// NOT USED + /// public string ProviderId { get; set; } public DateTime AddedAt { get; set; } + public string TvDbId { get; set; } + public string ImdbId { get; set; } + public string TheMovieDbId { get; set; } public EmbyContent Series { get; set; } } diff --git a/src/Ombi.Store/Entities/Requests/SeasonRequests.cs b/src/Ombi.Store/Entities/Requests/SeasonRequests.cs index 496f1988d..521cf5b94 100644 --- a/src/Ombi.Store/Entities/Requests/SeasonRequests.cs +++ b/src/Ombi.Store/Entities/Requests/SeasonRequests.cs @@ -10,7 +10,7 @@ namespace Ombi.Store.Repository.Requests public class SeasonRequests : Entity { public int SeasonNumber { get; set; } - public List Episodes { get; set; } + public List Episodes { get; set; } = new List(); public int ChildRequestId { get; set; } [ForeignKey(nameof(ChildRequestId))] diff --git a/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.Designer.cs b/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.Designer.cs new file mode 100644 index 000000000..644119ea0 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.Designer.cs @@ -0,0 +1,948 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180406224743_EmbyMetadata")] + partial class EmbyMetadata + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.cs b/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.cs new file mode 100644 index 000000000..b7f98525d --- /dev/null +++ b/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class EmbyMetadata : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ImdbId", + table: "EmbyEpisode", + nullable: true); + + migrationBuilder.AddColumn( + name: "TheMovieDbId", + table: "EmbyEpisode", + nullable: true); + + migrationBuilder.AddColumn( + name: "TvDbId", + table: "EmbyEpisode", + nullable: true); + + migrationBuilder.AddColumn( + name: "ImdbId", + table: "EmbyContent", + nullable: true); + + migrationBuilder.AddColumn( + name: "TheMovieDbId", + table: "EmbyContent", + nullable: true); + + migrationBuilder.AddColumn( + name: "TvDbId", + table: "EmbyContent", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ImdbId", + table: "EmbyEpisode"); + + migrationBuilder.DropColumn( + name: "TheMovieDbId", + table: "EmbyEpisode"); + + migrationBuilder.DropColumn( + name: "TvDbId", + table: "EmbyEpisode"); + + migrationBuilder.DropColumn( + name: "ImdbId", + table: "EmbyContent"); + + migrationBuilder.DropColumn( + name: "TheMovieDbId", + table: "EmbyContent"); + + migrationBuilder.DropColumn( + name: "TvDbId", + table: "EmbyContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index a24aa583a..19e14a4ca 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -185,10 +185,16 @@ namespace Ombi.Store.Migrations b.Property("EmbyId") .IsRequired(); + b.Property("ImdbId"); + b.Property("ProviderId"); + b.Property("TheMovieDbId"); + b.Property("Title"); + b.Property("TvDbId"); + b.Property("Type"); b.HasKey("Id"); @@ -207,14 +213,20 @@ namespace Ombi.Store.Migrations b.Property("EpisodeNumber"); + b.Property("ImdbId"); + b.Property("ParentId"); b.Property("ProviderId"); b.Property("SeasonNumber"); + b.Property("TheMovieDbId"); + b.Property("Title"); + b.Property("TvDbId"); + b.HasKey("Id"); b.HasIndex("ParentId"); diff --git a/src/Ombi.Store/Repository/EmbyContentRepository.cs b/src/Ombi.Store/Repository/EmbyContentRepository.cs index 280243455..c4377f929 100644 --- a/src/Ombi.Store/Repository/EmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/EmbyContentRepository.cs @@ -35,42 +35,28 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public class EmbyContentRepository : IEmbyContentRepository + public class EmbyContentRepository : Repository, IEmbyContentRepository { - public EmbyContentRepository(IOmbiContext db) + public EmbyContentRepository(IOmbiContext db):base(db) { Db = db; } private IOmbiContext Db { get; } - public IQueryable GetAll() + + public async Task GetByImdbId(string imdbid) { - return Db.EmbyContent.AsQueryable(); + return await Db.EmbyContent.FirstOrDefaultAsync(x => x.ImdbId == imdbid); } - - public async Task AddRange(IEnumerable content) + public async Task GetByTvDbId(string tv) { - Db.EmbyContent.AddRange(content); - await Db.SaveChangesAsync(); + return await Db.EmbyContent.FirstOrDefaultAsync(x => x.TvDbId == tv); } - - public async Task ContentExists(string providerId) + public async Task GetByTheMovieDbId(string mov) { - return await Db.EmbyContent.AnyAsync(x => x.ProviderId == providerId); - } - - public async Task Add(EmbyContent content) - { - await Db.EmbyContent.AddAsync(content); - await Db.SaveChangesAsync(); - return content; - } - - public async Task Get(string providerId) - { - return await Db.EmbyContent.FirstOrDefaultAsync(x => x.ProviderId == providerId); + return await Db.EmbyContent.FirstOrDefaultAsync(x => x.TheMovieDbId == mov); } public IQueryable Get() @@ -111,23 +97,9 @@ namespace Ombi.Store.Repository await Db.SaveChangesAsync(); } - private bool _disposed; - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - - if (disposing) - { - Db?.Dispose(); - } - _disposed = true; - } - - public void Dispose() + public void UpdateWithoutSave(EmbyContent existingContent) { - Dispose(true); - GC.SuppressFinalize(this); + Db.EmbyContent.Update(existingContent); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IEmbyContentRepository.cs b/src/Ombi.Store/Repository/IEmbyContentRepository.cs index 3ed8d8abd..a893e9aca 100644 --- a/src/Ombi.Store/Repository/IEmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/IEmbyContentRepository.cs @@ -6,19 +6,19 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public interface IEmbyContentRepository : IDisposable + public interface IEmbyContentRepository : IRepository { - Task Add(EmbyContent content); - Task AddRange(IEnumerable content); - Task ContentExists(string providerId); IQueryable Get(); - Task Get(string providerId); - IQueryable GetAll(); + Task GetByTheMovieDbId(string mov); + Task GetByTvDbId(string tv); + Task GetByImdbId(string imdbid); Task GetByEmbyId(string embyId); Task Update(EmbyContent existingContent); IQueryable GetAllEpisodes(); Task Add(EmbyEpisode content); Task GetEpisodeByEmbyId(string key); Task AddRange(IEnumerable content); + + void UpdateWithoutSave(EmbyContent existingContent); } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/app.module.ts b/src/Ombi/ClientApp/app/app.module.ts index 731fa383b..0936b13cd 100644 --- a/src/Ombi/ClientApp/app/app.module.ts +++ b/src/Ombi/ClientApp/app/app.module.ts @@ -58,10 +58,11 @@ const routes: Routes = [ // AoT requires an exported function for factories export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLocation) { const base = platformLocation.getBaseHrefFromDOM(); + const version = Math.floor(Math.random() * 999999999); if (base.length > 1) { - return new TranslateHttpLoader(http, `${base}/translations/`, ".json"); + return new TranslateHttpLoader(http, `${base}/translations/`, `.json?v=${version}`); } - return new TranslateHttpLoader(http, "/translations/", ".json"); + return new TranslateHttpLoader(http, "/translations/", `.json?v=${version}`); } @NgModule({ diff --git a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts index a7944f1a2..da1e147da 100644 --- a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts @@ -57,6 +57,9 @@ export interface IDiscordNotifcationSettings extends INotificationSettings { export interface INewsletterNotificationSettings extends INotificationSettings { notificationTemplate: INotificationTemplates; + disableMovies: boolean; + disableTv: boolean; + externalEmails: string[]; } export interface ITelegramNotifcationSettings extends INotificationSettings { diff --git a/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts b/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts index 5ed9567f5..f0afa76b2 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts @@ -30,3 +30,20 @@ export interface ISearchTvResult { firstSeason: boolean; latestSeason: boolean; } + +export interface ITvRequestViewModel { + requestAll: boolean; + firstSeason: boolean; + latestSeason: boolean; + tvDbId: number; + seasons: ISeasonsViewModel[]; +} + +export interface ISeasonsViewModel { + seasonNumber: number; + episodes: IEpisodesViewModel[]; +} + +export interface IEpisodesViewModel { + episodeNumber: number; +} diff --git a/src/Ombi/ClientApp/app/issues/issueDetails.component.html b/src/Ombi/ClientApp/app/issues/issueDetails.component.html index bad4885e7..e88ad621c 100644 --- a/src/Ombi/ClientApp/app/issues/issueDetails.component.html +++ b/src/Ombi/ClientApp/app/issues/issueDetails.component.html @@ -1,79 +1,80 @@
-
-
-

{{issue.title}}

-
- poster - {{IssueStatus[issue.status]}} - {{issue.issueCategory.value}} +
+
+
+

{{issue.title}}

+
+ poster + {{IssueStatus[issue.status]}} + {{issue.issueCategory.value}} -

{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}

-

{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}

-

{{'Issues.Subject' | translate}}: {{issue.subject}}

-
-
- -
- +

{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}

+

{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}

+

{{'Issues.Subject' | translate}}: {{issue.subject}}

+
+
+ +
+ +
-
-
+
-
-
-
-
-
-

- {{'Issues.Comments' | translate}} -

+
+
+
+
+
+

+ {{'Issues.Comments' | translate}} +

+
-
-
-
-
-
-

+
+
+
+
+

+
-
-
-
-
-

{{comment.comment}}

- +
+
+
+

{{comment.comment}}

+ +
-
-
-
-
-
-
-
- -
-
- +
+
+
+ +
+
+ +
-
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/issues/issueDetails.component.scss b/src/Ombi/ClientApp/app/issues/issueDetails.component.scss index 639b9318c..194ed9566 100644 --- a/src/Ombi/ClientApp/app/issues/issueDetails.component.scss +++ b/src/Ombi/ClientApp/app/issues/issueDetails.component.scss @@ -70,8 +70,9 @@ body{ .tint { z-index: -1; } -img-responsive poster { - display:block; +.img-responsive.poster { + margin-bottom: 21px; + width: 50%; } img { display: block; @@ -143,4 +144,8 @@ img { border-top: 1px solid transparent; border-bottom-right-radius: -1; border-bottom-left-radius: -1; +} + +.bottom-btn{ + margin-bottom: 10px; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.html b/src/Ombi/ClientApp/app/requests/movierequests.component.html index f798e4b70..6b2300f38 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.html +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.html @@ -14,32 +14,32 @@

-
- {{ 'Requests.RequestedBy' | translate }} - {{request.requestedUser.userName}} - {{request.requestedUser.alias}} - {{request.requestedUser.userName}} -
-
- {{ 'Requests.Status' | translate }} - {{request.status}} -
+
+
+ {{ 'Requests.RequestedBy' | translate }} + {{request.requestedUser.userName}} + {{request.requestedUser.alias}} + {{request.requestedUser.userName}} +
+
+ {{ 'Requests.Status' | translate }} + {{request.status}} +
-
- {{ 'Requests.RequestStatus' | translate }} - - - - - - - +
+ {{ 'Requests.RequestStatus' | translate }} + + + + + + + -
-
- {{ 'Requests.Denied' | translate }} - +
+
+ {{ 'Requests.Denied' | translate }} + -
+
-
{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}
-
{{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}
-
{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}
-
+
{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}
+
{{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}
+
{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}
+
+
{{ 'Requests.QualityOverride' | translate }} {{request.qualityOverrideTitle}} @@ -127,13 +129,15 @@
+ {{ 'Common.Approve' | translate }} +
+ {{ 'Requests.ChangeRootFolder' | translate }} + + {{ 'Requests.ChangeQualityProfile' | translate }} + + {{ 'Requests.Deny' | translate }} +
+ {{ 'Requests.Remove' | translate }} +
+ {{ 'Requests.MarkUnavailable' | translate }} + + {{ 'Requests.MarkAvailable' | translate }} +
@@ -183,7 +192,7 @@ +
+
+ +
+
+
+
+ +
@@ -43,6 +53,31 @@
When testing, the test newsletter will go to all users that have the Admin role, please ensure that there are valid email addresses for this. The test will also only grab the latest 10 movies and 10 shows just to give you an example. +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ {{email}} +
+
+ +
+
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts index 460c54955..7154543fc 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts @@ -11,6 +11,7 @@ export class NewsletterComponent implements OnInit { public NotificationType = NotificationType; public settings: INewsletterNotificationSettings; + public emailToAdd: string; constructor(private settingsService: SettingsService, private notificationService: NotificationService, @@ -48,4 +49,24 @@ export class NewsletterComponent implements OnInit { }); } + public addEmail() { + + if(this.emailToAdd) { + const emailRegex = "[a-zA-Z0-9.-_]{1,}@[a-zA-Z.-]{2,}[.]{1}[a-zA-Z]{2,}"; + const match = this.emailToAdd.match(emailRegex)!; + if(match && match.length > 0) { + this.settings.externalEmails.push(this.emailToAdd); + this.emailToAdd = ""; + } else { + this.notificationService.error("Please enter a valid email address"); + } + } + } + + public deleteEmail(email: string) { + const index = this.settings.externalEmails.indexOf(email); // <-- Not supported in -

User: {{user.userName}}

- +
+

User: {{user.userName}}

+ - - -