From 5a228026638dec34377610921fbeecf17d3bdf6a Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 14 Feb 2018 08:11:52 +0000 Subject: [PATCH 01/54] Fixed the update check for the master build --- src/Ombi.Schedule/Processor/ChangeLogProcessor.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs index 83d55aec3..f1c0925aa 100644 --- a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs +++ b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net.Http; using System.Threading.Tasks; @@ -9,6 +10,7 @@ using Octokit; using Ombi.Api; using Ombi.Api.Service; using Ombi.Core.Processor; +using Ombi.Helpers; namespace Ombi.Schedule.Processor { @@ -44,7 +46,8 @@ namespace Ombi.Schedule.Processor if (masterBranch) { latestRelease = doc.DocumentNode.Descendants("h2") - .FirstOrDefault(x => x.InnerText != "(unreleased)"); + .FirstOrDefault(x => x.InnerText == "(unreleased)"); + // TODO: Change this to InnterText != "(unreleased)" once we go live and it's not a prerelease } else { @@ -173,10 +176,13 @@ namespace Ombi.Schedule.Processor var releases = await client.Repository.Release.GetAll("tidusjar", "ombi"); var latest = releases.FirstOrDefault(x => x.TagName == releaseTag); - + if (latest.Name.Contains("V2", CompareOptions.IgnoreCase)) + { + latest = null; + } if (latest == null) { - latest = releases.OrderBy(x => x.CreatedAt).FirstOrDefault(); + latest = releases.OrderByDescending(x => x.CreatedAt).FirstOrDefault(); } foreach (var item in latest.Assets) { From 109f5317bbe826e3ad0a00d0ba5b872e5ae952ca Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 14 Feb 2018 13:43:39 +0000 Subject: [PATCH 02/54] Added the recently added engine with some basic methods --- src/Ombi.Core/Engine/RecentlyAddedEngine.cs | 80 +++++++++++++++++++ .../Models/RecentlyAddedMovieModel.cs | 23 ++++++ .../Repository/EmbyContentRepository.cs | 4 +- .../Repository/IEmbyContentRepository.cs | 2 +- src/Ombi/Ombi.csproj | 4 + 5 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 src/Ombi.Core/Engine/RecentlyAddedEngine.cs create mode 100644 src/Ombi.Core/Models/RecentlyAddedMovieModel.cs diff --git a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs new file mode 100644 index 000000000..97c086e5b --- /dev/null +++ b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Ombi.Core.Models; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Engine +{ + public class RecentlyAddedEngine + { + public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby) + { + _plex = plex; + _emby = emby; + } + + private readonly IPlexContentRepository _plex; + private readonly IEmbyContentRepository _emby; + + public IEnumerable GetRecentlyAddedMovies(TimeSpan from) + { + var model = new HashSet(); + var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie && x.AddedAt.Subtract(from) <= x.AddedAt); + var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie && x.AddedAt.Subtract(from) <= x.AddedAt); + + TransformPlexMovies(plexMovies, model); + TransformEmbyMovies(embyMovies, model); + + return model; + } + + public IEnumerable GetRecentlyAddedMovies() + { + var model = new HashSet(); + var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie); + var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie); + + TransformPlexMovies(plexMovies, model); + TransformEmbyMovies(embyMovies, model); + + return model; + } + + private static void TransformEmbyMovies(IQueryable embyMovies, HashSet model) + { + foreach (var emby in embyMovies) + { + model.Add(new RecentlyAddedMovieModel + { + Id = emby.Id, + ImdbId = emby.ProviderId, + AddedAt = emby.AddedAt, + Title = emby.Title, + }); + } + } + + private static void TransformPlexMovies(IQueryable plexMovies, HashSet model) + { + foreach (var plex in plexMovies) + { + model.Add(new RecentlyAddedMovieModel + { + Id = plex.Id, + ImdbId = plex.ImdbId, + TheMovieDbId = plex.TheMovieDbId, + AddedAt = plex.AddedAt, + Title = plex.Title, + Quality = plex.Quality, + ReleaseYear = plex.ReleaseYear + }); + } + } + } +} diff --git a/src/Ombi.Core/Models/RecentlyAddedMovieModel.cs b/src/Ombi.Core/Models/RecentlyAddedMovieModel.cs new file mode 100644 index 000000000..c63ea98d3 --- /dev/null +++ b/src/Ombi.Core/Models/RecentlyAddedMovieModel.cs @@ -0,0 +1,23 @@ +using System; + +namespace Ombi.Core.Models +{ + public class RecentlyAddedMovieModel + { + public int Id { get; set; } + public string Title { get; set; } + public string Overview { get; set; } + public string ImdbId { get; set; } + public string TvDbId { get; set; } + public string TheMovieDbId { get; set; } + public string ReleaseYear { get; set; } + public DateTime AddedAt { get; set; } + public string Quality { get; set; } + } + + public enum RecentlyAddedType + { + Plex, + Emby + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/EmbyContentRepository.cs b/src/Ombi.Store/Repository/EmbyContentRepository.cs index 519138edc..280243455 100644 --- a/src/Ombi.Store/Repository/EmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/EmbyContentRepository.cs @@ -45,9 +45,9 @@ namespace Ombi.Store.Repository private IOmbiContext Db { get; } - public async Task> GetAll() + public IQueryable GetAll() { - return await Db.EmbyContent.ToListAsync(); + return Db.EmbyContent.AsQueryable(); } public async Task AddRange(IEnumerable content) diff --git a/src/Ombi.Store/Repository/IEmbyContentRepository.cs b/src/Ombi.Store/Repository/IEmbyContentRepository.cs index e6fe18067..3ed8d8abd 100644 --- a/src/Ombi.Store/Repository/IEmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/IEmbyContentRepository.cs @@ -13,7 +13,7 @@ namespace Ombi.Store.Repository Task ContentExists(string providerId); IQueryable Get(); Task Get(string providerId); - Task> GetAll(); + IQueryable GetAll(); Task GetByEmbyId(string embyId); Task Update(EmbyContent existingContent); IQueryable GetAllEpisodes(); diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 0f0dfb8c6..f55e6a507 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -28,15 +28,19 @@ + + + + From ee50eb79b7301147b3d121f69beb2413574530f4 Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 15 Feb 2018 08:37:41 +0000 Subject: [PATCH 03/54] Added recently added stuff --- src/Ombi.Core/Engine/IRecentlyAddedEngine.cs | 12 +++++++ src/Ombi.Core/Engine/RecentlyAddedEngine.cs | 8 ++--- src/Ombi.DependencyInjection/IocExtensions.cs | 1 + .../Controllers/RecentlyAddedController.cs | 33 +++++++++++++++++++ src/Ombi/Models/RecentlyAddedRangeModel.cs | 10 ++++++ 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/Ombi.Core/Engine/IRecentlyAddedEngine.cs create mode 100644 src/Ombi/Controllers/RecentlyAddedController.cs create mode 100644 src/Ombi/Models/RecentlyAddedRangeModel.cs diff --git a/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs b/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs new file mode 100644 index 000000000..5b2988c8e --- /dev/null +++ b/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using Ombi.Core.Models; + +namespace Ombi.Core.Engine +{ + public interface IRecentlyAddedEngine + { + IEnumerable GetRecentlyAddedMovies(); + IEnumerable GetRecentlyAddedMovies(DateTime from, DateTime to); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs index 97c086e5b..07762d336 100644 --- a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs +++ b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs @@ -11,7 +11,7 @@ using Ombi.Store.Repository; namespace Ombi.Core.Engine { - public class RecentlyAddedEngine + public class RecentlyAddedEngine : IRecentlyAddedEngine { public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby) { @@ -22,11 +22,11 @@ namespace Ombi.Core.Engine private readonly IPlexContentRepository _plex; private readonly IEmbyContentRepository _emby; - public IEnumerable GetRecentlyAddedMovies(TimeSpan from) + public IEnumerable GetRecentlyAddedMovies(DateTime from, DateTime to) { var model = new HashSet(); - var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie && x.AddedAt.Subtract(from) <= x.AddedAt); - var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie && x.AddedAt.Subtract(from) <= x.AddedAt); + 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); TransformPlexMovies(plexMovies, model); TransformEmbyMovies(embyMovies, model); diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 86d6da800..472c88c14 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -79,6 +79,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); } public static void RegisterHttp(this IServiceCollection services) diff --git a/src/Ombi/Controllers/RecentlyAddedController.cs b/src/Ombi/Controllers/RecentlyAddedController.cs new file mode 100644 index 000000000..c2574bee3 --- /dev/null +++ b/src/Ombi/Controllers/RecentlyAddedController.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Ombi.Core.Engine; +using Ombi.Core.Models; +using Ombi.Models; + +namespace Ombi.Controllers +{ + [ApiV1] + [Produces("application/json")] + [Authorize] + public class RecentlyAddedController : Controller + { + public RecentlyAddedController(IRecentlyAddedEngine engine) + { + _recentlyAdded = engine; + } + + private readonly IRecentlyAddedEngine _recentlyAdded; + + /// + /// Returns the recently added movies between two dates + /// + [HttpPost("movies")] + [ProducesResponseType(typeof(IEnumerable), 200)] + public IEnumerable GetRecentlyAddedMovies([FromBody] RecentlyAddedRangeModel model) + { + return _recentlyAdded.GetRecentlyAddedMovies(model.From, model.To); + } + } +} \ No newline at end of file diff --git a/src/Ombi/Models/RecentlyAddedRangeModel.cs b/src/Ombi/Models/RecentlyAddedRangeModel.cs new file mode 100644 index 000000000..208f9cb49 --- /dev/null +++ b/src/Ombi/Models/RecentlyAddedRangeModel.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ombi.Models +{ + public class RecentlyAddedRangeModel + { + public DateTime From { get; set; } + public DateTime To { get; set; } + } +} \ No newline at end of file From d64e5ba23f0e928588556a340170d6f5f386d123 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 9 Feb 2018 14:21:51 +0000 Subject: [PATCH 04/54] Update appveyor.yml --- appveyor.yml | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a9c94fc07..51a0f0859 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,5 @@ -version: 2.2.{build} +version: 3.0.{build} configuration: Release -<<<<<<< HEAD os: Visual Studio 2017 environment: nodejs_version: "7.8.0" @@ -23,33 +22,13 @@ after_build: appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux.tar.gz" -======= -assembly_info: - patch: true - file: '**\AssemblyInfo.*' - assembly_version: '2.2.1' - assembly_file_version: '{version}' - assembly_informational_version: '2.2.1' -before_build: -- cmd: appveyor-retry nuget restore -build: - verbosity: minimal -after_build: -- cmd: >- - 7z a Ombi.zip %APPVEYOR_BUILD_FOLDER%\Ombi.UI\bin\Release\ ->>>>>>> origin/master - appveyor PushArtifact Ombi.zip - -<<<<<<< HEAD appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm.tar.gz" #cache: #- '%USERPROFILE%\.nuget\packages' -======= ->>>>>>> origin/master deploy: - provider: GitHub release: Ombi v$(appveyor_build_version) @@ -58,11 +37,3 @@ deploy: draft: true on: branch: master -<<<<<<< HEAD - -======= -- provider: Environment - name: Microserver - on: - branch: dev ->>>>>>> origin/master From 10b17143d18bd3088e0661b19a53633d67b8e742 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 9 Feb 2018 14:22:13 +0000 Subject: [PATCH 05/54] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 21dadcafa..2684ccc3e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,24 +1,3 @@ - -<<<<<<< HEAD -======= - - - -!! Version 2.X is not supported anymore. Pleas don't open a issue for the 2.x version. -See https://github.com/tidusjar/Ombi/issues/1455 for more information. - - - - - - - - - - - - ->>>>>>> origin/master
-
+
diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index 357a8ca9f..d65bf0264 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from "@angular/core"; +import { AfterViewInit, Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; @@ -13,11 +13,14 @@ import { StatusService } from "../services"; import { DomSanitizer } from "@angular/platform-browser"; import { ImageService } from "../services"; +import { fadeInOutAnimation } from "../animations/fadeinout"; + @Component({ templateUrl: "./login.component.html", + animations: [fadeInOutAnimation], styleUrls: ["./login.component.scss"], }) -export class LoginComponent implements OnInit { +export class LoginComponent implements AfterViewInit, OnDestroy, OnInit { public form: FormGroup; public customizationSettings: ICustomizationSettings; @@ -102,4 +105,24 @@ export class LoginComponent implements OnInit { }, err => this.notify.error(this.errorBody)); }); } + + public ngOnDestroy() { + setTimeout(() => { + this.images.getRandomBackground().subscribe(x => { + this.background = ""; + }); + }, 1000); + setTimeout(() => { + this.images.getRandomBackground().subscribe(x => { + this.background = this.sanitizer + .bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")"); + }); + }, 1000); + } + + public ngAfterViewInit() { + setInterval(() => { + this.ngOnDestroy(); + }, 10000); + } } diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 0c2c73360..766404941 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -92,5 +92,13 @@ + + + + + + + + diff --git a/src/Ombi/webpack.config.vendor.ts b/src/Ombi/webpack.config.vendor.ts index d4fe38c3a..b50e784f4 100644 --- a/src/Ombi/webpack.config.vendor.ts +++ b/src/Ombi/webpack.config.vendor.ts @@ -80,7 +80,7 @@ module.exports = (env: any) => { }, plugins: [ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", Hammer: "hammerjs/hammer" }), // Global identifiers - new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname, './client')), // Workaround for https://github.com/angular/angular/issues/20357 + new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname, "./client")), // Workaround for https://github.com/angular/angular/issues/20357 new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/11580 new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/14898 extractCSS, From 47f66978f00dc16ecd3c8adc0e0588b5c205a61e Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 15 Mar 2018 13:24:20 +0000 Subject: [PATCH 16/54] Added the ability to refresh out backend metadata (#2078) We now can refresh the Plex Metadata in our database. For example if the Plex Agent for TV Shows is TheMovieDb, we will use that and populate the IMDB Id and TheTvDb Id's so we can accuratly match and search things. Also improved the Job settings page so we can Test CRON's and we also validate them. --- src/Ombi.Api/Api.cs | 30 ++- src/Ombi.Api/HttpRequestExtnesions.cs | 45 ++++ src/Ombi.Api/Ombi.Api.csproj | 1 + src/Ombi.Api/Request.cs | 4 + src/Ombi.DependencyInjection/IocExtensions.cs | 1 + src/Ombi.Schedule/JobSetup.cs | 65 ++--- .../Jobs/Ombi/IRefreshMetadata.cs | 9 + .../Jobs/Ombi/RefreshMetadata.cs | 249 ++++++++++++++++++ src/Ombi.Schedule/Ombi.Schedule.csproj | 2 + .../Settings/Models/JobSettings.cs | 1 + .../Settings/Models/JobSettingsHelper.cs | 4 + .../Repository/IPlexContentRepository.cs | 2 + src/Ombi.Store/Repository/IRepository.cs | 2 + .../Repository/PlexContentRepository.cs | 10 + src/Ombi.Store/Repository/Repository.cs | 2 +- src/Ombi.TheMovieDbApi/IMovieDbApi.cs | 2 + src/Ombi.TheMovieDbApi/Models/FindResult.cs | 52 ++++ src/Ombi.TheMovieDbApi/Models/TvExternals.cs | 16 ++ src/Ombi.TheMovieDbApi/TheMovieDbApi.cs | 35 ++- .../ClientApp/app/interfaces/ISettings.ts | 16 ++ .../app/services/settings.service.ts | 14 +- .../app/settings/jobs/jobs.component.html | 23 +- .../app/settings/jobs/jobs.component.ts | 23 +- .../ClientApp/app/settings/settings.module.ts | 5 +- src/Ombi/Controllers/SettingsController.cs | 72 ++++- src/Ombi/Models/CronTestModel.cs | 12 + src/Ombi/Models/CronViewModelBody.cs | 7 + src/Ombi/Models/JobSettingsViewModel.cs | 8 + src/Ombi/Ombi.csproj | 1 + 29 files changed, 667 insertions(+), 46 deletions(-) create mode 100644 src/Ombi.Api/HttpRequestExtnesions.cs create mode 100644 src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs create mode 100644 src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs create mode 100644 src/Ombi.TheMovieDbApi/Models/FindResult.cs create mode 100644 src/Ombi.TheMovieDbApi/Models/TvExternals.cs create mode 100644 src/Ombi/Models/CronTestModel.cs create mode 100644 src/Ombi/Models/CronViewModelBody.cs create mode 100644 src/Ombi/Models/JobSettingsViewModel.cs diff --git a/src/Ombi.Api/Api.cs b/src/Ombi.Api/Api.cs index c12258b8e..98fff5e0c 100644 --- a/src/Ombi.Api/Api.cs +++ b/src/Ombi.Api/Api.cs @@ -1,4 +1,7 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; @@ -6,6 +9,7 @@ using System.Xml.Serialization; using Newtonsoft.Json; using Microsoft.Extensions.Logging; using Ombi.Helpers; +using Polly; namespace Ombi.Api { @@ -36,6 +40,30 @@ namespace Ombi.Api if (!httpResponseMessage.IsSuccessStatusCode) { LogError(request, httpResponseMessage); + if (request.Retry) + { + + var result = Policy + .Handle() + .OrResult(r => request.StatusCodeToRetry.Contains(r.StatusCode)) + .WaitAndRetryAsync(new[] + { + TimeSpan.FromSeconds(10), + }, (exception, timeSpan, context) => + { + + Logger.LogError(LoggingEvents.Api, + $"Retrying RequestUri: {request.FullUri} Because we got Status Code: {exception?.Result?.StatusCode}"); + }); + + httpResponseMessage = await result.ExecuteAsync(async () => + { + using (var req = await httpRequestMessage.Clone()) + { + return await _client.SendAsync(req); + } + }); + } } // do something with the response diff --git a/src/Ombi.Api/HttpRequestExtnesions.cs b/src/Ombi.Api/HttpRequestExtnesions.cs new file mode 100644 index 000000000..fa2ded97d --- /dev/null +++ b/src/Ombi.Api/HttpRequestExtnesions.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ombi.Api +{ + public static class HttpRequestExtnesions + { + public static async Task Clone(this HttpRequestMessage request) + { + var clone = new HttpRequestMessage(request.Method, request.RequestUri) + { + Content = await request.Content.Clone(), + Version = request.Version + }; + foreach (KeyValuePair prop in request.Properties) + { + clone.Properties.Add(prop); + } + foreach (KeyValuePair> header in request.Headers) + { + clone.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + return clone; + } + + public static async Task Clone(this HttpContent content) + { + if (content == null) return null; + + var ms = new MemoryStream(); + await content.CopyToAsync(ms); + ms.Position = 0; + + var clone = new StreamContent(ms); + foreach (KeyValuePair> header in content.Headers) + { + clone.Headers.Add(header.Key, header.Value); + } + return clone; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Api/Ombi.Api.csproj b/src/Ombi.Api/Ombi.Api.csproj index e89eb54b1..325f316b8 100644 --- a/src/Ombi.Api/Ombi.Api.csproj +++ b/src/Ombi.Api/Ombi.Api.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Ombi.Api/Request.cs b/src/Ombi.Api/Request.cs index 16dd38055..e4120ed9c 100644 --- a/src/Ombi.Api/Request.cs +++ b/src/Ombi.Api/Request.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Text; @@ -25,6 +26,9 @@ namespace Ombi.Api public string BaseUrl { get; } public HttpMethod HttpMethod { get; } + public bool Retry { get; set; } + public List StatusCodeToRetry { get; set; } = new List(); + public Action OnBeforeDeserialization { get; set; } private string FullUrl diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 817dfd551..b40d49036 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -172,6 +172,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } } } diff --git a/src/Ombi.Schedule/JobSetup.cs b/src/Ombi.Schedule/JobSetup.cs index dc9d49269..e73302387 100644 --- a/src/Ombi.Schedule/JobSetup.cs +++ b/src/Ombi.Schedule/JobSetup.cs @@ -17,46 +17,49 @@ namespace Ombi.Schedule public JobSetup(IPlexContentSync plexContentSync, IRadarrSync radarrSync, IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter, IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache, - ISettingsService jobsettings, ISickRageSync srSync) + ISettingsService jobsettings, ISickRageSync srSync, IRefreshMetadata refresh) { - PlexContentSync = plexContentSync; - RadarrSync = radarrSync; - Updater = updater; - EmbyContentSync = embySync; - PlexUserImporter = userImporter; - EmbyUserImporter = embyUserImporter; - SonarrSync = cache; - CpCache = cpCache; - JobSettings = jobsettings; - SrSync = srSync; + _plexContentSync = plexContentSync; + _radarrSync = radarrSync; + _updater = updater; + _embyContentSync = embySync; + _plexUserImporter = userImporter; + _embyUserImporter = embyUserImporter; + _sonarrSync = cache; + _cpCache = cpCache; + _jobSettings = jobsettings; + _srSync = srSync; + _refreshMetadata = refresh; } - private IPlexContentSync PlexContentSync { get; } - private IRadarrSync RadarrSync { get; } - private IOmbiAutomaticUpdater Updater { get; } - private IPlexUserImporter PlexUserImporter { get; } - private IEmbyContentSync EmbyContentSync { get; } - private IEmbyUserImporter EmbyUserImporter { get; } - private ISonarrSync SonarrSync { get; } - private ICouchPotatoSync CpCache { get; } - private ISickRageSync SrSync { get; } - private ISettingsService JobSettings { get; set; } + private readonly IPlexContentSync _plexContentSync; + private readonly IRadarrSync _radarrSync; + private readonly IOmbiAutomaticUpdater _updater; + private readonly IPlexUserImporter _plexUserImporter; + private readonly IEmbyContentSync _embyContentSync; + private readonly IEmbyUserImporter _embyUserImporter; + private readonly ISonarrSync _sonarrSync; + private readonly ICouchPotatoSync _cpCache; + private readonly ISickRageSync _srSync; + private readonly ISettingsService _jobSettings; + private readonly IRefreshMetadata _refreshMetadata; public void Setup() { - var s = JobSettings.GetSettings(); + var s = _jobSettings.GetSettings(); - RecurringJob.AddOrUpdate(() => EmbyContentSync.Start(), JobSettingsHelper.EmbyContent(s)); - RecurringJob.AddOrUpdate(() => SonarrSync.Start(), JobSettingsHelper.Sonarr(s)); - RecurringJob.AddOrUpdate(() => RadarrSync.CacheContent(), JobSettingsHelper.Radarr(s)); - RecurringJob.AddOrUpdate(() => PlexContentSync.CacheContent(), JobSettingsHelper.PlexContent(s)); - RecurringJob.AddOrUpdate(() => CpCache.Start(), JobSettingsHelper.CouchPotato(s)); - RecurringJob.AddOrUpdate(() => SrSync.Start(), JobSettingsHelper.SickRageSync(s)); + RecurringJob.AddOrUpdate(() => _embyContentSync.Start(), JobSettingsHelper.EmbyContent(s)); + RecurringJob.AddOrUpdate(() => _sonarrSync.Start(), JobSettingsHelper.Sonarr(s)); + RecurringJob.AddOrUpdate(() => _radarrSync.CacheContent(), JobSettingsHelper.Radarr(s)); + RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(), JobSettingsHelper.PlexContent(s)); + RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s)); + RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s)); + RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s)); - RecurringJob.AddOrUpdate(() => Updater.Update(null), JobSettingsHelper.Updater(s)); + RecurringJob.AddOrUpdate(() => _updater.Update(null), JobSettingsHelper.Updater(s)); - RecurringJob.AddOrUpdate(() => EmbyUserImporter.Start(), JobSettingsHelper.UserImporter(s)); - RecurringJob.AddOrUpdate(() => PlexUserImporter.Start(), JobSettingsHelper.UserImporter(s)); + RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s)); + RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s)); } } } diff --git a/src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs new file mode 100644 index 000000000..a08db74d0 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public interface IRefreshMetadata : IBaseJob + { + Task Start(); + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs new file mode 100644 index 000000000..5f5dd4635 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs @@ -0,0 +1,249 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Ombi.Api.TheMovieDb; +using Ombi.Api.TheMovieDb.Models; +using Ombi.Api.TvMaze; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Helpers; +using Ombi.Store.Entities; +using Ombi.Store.Repository; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public class RefreshMetadata : IRefreshMetadata + { + public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, + ILogger log, ITvMazeApi tvApi, ISettingsService plexSettings, + IMovieDbApi movieApi) + { + _plexRepo = plexRepo; + _embyRepo = embyRepo; + _log = log; + _movieApi = movieApi; + _tvApi = tvApi; + _plexSettings = plexSettings; + } + + private readonly IPlexContentRepository _plexRepo; + private readonly IEmbyContentRepository _embyRepo; + private readonly ILogger _log; + private readonly IMovieDbApi _movieApi; + private readonly ITvMazeApi _tvApi; + private readonly ISettingsService _plexSettings; + + public async Task Start() + { + _log.LogInformation("Starting the Metadata refresh"); + try + { + var settings = await _plexSettings.GetSettingsAsync(); + if (settings.Enable) + { + await StartPlex(); + } + } + catch (Exception e) + { + _log.LogError(e, "Exception when refreshing the Plex Metadata"); + throw; + } + } + + private async Task StartPlex() + { + await StartPlexMovies(); + + // Now Tv + await StartPlexTv(); + } + + private async Task StartPlexTv() + { + var allTv = _plexRepo.GetAll().Where(x => + x.Type == PlexMediaTypeEntity.Show && (!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; + _plexRepo.UpdateWithoutSave(show); + } + + if (!hasTvDbId) + { + var id = await GetTvDbId(hasTheMovieDb, hasImdb, show.TheMovieDbId, show.ImdbId, show.Title); + show.TvDbId = id; + _plexRepo.UpdateWithoutSave(show); + } + tvCount++; + if (tvCount >= 20) + { + await _plexRepo.SaveChangesAsync(); + tvCount = 0; + } + } + await _plexRepo.SaveChangesAsync(); + } + + private async Task StartPlexMovies() + { + var allMovies = _plexRepo.GetAll().Where(x => + x.Type == PlexMediaTypeEntity.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; + _plexRepo.UpdateWithoutSave(movie); + } + if (!hasTheMovieDb) + { + var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title); + movie.TheMovieDbId = id; + _plexRepo.UpdateWithoutSave(movie); + } + movieCount++; + if (movieCount >= 20) + { + await _plexRepo.SaveChangesAsync(); + movieCount = 0; + } + } + + await _plexRepo.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; + var hasResult = false; + if (hasTvDbId) + { + result = await _movieApi.Find(tvdbID, ExternalSource.tvdb_id); + hasResult = result?.tv_results?.Length > 0; + + _log.LogInformation("Setting Show {0} because we have TvDbId, result: {1}", title, hasResult); + } + if (hasImdb && !hasResult) + { + result = await _movieApi.Find(imdbId, ExternalSource.imdb_id); + hasResult = result?.tv_results?.Length > 0; + + _log.LogInformation("Setting Show {0} because we have ImdbId, result: {1}", title, hasResult); + } + if (hasResult) + { + return result.tv_results?[0]?.id.ToString() ?? string.Empty; + } + return string.Empty; + } + + private async Task GetImdbId(bool hasTheMovieDb, bool hasTvDbId, string title, string theMovieDbId, string tvDbId) + { + _log.LogInformation("The media item {0} does not have a ImdbId, searching for ImdbId", title); + // Looks like TV Maze does not provide the moviedb id, neither does the TV endpoint on TheMovieDb + if (hasTheMovieDb) + { + _log.LogInformation("The show {0} has TheMovieDbId but not ImdbId, searching for ImdbId", title); + if (int.TryParse(theMovieDbId, out var id)) + { + var result = await _movieApi.GetTvExternals(id); + + return result.imdb_id; + } + } + + if (hasTvDbId) + { + _log.LogInformation("The show {0} has tvdbid but not ImdbId, searching for ImdbId", title); + if (int.TryParse(tvDbId, out var id)) + { + var result = await _tvApi.ShowLookupByTheTvDbId(id); + return result?.externals?.imdb; + } + } + return string.Empty; + } + + + private async Task GetTvDbId(bool hasTheMovieDb, bool hasImdb, string theMovieDbId, string imdbId, string title) + { + _log.LogInformation("The media item {0} does not have a TvDbId, searching for TvDbId", title); + if (hasTheMovieDb) + { + _log.LogInformation("The show {0} has theMovieDBId but not ImdbId, searching for ImdbId", title); + if (int.TryParse(theMovieDbId, out var id)) + { + var result = await _movieApi.GetTvExternals(id); + + return result.tvdb_id.ToString(); + } + } + + if (hasImdb) + { + _log.LogInformation("The show {0} has ImdbId but not ImdbId, searching for ImdbId", title); + var result = await _movieApi.Find(imdbId, ExternalSource.imdb_id); + if (result?.tv_results?.Length > 0) + { + var movieId = result.tv_results?[0]?.id ?? 0; + + var externalResult = await _movieApi.GetTvExternals(movieId); + + return externalResult.imdb_id; + } + } + return string.Empty; + } + + + private async Task StartEmby() + { + + } + + + private bool _disposed; + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _plexRepo?.Dispose(); + _embyRepo?.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Ombi.Schedule.csproj b/src/Ombi.Schedule/Ombi.Schedule.csproj index cb8cef8ab..5088bc9f8 100644 --- a/src/Ombi.Schedule/Ombi.Schedule.csproj +++ b/src/Ombi.Schedule/Ombi.Schedule.csproj @@ -32,8 +32,10 @@ + + \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/JobSettings.cs b/src/Ombi.Settings/Settings/Models/JobSettings.cs index 7cf6e7104..ef4335fa5 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettings.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettings.cs @@ -10,5 +10,6 @@ public string AutomaticUpdater { get; set; } public string UserImporter { get; set; } public string SickRageSync { get; set; } + public string RefreshMetadata { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs index 69eaf4b33..a200c6b49 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs @@ -39,6 +39,10 @@ namespace Ombi.Settings.Settings.Models { return Get(s.SickRageSync, Cron.Hourly(35)); } + public static string RefreshMetadata(JobSettings s) + { + return Get(s.RefreshMetadata, Cron.Daily(3)); + } private static string Get(string settings, string defaultCron) diff --git a/src/Ombi.Store/Repository/IPlexContentRepository.cs b/src/Ombi.Store/Repository/IPlexContentRepository.cs index 2fef89be2..381a89fa3 100644 --- a/src/Ombi.Store/Repository/IPlexContentRepository.cs +++ b/src/Ombi.Store/Repository/IPlexContentRepository.cs @@ -22,5 +22,7 @@ namespace Ombi.Store.Repository Task DeleteEpisode(PlexEpisode content); void DeleteWithoutSave(PlexServerContent content); void DeleteWithoutSave(PlexEpisode content); + Task UpdateRange(IEnumerable existingContent); + void UpdateWithoutSave(PlexServerContent existingContent); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IRepository.cs b/src/Ombi.Store/Repository/IRepository.cs index ed5ed28c5..c85b45d8f 100644 --- a/src/Ombi.Store/Repository/IRepository.cs +++ b/src/Ombi.Store/Repository/IRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Query; using Ombi.Store.Entities; @@ -24,5 +25,6 @@ namespace Ombi.Store.Repository where TEntity : class; Task ExecuteSql(string sql); + DbSet _db { get; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index 56fec441a..098466310 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -97,6 +97,16 @@ namespace Ombi.Store.Repository Db.PlexServerContent.Update(existingContent); await Db.SaveChangesAsync(); } + public void UpdateWithoutSave(PlexServerContent existingContent) + { + Db.PlexServerContent.Update(existingContent); + } + + public async Task UpdateRange(IEnumerable existingContent) + { + Db.PlexServerContent.UpdateRange(existingContent); + await Db.SaveChangesAsync(); + } public IQueryable GetAllEpisodes() { diff --git a/src/Ombi.Store/Repository/Repository.cs b/src/Ombi.Store/Repository/Repository.cs index b4b9f8e93..049da0356 100644 --- a/src/Ombi.Store/Repository/Repository.cs +++ b/src/Ombi.Store/Repository/Repository.cs @@ -17,7 +17,7 @@ namespace Ombi.Store.Repository _ctx = ctx; _db = _ctx.Set(); } - private readonly DbSet _db; + public DbSet _db { get; } private readonly IOmbiContext _ctx; public async Task Find(object key) diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index dd0d0e92c..787902a4b 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -15,5 +15,7 @@ namespace Ombi.Api.TheMovieDb Task> TopRated(); Task> Upcoming(); Task> SimilarMovies(int movieId); + Task Find(string externalId, ExternalSource source); + Task GetTvExternals(int theMovieDbId); } } \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/Models/FindResult.cs b/src/Ombi.TheMovieDbApi/Models/FindResult.cs new file mode 100644 index 000000000..f76fca564 --- /dev/null +++ b/src/Ombi.TheMovieDbApi/Models/FindResult.cs @@ -0,0 +1,52 @@ +namespace Ombi.Api.TheMovieDb.Models +{ + public class FindResult + { + public Movie_Results[] movie_results { get; set; } + public object[] person_results { get; set; } + public TvResults[] tv_results { get; set; } + public object[] tv_episode_results { get; set; } + public object[] tv_season_results { get; set; } + } + + public class Movie_Results + { + public bool adult { get; set; } + public string backdrop_path { get; set; } + public int[] genre_ids { get; set; } + public int id { get; set; } + public string original_language { get; set; } + public string original_title { get; set; } + public string overview { get; set; } + public string poster_path { get; set; } + public string release_date { get; set; } + public string title { get; set; } + public bool video { get; set; } + public float vote_average { get; set; } + public int vote_count { get; set; } + } + + + public class TvResults + { + public string original_name { get; set; } + public int id { get; set; } + public string name { get; set; } + public int vote_count { get; set; } + public float vote_average { get; set; } + public string first_air_date { get; set; } + public string poster_path { get; set; } + public int[] genre_ids { get; set; } + public string original_language { get; set; } + public string backdrop_path { get; set; } + public string overview { get; set; } + public string[] origin_country { get; set; } + } + + + public enum ExternalSource + { + imdb_id, + tvdb_id + } +} \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/Models/TvExternals.cs b/src/Ombi.TheMovieDbApi/Models/TvExternals.cs new file mode 100644 index 000000000..237ae36a7 --- /dev/null +++ b/src/Ombi.TheMovieDbApi/Models/TvExternals.cs @@ -0,0 +1,16 @@ +namespace Ombi.Api.TheMovieDb.Models +{ + public class TvExternals + { + public string imdb_id { get; set; } + public string freebase_mid { get; set; } + public string freebase_id { get; set; } + public int tvdb_id { get; set; } + public int tvrage_id { get; set; } + public string facebook_id { get; set; } + public object instagram_id { get; set; } + public object twitter_id { get; set; } + public int id { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index 1fbfe9aaf..08925e490 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using AutoMapper; @@ -25,15 +26,37 @@ namespace Ombi.Api.TheMovieDb { var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + AddRetry(request); var result = await Api.Request(request); return Mapper.Map(result); } + public async Task Find(string externalId, ExternalSource source) + { + var request = new Request($"find/{externalId}", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + AddRetry(request); + + request.AddQueryString("external_source", source.ToString()); + + return await Api.Request(request); + } + + public async Task GetTvExternals(int theMovieDbId) + { + var request = new Request($"/tv/{theMovieDbId}/external_ids", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + AddRetry(request); + + return await Api.Request(request); + } + public async Task> SimilarMovies(int movieId) { var request = new Request($"movie/{movieId}/similar", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); @@ -44,6 +67,7 @@ namespace Ombi.Api.TheMovieDb var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); request.FullUri = request.FullUri.AddQueryParameter("append_to_response", "videos,release_dates"); + AddRetry(request); var result = await Api.Request(request); return Mapper.Map(result); } @@ -53,6 +77,7 @@ namespace Ombi.Api.TheMovieDb var request = new Request($"search/movie", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm); + AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); @@ -62,6 +87,7 @@ namespace Ombi.Api.TheMovieDb { var request = new Request($"movie/popular", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); } @@ -70,6 +96,7 @@ namespace Ombi.Api.TheMovieDb { var request = new Request($"movie/top_rated", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); } @@ -78,6 +105,7 @@ namespace Ombi.Api.TheMovieDb { var request = new Request($"movie/upcoming", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); } @@ -86,9 +114,14 @@ namespace Ombi.Api.TheMovieDb { var request = new Request($"movie/now_playing", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); } - + private static void AddRetry(Request request) + { + request.Retry = true; + request.StatusCodeToRetry.Add((HttpStatusCode)429); + } } } diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index 1ba2c4843..c79860801 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -125,6 +125,7 @@ export interface IJobSettings { automaticUpdater: string; userImporter: string; sickRageSync: string; + refreshMetadata: string; } export interface IIssueSettings extends ISettings { @@ -193,3 +194,18 @@ export interface IDogNzbSettings extends ISettings { export interface IIssueCategory extends ISettings { value: string; } + +export interface ICronTestModel { + success: boolean; + message: string; + schedule: Date[]; +} + +export interface ICronViewModelBody { + expression: string; +} + +export interface IJobSettingsViewModel { + result: boolean; + message: string; +} diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index 059df61e8..54f3458d5 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -7,6 +7,8 @@ import { IAbout, IAuthenticationSettings, ICouchPotatoSettings, + ICronTestModel, + ICronViewModelBody, ICustomizationSettings, IDiscordNotifcationSettings, IDogNzbSettings, @@ -14,6 +16,7 @@ import { IEmbySettings, IIssueSettings, IJobSettings, + IJobSettingsViewModel, ILandingPageSettings, IMattermostNotifcationSettings, IMobileNotifcationSettings, @@ -231,10 +234,15 @@ export class SettingsService extends ServiceHelpers { return this.http.get(`${this.url}/jobs`, {headers: this.headers}); } - public saveJobSettings(settings: IJobSettings): Observable { + public saveJobSettings(settings: IJobSettings): Observable { return this.http - .post(`${this.url}/jobs`, JSON.stringify(settings), {headers: this.headers}); - } + .post(`${this.url}/jobs`, JSON.stringify(settings), {headers: this.headers}); + } + + public testCron(body: ICronViewModelBody): Observable { + return this.http + .post(`${this.url}/testcron`, JSON.stringify(body), {headers: this.headers}); + } public getSickRageSettings(): Observable { return this.http.get(`${this.url}/sickrage`, {headers: this.headers}); diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html index ff5e56ed8..02eb51d77 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html @@ -12,29 +12,34 @@ The Sonarr Sync is required +
The SickRage Sync is required +
The Radarr Sync is required +
The CouchPotato Sync is required +
The Automatic Update is required +
@@ -50,21 +55,37 @@ The Plex Sync is required +
The Emby Sync is required +
The User Importer is required + +
+ +
+ + + The Refresh Metadata is required +
- \ No newline at end of file + + + +
    +
  • {{item | date:'short'}}
  • +
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts index f547c5056..380cef8de 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts @@ -1,7 +1,10 @@ import { Component, OnInit } from "@angular/core"; + import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { NotificationService, SettingsService } from "../../services"; +import { ICronTestModel } from "./../../interfaces"; + @Component({ templateUrl: "./jobs.component.html", }) @@ -10,6 +13,8 @@ export class JobsComponent implements OnInit { public form: FormGroup; public profilesRunning: boolean; + public testModel: ICronTestModel; + public displayTest: boolean; constructor(private readonly settingsService: SettingsService, private readonly fb: FormBuilder, @@ -26,7 +31,19 @@ export class JobsComponent implements OnInit { sonarrSync: [x.radarrSync, Validators.required], radarrSync: [x.sonarrSync, Validators.required], sickRageSync: [x.sickRageSync, Validators.required], - }); + refreshMetadata: [x.refreshMetadata, Validators.required], + }); + }); + } + + public testCron(expression: string) { + this.settingsService.testCron({ expression }).subscribe(x => { + if(x.success) { + this.testModel = x; + this.displayTest = true; + } else { + this.notificationService.error(x.message); + } }); } @@ -37,10 +54,10 @@ export class JobsComponent implements OnInit { } const settings = form.value; this.settingsService.saveJobSettings(settings).subscribe(x => { - if (x) { + if (x.result) { this.notificationService.success("Successfully saved the job settings"); } else { - this.notificationService.success("There was an error when saving the job settings"); + this.notificationService.error("There was an error when saving the job settings. " + x.message); } }); } diff --git a/src/Ombi/ClientApp/app/settings/settings.module.ts b/src/Ombi/ClientApp/app/settings/settings.module.ts index f10df8448..c95aa4362 100644 --- a/src/Ombi/ClientApp/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/app/settings/settings.module.ts @@ -41,7 +41,7 @@ import { WikiComponent } from "./wiki.component"; import { SettingsMenuComponent } from "./settingsmenu.component"; -import { AutoCompleteModule, CalendarModule, InputSwitchModule, InputTextModule, MenuModule, RadioButtonModule, TooltipModule } from "primeng/primeng"; +import { AutoCompleteModule, CalendarModule, DialogModule, InputSwitchModule, InputTextModule, MenuModule, RadioButtonModule, TooltipModule } from "primeng/primeng"; const routes: Routes = [ { path: "Ombi", component: OmbiComponent, canActivate: [AuthGuard] }, @@ -88,6 +88,7 @@ const routes: Routes = [ ClipboardModule, PipeModule, RadioButtonModule, + DialogModule, ], declarations: [ SettingsMenuComponent, @@ -139,4 +140,4 @@ const routes: Routes = [ ], }) -export class SettingsModule { } +export class SettingsModule { } \ No newline at end of file diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index c68a648c8..c0148c1bb 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -5,15 +5,18 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using AutoMapper; using Hangfire; +using Hangfire.RecurringJobExtensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.PlatformAbstractions; +using NCrontab; using Ombi.Api.Emby; using Ombi.Attributes; using Ombi.Core.Models.UI; @@ -465,7 +468,8 @@ namespace Ombi.Controllers j.PlexContentSync = j.PlexContentSync.HasValue() ? j.PlexContentSync : JobSettingsHelper.PlexContent(j); j.UserImporter = j.UserImporter.HasValue() ? j.UserImporter : JobSettingsHelper.UserImporter(j); j.SickRageSync = j.SickRageSync.HasValue() ? j.SickRageSync : JobSettingsHelper.SickRageSync(j); - + j.RefreshMetadata = j.RefreshMetadata.HasValue() ? j.RefreshMetadata : JobSettingsHelper.RefreshMetadata(j); + return j; } @@ -475,9 +479,71 @@ namespace Ombi.Controllers /// The settings. /// [HttpPost("jobs")] - public async Task JobSettings([FromBody]JobSettings settings) + public async Task JobSettings([FromBody]JobSettings settings) { - return await Save(settings); + // Verify that we have correct CRON's + foreach (var propertyInfo in settings.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (propertyInfo.Name.Equals("Id", StringComparison.CurrentCultureIgnoreCase)) + { + continue; + } + var expression = (string)propertyInfo.GetValue(settings, null); + + try + { + var r = CrontabSchedule.TryParse(expression); + if (r == null) + { + return new JobSettingsViewModel + { + Message = $"{propertyInfo.Name} does not have a valid CRON Expression" + }; + } + } + catch (Exception) + { + return new JobSettingsViewModel + { + Message = $"{propertyInfo.Name} does not have a valid CRON Expression" + }; + } + } + var result = await Save(settings); + + return new JobSettingsViewModel + { + Result = result + }; + } + + [HttpPost("testcron")] + public CronTestModel TestCron([FromBody] CronViewModelBody body) + { + var model = new CronTestModel(); + try + { + var time = DateTime.UtcNow; + var result = CrontabSchedule.TryParse(body.Expression); + for (int i = 0; i < 10; i++) + { + var next = result.GetNextOccurrence(time); + model.Schedule.Add(next); + time = next; + } + model.Success = true; + return model; + } + catch (Exception) + { + return new CronTestModel + { + Message = $"CRON Expression {body.Expression} is not valid" + }; + } + + } diff --git a/src/Ombi/Models/CronTestModel.cs b/src/Ombi/Models/CronTestModel.cs new file mode 100644 index 000000000..9698afbff --- /dev/null +++ b/src/Ombi/Models/CronTestModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Ombi.Models +{ + public class CronTestModel + { + public bool Success { get; set; } + public string Message { get; set; } + public List Schedule { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/Ombi/Models/CronViewModelBody.cs b/src/Ombi/Models/CronViewModelBody.cs new file mode 100644 index 000000000..cd961eda1 --- /dev/null +++ b/src/Ombi/Models/CronViewModelBody.cs @@ -0,0 +1,7 @@ +namespace Ombi.Models +{ + public class CronViewModelBody + { + public string Expression { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/Models/JobSettingsViewModel.cs b/src/Ombi/Models/JobSettingsViewModel.cs new file mode 100644 index 000000000..75006b86a --- /dev/null +++ b/src/Ombi/Models/JobSettingsViewModel.cs @@ -0,0 +1,8 @@ +namespace Ombi.Models +{ + public class JobSettingsViewModel + { + public bool Result { get; set; } + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 0c2c73360..5a465fb76 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -66,6 +66,7 @@ + From 94c713b3b518c3f91933d3a621262f0f2927952a Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 16 Mar 2018 08:21:56 +0000 Subject: [PATCH 17/54] Fixed #2074 and #2079 --- src/Ombi.Schedule/Processor/ChangeLogProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs index 09ceed5d3..f3053876b 100644 --- a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs +++ b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs @@ -78,9 +78,9 @@ namespace Ombi.Schedule.Processor Downloads = new List() }; - var releaseTag = latestRelease.InnerText.Substring(0, 6); if (masterBranch) { + var releaseTag = latestRelease.InnerText.Substring(0, 9); await GetGitubRelease(release, releaseTag); } else @@ -147,7 +147,7 @@ namespace Ombi.Schedule.Processor var builds = await _api.Request(request); var jobId = builds.build.jobs.FirstOrDefault()?.jobId ?? string.Empty; - if (builds.build.finished == DateTime.MinValue) + if (builds.build.finished == DateTime.MinValue || builds.build.status.Equals("failed")) { return; } From df95962aa6fd3571e95d8e412ac9a62a331851e8 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 16 Mar 2018 14:22:53 +0000 Subject: [PATCH 18/54] Made a start on adding tv shows for the recently added !wip --- src/Ombi.Core/Engine/IRecentlyAddedEngine.cs | 2 + src/Ombi.Core/Engine/RecentlyAddedEngine.cs | 92 +++++++++++++++++-- src/Ombi.Core/Models/RecentlyAddedTvModel.cs | 19 ++++ .../app/interfaces/IRecentlyAdded.ts | 5 + .../recentlyAdded.component.html | 22 +++-- .../recentlyAdded/recentlyAdded.component.ts | 79 +++++++++------- .../app/services/recentlyAdded.service.ts | 13 ++- .../ClientApp/app/settings/settings.module.ts | 2 +- src/Ombi/Controllers/ImagesController.cs | 11 ++- .../Controllers/RecentlyAddedController.cs | 31 ++++++- src/Ombi/Models/RecentlyAddedRangeModel.cs | 10 -- 11 files changed, 220 insertions(+), 66 deletions(-) create mode 100644 src/Ombi.Core/Models/RecentlyAddedTvModel.cs delete mode 100644 src/Ombi/Models/RecentlyAddedRangeModel.cs diff --git a/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs b/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs index 5b2988c8e..c119abbb4 100644 --- a/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs +++ b/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs @@ -8,5 +8,7 @@ namespace Ombi.Core.Engine { IEnumerable GetRecentlyAddedMovies(); IEnumerable GetRecentlyAddedMovies(DateTime from, DateTime to); + IEnumerable GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason); + IEnumerable GetRecentlyAddedTv(bool groupBySeason); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs index bfea4dba2..b8cc0ee6b 100644 --- a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs +++ b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Ombi.Core.Models; +using Ombi.Helpers; using Ombi.Store.Entities; using Ombi.Store.Repository; @@ -24,22 +25,54 @@ namespace Ombi.Core.Engine public IEnumerable GetRecentlyAddedMovies(DateTime from, DateTime to) { - var model = new HashSet(); 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); - - TransformPlexMovies(plexMovies, model); - TransformEmbyMovies(embyMovies, model); - - return model.Take(30); + + return GetRecentlyAddedMovies(plexMovies, embyMovies).Take(30); } public IEnumerable GetRecentlyAddedMovies() { - var model = new HashSet(); var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie); var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie); + return GetRecentlyAddedMovies(plexMovies, embyMovies); + } + + public IEnumerable GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason) + { + var plexTv = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Show && x.AddedAt > from && x.AddedAt < to); + var embyTv = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Series && x.AddedAt > from && x.AddedAt < to); + + return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason).Take(30); + } + + + public IEnumerable GetRecentlyAddedTv(bool groupBySeason) + { + var plexTv = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Show); + var embyTv = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Series); + + return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason); + } + + private IEnumerable GetRecentlyAddedTv(IQueryable plexTv, IQueryable embyTv, + bool groupBySeason) + { + var model = new HashSet(); + TransformPlexShows(plexTv, model); + TransformEmbyShows(embyTv, model); + + if (groupBySeason) + { + return model.DistinctBy(x => x.SeasonNumber); + } + + return model; + } + private IEnumerable GetRecentlyAddedMovies(IQueryable plexMovies, IQueryable embyMovies) + { + var model = new HashSet(); TransformPlexMovies(plexMovies, model); TransformEmbyMovies(embyMovies, model); @@ -76,5 +109,50 @@ namespace Ombi.Core.Engine }); } } + + private static void TransformPlexShows(IQueryable plexShows, HashSet model) + { + foreach (var plex in plexShows) + { + foreach (var season in plex.Seasons) + { + foreach (var episode in plex.Episodes) + { + model.Add(new RecentlyAddedTvModel + { + Id = plex.Id, + ImdbId = plex.ImdbId, + TheMovieDbId = plex.TheMovieDbId, + AddedAt = plex.AddedAt, + Title = plex.Title, + Quality = plex.Quality, + ReleaseYear = plex.ReleaseYear, + TvDbId = plex.TvDbId, + EpisodeNumber = episode.EpisodeNumber, + SeasonNumber = season.SeasonNumber + }); + } + } + } + } + + private static void TransformEmbyShows(IQueryable embyShows, HashSet model) + { + foreach (var emby in embyShows) + { + foreach (var episode in emby.Episodes) + { + model.Add(new RecentlyAddedTvModel + { + Id = emby.Id, + ImdbId = emby.ProviderId, + AddedAt = emby.AddedAt, + Title = emby.Title, + EpisodeNumber = episode.EpisodeNumber, + SeasonNumber = episode.SeasonNumber + }); + } + } + } } } diff --git a/src/Ombi.Core/Models/RecentlyAddedTvModel.cs b/src/Ombi.Core/Models/RecentlyAddedTvModel.cs new file mode 100644 index 000000000..dd485604d --- /dev/null +++ b/src/Ombi.Core/Models/RecentlyAddedTvModel.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ombi.Core.Models +{ + public class RecentlyAddedTvModel + { + public int Id { get; set; } + public string Title { get; set; } // Series Title + public string Overview { get; set; } + public string ImdbId { get; set; } + public string TvDbId { get; set; } + public string TheMovieDbId { get; set; } + public string ReleaseYear { get; set; } + public DateTime AddedAt { get; set; } + public string Quality { get; set; } + public int SeasonNumber { get; set; } + public int EpisodeNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts b/src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts index bce430f76..357a70d8b 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts @@ -12,6 +12,11 @@ export interface IRecentlyAddedMovies { posterPath: string; } +export interface IRecentlyAddedTvShows extends IRecentlyAddedMovies { + seasonNumber: number; + episodeNumber: number; +} + export interface IRecentlyAddedRangeModel { from: Date; to: Date; diff --git a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.html b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.html index 85c231a40..f9234f4a4 100644 --- a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.html +++ b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.html @@ -4,16 +4,26 @@
- - + poster + {{movie.title}} - - + + -
\ No newline at end of file + +
+ + + + poster + {{t.title}} + + + + + diff --git a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts index 660a83d52..d540a6b2d 100644 --- a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts +++ b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { NguCarousel } from "@ngu/carousel"; import { ImageService, RecentlyAddedService } from "../services"; -import { IRecentlyAddedMovies, IRecentlyAddedRangeModel } from "./../interfaces"; +import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces"; @Component({ templateUrl: "recentlyAdded.component.html", @@ -15,8 +15,9 @@ import { IRecentlyAddedMovies, IRecentlyAddedRangeModel } from "./../interfaces" width: 50px; height: 50px; box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); - border-radius: 999px; + border-radius: 100%; left: 0; + background: #df691a; } .rightRs { @@ -27,14 +28,16 @@ import { IRecentlyAddedMovies, IRecentlyAddedRangeModel } from "./../interfaces" width: 50px; height: 50px; box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); - border-radius: 999px; + border-radius: 100%; right: 0; + background: #df691a; } `], }) export class RecentlyAddedComponent implements OnInit { public movies: IRecentlyAddedMovies[]; + public tv: IRecentlyAddedTvShows[]; public range: Date[]; // https://github.com/sheikalthaf/ngu-carousel @@ -44,28 +47,8 @@ export class RecentlyAddedComponent implements OnInit { private imageService: ImageService) {} public ngOnInit() { - const weekAgo = new Date(); - weekAgo.setDate(weekAgo.getDate() - 7); - - const today =new Date(); - const initModel = {from: weekAgo, to: today}; - this.recentlyAddedService.getRecentlyAddedMovies(initModel).subscribe(x => { - this.movies = x; - - this.movies.forEach((movie) => { - if(movie.theMovieDbId) { - this.imageService.getMoviePoster(movie.theMovieDbId).subscribe(p => { - movie.posterPath = p; - }); - } else if(movie.imdbId) { - this.imageService.getMoviePoster(movie.imdbId).subscribe(p => { - movie.posterPath = p; - }); - } else { - movie.posterPath = ""; - } - }); - }); + this.getMovies(); + this.getShows(); this.carouselTile = { grid: {xs: 2, sm: 3, md: 3, lg: 5, all: 0}, @@ -89,12 +72,46 @@ export class RecentlyAddedComponent implements OnInit { // If we do not have a second date then just set it to now this.range[1] = new Date(); } - const initModel = {from: this.range[0], to: this.range[1]}; - this.recentlyAddedService.getRecentlyAddedMovies(initModel).subscribe(x => this.movies = x); + this.getMovies(); } - - public page(event: any) { - debugger; - console.log(event); + + private getShows() { + this.recentlyAddedService.getRecentlyAddedTv().subscribe(x => { + this.tv = x; + + this.tv.forEach((t) => { + if(t.theMovieDbId) { + this.imageService.getTvPoster(t.imdbId).subscribe(p => { + t.posterPath = p; + }); + } else if(t.imdbId) { + this.imageService.getMoviePoster(t.imdbId).subscribe(p => { + t.posterPath = p; + }); + } else { + t.posterPath = ""; + } + }); + }); + } + + private getMovies() { + this.recentlyAddedService.getRecentlyAddedMovies().subscribe(x => { + this.movies = x; + + this.movies.forEach((movie) => { + if(movie.theMovieDbId) { + this.imageService.getMoviePoster(movie.theMovieDbId).subscribe(p => { + movie.posterPath = p; + }); + } else if(movie.imdbId) { + this.imageService.getMoviePoster(movie.imdbId).subscribe(p => { + movie.posterPath = p; + }); + } else { + movie.posterPath = ""; + } + }); + }); } } diff --git a/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts b/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts index 55059ec76..366c6e583 100644 --- a/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts +++ b/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs/Rx"; -import { IRecentlyAddedMovies, IRecentlyAddedRangeModel } from "./../interfaces"; +import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -12,7 +12,14 @@ export class RecentlyAddedService extends ServiceHelpers { constructor(http: HttpClient, public platformLocation: PlatformLocation) { super(http, "/api/v1/recentlyadded/", platformLocation); } - public getRecentlyAddedMovies(model: IRecentlyAddedRangeModel): Observable { - return this.http.post(`${this.url}movies/`,JSON.stringify(model), {headers: this.headers}); + public getRecentlyAddedMovies(): Observable { + return this.http.get(`${this.url}movies/`, {headers: this.headers}); + } + + public getRecentlyAddedTv(): Observable { + return this.http.get(`${this. url}tv/`, {headers: this.headers}); + } + public getRecentlyAddedTvGrouped(): Observable { + return this.http.get(`${this.url}tv/grouped`, {headers: this.headers}); } } diff --git a/src/Ombi/ClientApp/app/settings/settings.module.ts b/src/Ombi/ClientApp/app/settings/settings.module.ts index c95aa4362..29866a273 100644 --- a/src/Ombi/ClientApp/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/app/settings/settings.module.ts @@ -140,4 +140,4 @@ const routes: Routes = [ ], }) -export class SettingsModule { } \ No newline at end of file +export class SettingsModule { } diff --git a/src/Ombi/Controllers/ImagesController.cs b/src/Ombi/Controllers/ImagesController.cs index 182fb9a3a..2cee60476 100644 --- a/src/Ombi/Controllers/ImagesController.cs +++ b/src/Ombi/Controllers/ImagesController.cs @@ -67,12 +67,17 @@ namespace Ombi.Controllers if (images.movieposter?.Any() ?? false) { - return images.movieposter.OrderBy(x => x.likes).Select(x => x.url).FirstOrDefault(); + var enImage = images.movieposter.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); + if (enImage == null) + { + return images.movieposter.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); + } + return enImage; } - if (images.hdmovieclearart?.Any() ?? false) + if (images.moviethumb?.Any() ?? false) { - return images.hdmovieclearart.OrderBy(x => x.likes).Select(x => x.url).FirstOrDefault(); + return images.moviethumb.OrderBy(x => x.likes).Select(x => x.url).FirstOrDefault(); } return string.Empty; diff --git a/src/Ombi/Controllers/RecentlyAddedController.cs b/src/Ombi/Controllers/RecentlyAddedController.cs index c2574bee3..fb4446efd 100644 --- a/src/Ombi/Controllers/RecentlyAddedController.cs +++ b/src/Ombi/Controllers/RecentlyAddedController.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -21,13 +22,33 @@ namespace Ombi.Controllers private readonly IRecentlyAddedEngine _recentlyAdded; /// - /// Returns the recently added movies between two dates + /// Returns the recently added movies for the past 7 days /// - [HttpPost("movies")] + [HttpGet("movies")] [ProducesResponseType(typeof(IEnumerable), 200)] - public IEnumerable GetRecentlyAddedMovies([FromBody] RecentlyAddedRangeModel model) + public IEnumerable GetRecentlyAddedMovies() { - return _recentlyAdded.GetRecentlyAddedMovies(model.From, model.To); + return _recentlyAdded.GetRecentlyAddedMovies(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow); + } + + /// + /// Returns the recently added tv shows for the past 7 days + /// + [HttpGet("tv")] + [ProducesResponseType(typeof(IEnumerable), 200)] + public IEnumerable GetRecentlyAddedShows() + { + return _recentlyAdded.GetRecentlyAddedTv(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow, false); + } + + /// + /// Returns the recently added tv shows for the past 7 days and groups them by season + /// + [HttpGet("tv/grouped")] + [ProducesResponseType(typeof(IEnumerable), 200)] + public IEnumerable GetRecentlyAddedShowsGrouped() + { + return _recentlyAdded.GetRecentlyAddedTv(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow, true); } } } \ No newline at end of file diff --git a/src/Ombi/Models/RecentlyAddedRangeModel.cs b/src/Ombi/Models/RecentlyAddedRangeModel.cs deleted file mode 100644 index 208f9cb49..000000000 --- a/src/Ombi/Models/RecentlyAddedRangeModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Ombi.Models -{ - public class RecentlyAddedRangeModel - { - public DateTime From { get; set; } - public DateTime To { get; set; } - } -} \ No newline at end of file From 3496cce670d167b3966304f7cb6d15201e81b365 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 16 Mar 2018 21:24:49 +0000 Subject: [PATCH 19/54] Got tv working, just need to do styling. I suck at this part !wip --- src/Ombi.Core/Engine/RecentlyAddedEngine.cs | 8 ++--- .../app/interfaces/IRecentlyAdded.ts | 1 + .../recentlyAdded.component.html | 30 +++++++++++++--- .../recentlyAdded/recentlyAdded.component.ts | 36 ++++++++++++------- src/Ombi/Controllers/ImagesController.cs | 17 ++++++--- 5 files changed, 67 insertions(+), 25 deletions(-) diff --git a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs index b8cc0ee6b..aab5ea607 100644 --- a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs +++ b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs @@ -40,8 +40,8 @@ namespace Ombi.Core.Engine public IEnumerable GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason) { - var plexTv = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Show && x.AddedAt > from && x.AddedAt < to); - var embyTv = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Series && x.AddedAt > from && x.AddedAt < to); + 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); return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason).Take(30); } @@ -49,8 +49,8 @@ namespace Ombi.Core.Engine public IEnumerable GetRecentlyAddedTv(bool groupBySeason) { - var plexTv = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Show); - var embyTv = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Series); + 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); return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason); } diff --git a/src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts b/src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts index 357a70d8b..e3b62b333 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts @@ -15,6 +15,7 @@ export interface IRecentlyAddedMovies { export interface IRecentlyAddedTvShows extends IRecentlyAddedMovies { seasonNumber: number; episodeNumber: number; + tvDbId: number; } export interface IRecentlyAddedRangeModel { diff --git a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.html b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.html index f9234f4a4..ee60b7c87 100644 --- a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.html +++ b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.html @@ -1,13 +1,31 @@

Recently Added

- +

- + - poster - {{movie.title}} +
+ poster +
{{movie.title}}
+
+ +
@@ -22,6 +40,10 @@ poster {{t.title}} +
+ Season: {{t.seasonNumber}} +
+ Episode: {{t.episodeNumber}}
diff --git a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts index d540a6b2d..c5c09f814 100644 --- a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts +++ b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts @@ -39,6 +39,8 @@ export class RecentlyAddedComponent implements OnInit { public movies: IRecentlyAddedMovies[]; public tv: IRecentlyAddedTvShows[]; public range: Date[]; + + public groupTv: boolean; // https://github.com/sheikalthaf/ngu-carousel public carouselTile: NguCarousel; @@ -74,25 +76,33 @@ export class RecentlyAddedComponent implements OnInit { } this.getMovies(); } + + public change() { + this.getShows(); + } private getShows() { - this.recentlyAddedService.getRecentlyAddedTv().subscribe(x => { - this.tv = x; - - this.tv.forEach((t) => { - if(t.theMovieDbId) { - this.imageService.getTvPoster(t.imdbId).subscribe(p => { - t.posterPath = p; + if(this.groupTv) { + this.recentlyAddedService.getRecentlyAddedTvGrouped().subscribe(x => { + this.tv = x; + + this.tv.forEach((t) => { + this.imageService.getTvPoster(t.tvDbId).subscribe(p => { + t.posterPath = p; + }); }); - } else if(t.imdbId) { - this.imageService.getMoviePoster(t.imdbId).subscribe(p => { + }); + } else { + this.recentlyAddedService.getRecentlyAddedTv().subscribe(x => { + this.tv = x; + + this.tv.forEach((t) => { + this.imageService.getTvPoster(t.tvDbId).subscribe(p => { t.posterPath = p; }); - } else { - t.posterPath = ""; - } + }); }); - }); + } } private getMovies() { diff --git a/src/Ombi/Controllers/ImagesController.cs b/src/Ombi/Controllers/ImagesController.cs index 2cee60476..9e85f0d56 100644 --- a/src/Ombi/Controllers/ImagesController.cs +++ b/src/Ombi/Controllers/ImagesController.cs @@ -44,11 +44,15 @@ namespace Ombi.Controllers } if (images.tvbanner != null) { - return images.tvbanner.FirstOrDefault()?.url ?? string.Empty; + var enImage = images.tvbanner.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); + if (enImage == null) + { + return images.tvbanner.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); + } } - if (images.showbackground != null) + if (images.seasonposter != null) { - return images.showbackground.FirstOrDefault()?.url ?? string.Empty; + return images.seasonposter.FirstOrDefault()?.url ?? string.Empty; } return string.Empty; } @@ -97,7 +101,12 @@ namespace Ombi.Controllers if (images.tvposter?.Any() ?? false) { - return images.tvposter.OrderBy(x => x.likes).Select(x => x.url).FirstOrDefault(); + var enImage = images.tvposter.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); + if (enImage == null) + { + return images.tvposter.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); + } + return enImage; } if (images.tvthumb?.Any() ?? false) From 9767380f837eec71c87df7247a7bdfdf5305acbb Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 22 Mar 2018 09:18:33 +0000 Subject: [PATCH 20/54] Newsletter almost finished. !wip --- src/Ombi.DependencyInjection/IocExtensions.cs | 1 + src/Ombi.Helpers/NotificationType.cs | 1 + .../EmailBasicTemplate.cs | 17 +- .../INewsletterTemplate.cs | 7 + .../NewsletterTemplate.cs | 46 + .../Ombi.Notifications.Templates.csproj | 3 + .../TemplateBase.cs | 8 + .../Templates/NewsletterTemplate.html | 187 ++++ .../GenericEmailProvider.cs | 6 +- src/Ombi.Schedule/JobSetup.cs | 6 +- .../Jobs/Ombi/HtmlTemplateGenerator.cs | 46 + src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs | 9 + src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 439 ++++++++ .../Jobs/Ombi/RefreshMetadata.cs | 7 +- src/Ombi.Store/Context/IOmbiContext.cs | 1 + src/Ombi.Store/Context/OmbiContext.cs | 17 +- src/Ombi.Store/Entities/PlexServerContent.cs | 9 + src/Ombi.Store/Entities/RecentlyAddedLog.cs | 19 + ...0180322085345_RecentlyAddedLog.Designer.cs | 934 ++++++++++++++++++ .../20180322085345_RecentlyAddedLog.cs | 33 + .../Migrations/OmbiContextModelSnapshot.cs | 18 +- .../recentlyAdded/recentlyAdded.component.ts | 26 +- src/Ombi/webpack.config.vendor.ts | 2 +- 23 files changed, 1811 insertions(+), 31 deletions(-) create mode 100644 src/Ombi.Notifications.Templates/INewsletterTemplate.cs create mode 100644 src/Ombi.Notifications.Templates/NewsletterTemplate.cs create mode 100644 src/Ombi.Notifications.Templates/TemplateBase.cs create mode 100644 src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html create mode 100644 src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs create mode 100644 src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs create mode 100644 src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs create mode 100644 src/Ombi.Store/Entities/RecentlyAddedLog.cs create mode 100644 src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.Designer.cs create mode 100644 src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.cs diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 48c22b40e..92ecf8282 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -174,6 +174,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } } } diff --git a/src/Ombi.Helpers/NotificationType.cs b/src/Ombi.Helpers/NotificationType.cs index d6466096c..8ea542063 100644 --- a/src/Ombi.Helpers/NotificationType.cs +++ b/src/Ombi.Helpers/NotificationType.cs @@ -13,5 +13,6 @@ WelcomeEmail = 8, IssueResolved = 9, IssueComment = 10, + Newsletter = 11, } } diff --git a/src/Ombi.Notifications.Templates/EmailBasicTemplate.cs b/src/Ombi.Notifications.Templates/EmailBasicTemplate.cs index 4814945e7..b29122be0 100644 --- a/src/Ombi.Notifications.Templates/EmailBasicTemplate.cs +++ b/src/Ombi.Notifications.Templates/EmailBasicTemplate.cs @@ -4,19 +4,26 @@ using System.Text; namespace Ombi.Notifications.Templates { - public class EmailBasicTemplate : IEmailBasicTemplate + public class EmailBasicTemplate : TemplateBase, IEmailBasicTemplate { - public string TemplateLocation + public override string TemplateLocation { get { + if (string.IsNullOrEmpty(_templateLocation)) + { #if DEBUG - return Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates", "BasicTemplate.html"); + _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates", + "BasicTemplate.html"); #else - return Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html"); + _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html"); #endif + } + return _templateLocation; } } + + private string _templateLocation; private const string SubjectKey = "{@SUBJECT}"; private const string BodyKey = "{@BODY}"; @@ -31,7 +38,7 @@ namespace Ombi.Notifications.Templates sb.Replace(BodyKey, body); sb.Replace(DateKey, DateTime.Now.ToString("f")); sb.Replace(Poster, string.IsNullOrEmpty(imgsrc) ? string.Empty : $"\"Poster\""); - sb.Replace(Logo, string.IsNullOrEmpty(logo) ? "http://i.imgur.com/qQsN78U.png" : logo); + sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo); return sb.ToString(); } diff --git a/src/Ombi.Notifications.Templates/INewsletterTemplate.cs b/src/Ombi.Notifications.Templates/INewsletterTemplate.cs new file mode 100644 index 000000000..e3302710d --- /dev/null +++ b/src/Ombi.Notifications.Templates/INewsletterTemplate.cs @@ -0,0 +1,7 @@ +namespace Ombi.Notifications.Templates +{ + public interface INewsletterTemplate + { + string LoadTemplate(string subject, string intro, string tableHtml, string logo); + } +} \ No newline at end of file diff --git a/src/Ombi.Notifications.Templates/NewsletterTemplate.cs b/src/Ombi.Notifications.Templates/NewsletterTemplate.cs new file mode 100644 index 000000000..389ff5cd6 --- /dev/null +++ b/src/Ombi.Notifications.Templates/NewsletterTemplate.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Text; + +namespace Ombi.Notifications.Templates +{ + public class NewsletterTemplate : TemplateBase, INewsletterTemplate + { + public override string TemplateLocation + { + get + { + if (string.IsNullOrEmpty(_templateLocation)) + { +#if DEBUG + _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates", "NewsletterTemplate.html"); +#else + _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "NewsletterTemplate.html"); +#endif + } + return _templateLocation; + } + } + + private string _templateLocation; + + private const string SubjectKey = "{@SUBJECT}"; + private const string DateKey = "{@DATENOW}"; + private const string Logo = "{@LOGO}"; + private const string TableLocation = "{@RECENTLYADDED}"; + private const string IntroText = "{@INTRO}"; + + + public string LoadTemplate(string subject, string intro, string tableHtml, string logo) + { + var sb = new StringBuilder(File.ReadAllText(TemplateLocation)); + sb.Replace(SubjectKey, subject); + sb.Replace(TableLocation, tableHtml); + sb.Replace(IntroText, intro); + sb.Replace(DateKey, DateTime.Now.ToString("f")); + sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo); + + return sb.ToString(); + } + } +} diff --git a/src/Ombi.Notifications.Templates/Ombi.Notifications.Templates.csproj b/src/Ombi.Notifications.Templates/Ombi.Notifications.Templates.csproj index 085c44dc1..cf310acc9 100644 --- a/src/Ombi.Notifications.Templates/Ombi.Notifications.Templates.csproj +++ b/src/Ombi.Notifications.Templates/Ombi.Notifications.Templates.csproj @@ -9,6 +9,9 @@ + + Always + Always diff --git a/src/Ombi.Notifications.Templates/TemplateBase.cs b/src/Ombi.Notifications.Templates/TemplateBase.cs new file mode 100644 index 000000000..9b9207c9a --- /dev/null +++ b/src/Ombi.Notifications.Templates/TemplateBase.cs @@ -0,0 +1,8 @@ +namespace Ombi.Notifications.Templates +{ + public abstract class TemplateBase + { + public abstract string TemplateLocation { get; } + public virtual string OmbiLogo => "http://i.imgur.com/qQsN78U.png"; + } +} \ No newline at end of file diff --git a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html new file mode 100644 index 000000000..21a10aebb --- /dev/null +++ b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html @@ -0,0 +1,187 @@ + + + + + + Ombi + + + + + + + + + +
  +
+ + + + + + + + + + + +
+ + + + + + + +
+ +
+
+
+

Here is a list of Movies and TV Shows that have recently been added!

+ +
+ + {@RECENTLYADDED} + +
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/src/Ombi.Notifications/GenericEmailProvider.cs b/src/Ombi.Notifications/GenericEmailProvider.cs index e28b48c27..462f8918e 100644 --- a/src/Ombi.Notifications/GenericEmailProvider.cs +++ b/src/Ombi.Notifications/GenericEmailProvider.cs @@ -107,9 +107,13 @@ namespace Ombi.Notifications var body = new BodyBuilder { HtmlBody = model.Message, - TextBody = model.Other["PlainTextBody"] }; + if (model.Other.ContainsKey("PlainTextBody")) + { + body.TextBody = model.Other["PlainTextBody"]; + } + var message = new MimeMessage { Body = body.ToMessageBody(), diff --git a/src/Ombi.Schedule/JobSetup.cs b/src/Ombi.Schedule/JobSetup.cs index e73302387..3e8589fea 100644 --- a/src/Ombi.Schedule/JobSetup.cs +++ b/src/Ombi.Schedule/JobSetup.cs @@ -17,7 +17,8 @@ namespace Ombi.Schedule public JobSetup(IPlexContentSync plexContentSync, IRadarrSync radarrSync, IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter, IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache, - ISettingsService jobsettings, ISickRageSync srSync, IRefreshMetadata refresh) + ISettingsService jobsettings, ISickRageSync srSync, IRefreshMetadata refresh, + INewsletterJob newsletter) { _plexContentSync = plexContentSync; _radarrSync = radarrSync; @@ -30,6 +31,7 @@ namespace Ombi.Schedule _jobSettings = jobsettings; _srSync = srSync; _refreshMetadata = refresh; + _newsletter = newsletter; } private readonly IPlexContentSync _plexContentSync; @@ -43,6 +45,7 @@ namespace Ombi.Schedule private readonly ISickRageSync _srSync; private readonly ISettingsService _jobSettings; private readonly IRefreshMetadata _refreshMetadata; + private readonly INewsletterJob _newsletter; public void Setup() { @@ -60,6 +63,7 @@ namespace Ombi.Schedule RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s)); RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s)); + BackgroundJob.Enqueue(() => _newsletter.Start()); } } } diff --git a/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs b/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs new file mode 100644 index 000000000..61f4bd7c8 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs @@ -0,0 +1,46 @@ +using System.Text; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public abstract class HtmlTemplateGenerator + { + protected virtual void AddParagraph(StringBuilder stringBuilder, string text, int fontSize = 14, string fontWeight = "normal") + { + stringBuilder.AppendFormat("

{0}

", text, fontSize, fontWeight); + } + + protected virtual void AddImageInsideTable(StringBuilder sb, string url, int size = 400) + { + sb.Append(""); + sb.Append(""); + sb.Append($""); + sb.Append(""); + sb.Append(""); + } + + protected virtual void Href(StringBuilder sb, string url) + { + sb.AppendFormat("", url); + } + + protected virtual void TableData(StringBuilder sb) + { + sb.Append( + ""); + } + + protected virtual void EndTag(StringBuilder sb, string tag) + { + sb.AppendFormat("", tag); + } + + protected virtual void Header(StringBuilder sb, int size, string text, string fontWeight = "normal") + { + sb.AppendFormat( + "{1}", + size, text, fontWeight); + } + + + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs new file mode 100644 index 000000000..e5e587bec --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public interface INewsletterJob : IBaseJob + { + Task Start(); + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs new file mode 100644 index 000000000..544bbc6dd --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -0,0 +1,439 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Api.TheMovieDb; +using Ombi.Api.TheMovieDb.Models; +using Ombi.Api.TvMaze; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Notifications; +using Ombi.Notifications.Models; +using Ombi.Notifications.Templates; +using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.Notifications; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public class NewsletterJob : HtmlTemplateGenerator, INewsletterJob + { + public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository addedLog, + IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService custom, + ISettingsService emailSettings, INotificationTemplatesRepository templateRepo) + { + _plex = plex; + _emby = emby; + _recentlyAddedLog = addedLog; + _movieApi = movieApi; + _tvApi = tvApi; + _email = email; + _customizationSettings = custom; + _templateRepo = templateRepo; + _emailSettings = emailSettings; + } + + private readonly IPlexContentRepository _plex; + private readonly IEmbyContentRepository _emby; + private readonly IRepository _recentlyAddedLog; + private readonly IMovieDbApi _movieApi; + private readonly ITvMazeApi _tvApi; + private readonly IEmailProvider _email; + private readonly ISettingsService _customizationSettings; + private readonly INotificationTemplatesRepository _templateRepo; + private readonly ISettingsService _emailSettings; + + public async Task Start() + { + var template = await _templateRepo.GetTemplate(NotificationAgent.Email, NotificationType.Newsletter); + if (!template.Enabled) + { + return; + } + + var emailSettings = await _emailSettings.GetSettingsAsync(); + if (!ValidateConfiguration(emailSettings)) + { + return; + } + + // Get the Content + var plexContent = _plex.GetAll().Include(x => x.Episodes); + var embyContent = _emby.GetAll().Include(x => x.Episodes); + + var addedLog = _recentlyAddedLog.GetAll(); + var addedPlexLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex).Select(x => x.ContentId); + var addedEmbyLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby).Select(x => x.ContentId); + + // Filter out the ones that we haven't sent yet + var plexContentToSend = plexContent.Where(x => !addedPlexLogIds.Contains(x.Id)); + var embyContentToSend = embyContent.Where(x => !addedEmbyLogIds.Contains(x.Id)); + + var body = await BuildHtml(plexContentToSend, embyContentToSend); + + var email = new NewsletterTemplate(); + + var customization = await _customizationSettings.GetSettingsAsync(); + + var html = email.LoadTemplate(template.Subject, template.Message, body, customization.Logo); + + await _email.Send(new NotificationMessage {Message = html, Subject = template.Subject, To = "tidusjar@gmail.com"}, emailSettings); + } + + private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend) + { + var sb = new StringBuilder(); + + sb.Append("

New Movies:



"); + await ProcessPlexMovies(plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie), sb); + await ProcessEmbyMovies(embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie), sb); + + sb.Append("

New Episodes:



"); + await ProcessPlexTv(plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Show), sb); + await ProcessEmbyMovies(embyContentToSend.Where(x => x.Type == EmbyMediaType.Series), sb); + + return sb.ToString(); + } + + private async Task ProcessPlexMovies(IQueryable plexContentToSend, StringBuilder sb) + { + sb.Append( + ""); + var ordered = plexContentToSend.OrderByDescending(x => x.AddedAt); + foreach (var content in ordered) + { + if (content.TheMovieDbId.IsNullOrEmpty()) + { + // 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(); + } + } + + int.TryParse(content.TheMovieDbId, out var movieDbId); + var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId); + if (info == null) + { + continue; + } + try + { + AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}"); + + sb.Append(""); + TableData(sb); + + Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/"); + Header(sb, 3, $"{info.Title} {info.ReleaseDate ?? string.Empty}"); + EndTag(sb, "a"); + + if (info.Genres.Any()) + { + AddParagraph(sb, + $"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}"); + } + + AddParagraph(sb, info.Overview); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + finally + { + EndLoopHtml(sb); + } + } + } + private async Task ProcessEmbyMovies(IQueryable embyContent, StringBuilder sb) + { + sb.Append( + "
"); + 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); + if (info == null) + { + continue; + } + try + { + AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}"); + + sb.Append(""); + TableData(sb); + + Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/"); + Header(sb, 3, $"{info.Title} {info.ReleaseDate ?? string.Empty}"); + EndTag(sb, "a"); + + if (info.Genres.Any()) + { + AddParagraph(sb, + $"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}"); + } + + AddParagraph(sb, info.Overview); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + finally + { + EndLoopHtml(sb); + } + } + } + + private async Task ProcessPlexTv(IQueryable plexContent, StringBuilder sb) + { + var orderedTv = plexContent.OrderByDescending(x => x.AddedAt); + sb.Append( + "
"); + foreach (var t in orderedTv) + { + try + { + if (!t.HasTvDb) + { + // We may need to use themoviedb for the imdbid or their own id to get info + if (t.HasTheMovieDb) + { + int.TryParse(t.TheMovieDbId, out var movieId); + var externals = await _movieApi.GetTvExternals(movieId); + if (externals == null || externals.tvdb_id <= 0) + { + continue; + } + t.TvDbId = externals.tvdb_id.ToString(); + } + // WE could check the below but we need to get the moviedb and then perform the above, let the metadata job figure this out. + //else if(t.HasImdb) + //{ + // // Check the imdbid + // var externals = await _movieApi.Find(t.ImdbId, ExternalSource.imdb_id); + // if (externals?.tv_results == null || externals.tv_results.Length <= 0) + // { + // continue; + // } + // t.TvDbId = externals.tv_results.FirstOrDefault()..ToString(); + //} + + } + + int.TryParse(t.TvDbId, out var tvdbId); + var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId); + if (info == null) + { + continue; + } + var banner = info.image?.original; + if (!string.IsNullOrEmpty(banner)) + { + banner = banner.Replace("http", "https"); // Always use the Https banners + } + AddImageInsideTable(sb, banner); + + sb.Append(""); + sb.Append( + "
"); + + var title = $"{t.Title} {t.ReleaseYear}"; + + Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/"); + Header(sb, 3, title); + EndTag(sb, "a"); + + // Group by the season number + var results = t.Episodes?.GroupBy(p => p.SeasonNumber, + (key, g) => new + { + SeasonNumber = key, + Episodes = g.ToList() + } + ); + + // Group the episodes + foreach (var epInformation in results.OrderBy(x => x.SeasonNumber)) + { + var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList(); + var epSb = new StringBuilder(); + for (var i = 0; i < orderedEpisodes.Count; i++) + { + var ep = orderedEpisodes[i]; + if (i < orderedEpisodes.Count - 1) + { + epSb.Append($"{ep.EpisodeNumber},"); + } + else + { + epSb.Append($"{ep.EpisodeNumber}"); + } + + } + AddParagraph(sb, $"Season: {epInformation.SeasonNumber}, Episode: {epSb}"); + } + + if (info.genres.Any()) + { + AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}"); + } + + AddParagraph(sb, info.summary); + } + catch (Exception e) + { + //Log.Error(e); + } + finally + { + EndLoopHtml(sb); + } + } + sb.Append("


"); + + } + + private async Task ProcessEmbyTv(IQueryable plexContent, StringBuilder sb) + { + var orderedTv = plexContent.OrderByDescending(x => x.AddedAt); + sb.Append( + ""); + foreach (var t in orderedTv) + { + try + { + int.TryParse(t.ProviderId, out var tvdbId); + var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId); + if (info == null) + { + continue; + } + var banner = info.image?.original; + if (!string.IsNullOrEmpty(banner)) + { + banner = banner.Replace("http", "https"); // Always use the Https banners + } + AddImageInsideTable(sb, banner); + + sb.Append(""); + sb.Append( + "
"); + + Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/"); + Header(sb, 3, t.Title); + EndTag(sb, "a"); + + // Group by the season number + var results = t.Episodes?.GroupBy(p => p.SeasonNumber, + (key, g) => new + { + SeasonNumber = key, + Episodes = g.ToList() + } + ); + + // Group the episodes + foreach (var epInformation in results.OrderBy(x => x.SeasonNumber)) + { + var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList(); + var epSb = new StringBuilder(); + for (var i = 0; i < orderedEpisodes.Count; i++) + { + var ep = orderedEpisodes[i]; + if (i < orderedEpisodes.Count - 1) + { + epSb.Append($"{ep.EpisodeNumber},"); + } + else + { + epSb.Append($"{ep.EpisodeNumber}"); + } + + } + AddParagraph(sb, $"Season: {epInformation.SeasonNumber}, Episode: {epSb}"); + } + + if (info.genres.Any()) + { + AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}"); + } + + AddParagraph(sb, info.summary); + } + catch (Exception e) + { + //Log.Error(e); + } + finally + { + EndLoopHtml(sb); + } + } + sb.Append("


"); + } + + private void EndLoopHtml(StringBuilder sb) + { + //NOTE: BR have to be in TD's as per html spec or it will be put outside of the table... + //Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag + sb.Append("
"); + sb.Append("
"); + sb.Append("
"); + sb.Append(""); + sb.Append(""); + } + + protected bool ValidateConfiguration(EmailNotificationSettings settings) + { + if (!settings.Enabled) + { + return false; + } + if (settings.Authentication) + { + if (string.IsNullOrEmpty(settings.Username) || string.IsNullOrEmpty(settings.Password)) + { + return false; + } + } + if (string.IsNullOrEmpty(settings.Host) || string.IsNullOrEmpty(settings.AdminEmail) || string.IsNullOrEmpty(settings.Port.ToString())) + { + return false; + } + + return true; + } + + private bool _disposed; + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _plex?.Dispose(); + _emby?.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs index 5f5dd4635..225efb7d3 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs @@ -218,12 +218,6 @@ namespace Ombi.Schedule.Jobs.Ombi } return string.Empty; } - - - private async Task StartEmby() - { - - } private bool _disposed; @@ -236,6 +230,7 @@ namespace Ombi.Schedule.Jobs.Ombi { _plexRepo?.Dispose(); _embyRepo?.Dispose(); + _plexSettings?.Dispose(); } _disposed = true; } diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index 64f23669e..55d7db563 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -41,5 +41,6 @@ namespace Ombi.Store.Context DbSet SickRageCache { get; set; } DbSet SickRageEpisodeCache { get; set; } DbSet RequestLogs { get; set; } + DbSet RecentlyAddedLogs { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 0ccf8ebe5..65c212d2b 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -15,7 +15,7 @@ namespace Ombi.Store.Context public OmbiContext() { if (_created) return; - + _created = true; Database.Migrate(); } @@ -37,6 +37,7 @@ namespace Ombi.Store.Context public DbSet IssueCategories { get; set; } public DbSet IssueComments { get; set; } public DbSet RequestLogs { get; set; } + public DbSet RecentlyAddedLogs { get; set; } public DbSet Audit { get; set; } @@ -55,7 +56,7 @@ namespace Ombi.Store.Context { i.StoragePath = string.Empty; } - optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath,"Ombi.db")}"); + optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath, "Ombi.db")}"); } protected override void OnModelCreating(ModelBuilder builder) @@ -70,7 +71,7 @@ namespace Ombi.Store.Context .WithMany(b => b.Episodes) .HasPrincipalKey(x => x.EmbyId) .HasForeignKey(p => p.ParentId); - + base.OnModelCreating(builder); } @@ -218,6 +219,16 @@ namespace Ombi.Store.Context break; case NotificationType.AdminNote: continue; + case NotificationType.Newsletter: + notificationToAdd = new NotificationTemplates + { + NotificationType = notificationType, + Message = "Here is a list of Movies and TV Shows that have recently been added!", + Subject = "{ApplicationName}: Recently Added Content!", + Agent = agent, + Enabled = true, + }; + break; default: throw new ArgumentOutOfRangeException(); } diff --git a/src/Ombi.Store/Entities/PlexServerContent.cs b/src/Ombi.Store/Entities/PlexServerContent.cs index f62dc7ff9..14028cb57 100644 --- a/src/Ombi.Store/Entities/PlexServerContent.cs +++ b/src/Ombi.Store/Entities/PlexServerContent.cs @@ -56,6 +56,15 @@ namespace Ombi.Store.Entities public int Key { get; set; } public DateTime AddedAt { get; set; } public string Quality { get; set; } + + [NotMapped] + public bool HasImdb => !string.IsNullOrEmpty(ImdbId); + + [NotMapped] + public bool HasTvDb => !string.IsNullOrEmpty(TvDbId); + + [NotMapped] + public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId); } [Table("PlexSeasonsContent")] diff --git a/src/Ombi.Store/Entities/RecentlyAddedLog.cs b/src/Ombi.Store/Entities/RecentlyAddedLog.cs new file mode 100644 index 000000000..52b047993 --- /dev/null +++ b/src/Ombi.Store/Entities/RecentlyAddedLog.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities +{ + [Table("RecentlyAddedLog")] + public class RecentlyAddedLog : Entity + { + public RecentlyAddedType Type { get; set; } + public int ContentId { get; set; } // This is dependant on the type + public DateTime AddedAt { get; set; } + } + + public enum RecentlyAddedType + { + Plex, + Emby + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.Designer.cs b/src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.Designer.cs new file mode 100644 index 000000000..23f23bc4b --- /dev/null +++ b/src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.Designer.cs @@ -0,0 +1,934 @@ +// +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("20180322085345_RecentlyAddedLog")] + partial class RecentlyAddedLog + { + 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("ProviderId"); + + b.Property("Title"); + + 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("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + 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("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/20180322085345_RecentlyAddedLog.cs b/src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.cs new file mode 100644 index 000000000..0e815a06f --- /dev/null +++ b/src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class RecentlyAddedLog : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RecentlyAddedLog", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AddedAt = table.Column(nullable: false), + ContentId = table.Column(nullable: false), + Type = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RecentlyAddedLog", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RecentlyAddedLog"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 35c5755da..7571c6f11 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -20,7 +20,7 @@ namespace Ombi.Store.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { @@ -430,6 +430,22 @@ namespace Ombi.Store.Migrations b.ToTable("RadarrCache"); }); + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => { b.Property("Id") diff --git a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts index d540a6b2d..40025486f 100644 --- a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts +++ b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts @@ -79,19 +79,19 @@ export class RecentlyAddedComponent implements OnInit { this.recentlyAddedService.getRecentlyAddedTv().subscribe(x => { this.tv = x; - this.tv.forEach((t) => { - if(t.theMovieDbId) { - this.imageService.getTvPoster(t.imdbId).subscribe(p => { - t.posterPath = p; - }); - } else if(t.imdbId) { - this.imageService.getMoviePoster(t.imdbId).subscribe(p => { - t.posterPath = p; - }); - } else { - t.posterPath = ""; - } - }); + //this.tv.forEach((t) => { + // if(t.theMovieDbId) { + // this.imageService.getTvPoster(t.imdbId).subscribe(p => { + // t.posterPath = p; + // }); + // } else if(t.imdbId) { + // this.imageService.getMoviePoster(t.imdbId).subscribe(p => { + // t.posterPath = p; + // }); + // } else { + // t.posterPath = ""; + // } + //}); }); } diff --git a/src/Ombi/webpack.config.vendor.ts b/src/Ombi/webpack.config.vendor.ts index d4fe38c3a..1fdacb684 100644 --- a/src/Ombi/webpack.config.vendor.ts +++ b/src/Ombi/webpack.config.vendor.ts @@ -80,7 +80,7 @@ module.exports = (env: any) => { }, plugins: [ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", Hammer: "hammerjs/hammer" }), // Global identifiers - new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname, './client')), // Workaround for https://github.com/angular/angular/issues/20357 + new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname,"./client")), // Workaround for https://github.com/angular/angular/issues/20357 new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/11580 new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/14898 extractCSS, From 1528cdfc037336a2596301647dd3fac637a3127b Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 22 Mar 2018 12:27:01 +0000 Subject: [PATCH 21/54] Pretty much finished the actual newsletter. Still need to work on the UI !wip --- src/Ombi.Core/Engine/IRecentlyAddedEngine.cs | 2 + src/Ombi.Core/Engine/RecentlyAddedEngine.cs | 68 +++++++- .../UI/NewsletterNotificationViewModel.cs | 23 +++ src/Ombi.Helpers/OmbiRoles.cs | 1 + .../NotificationMessageCurlys.cs | 7 + src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 154 ++++++++++++++++-- .../Notifications/NewsletterSettings.cs | 7 + src/Ombi.Store/Entities/RecentlyAddedLog.cs | 11 +- .../INotificationTemplatesRepository.cs | 5 +- .../NotificationTemplatesRepository.cs | 21 +++ .../app/interfaces/INotificationSettings.ts | 4 + .../app/services/settings.service.ts | 14 ++ .../notifications/newsletter.component.html | 52 ++++++ .../notifications/newsletter.component.ts | 71 ++++++++ src/Ombi/Controllers/SettingsController.cs | 47 +++++- 15 files changed, 463 insertions(+), 24 deletions(-) create mode 100644 src/Ombi.Core/Models/UI/NewsletterNotificationViewModel.cs create mode 100644 src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs create mode 100644 src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html create mode 100644 src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts diff --git a/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs b/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs index c119abbb4..3087ec829 100644 --- a/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs +++ b/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Ombi.Core.Models; namespace Ombi.Core.Engine @@ -10,5 +11,6 @@ namespace Ombi.Core.Engine IEnumerable GetRecentlyAddedMovies(DateTime from, DateTime to); IEnumerable GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason); IEnumerable GetRecentlyAddedTv(bool groupBySeason); + Task UpdateRecentlyAddedDatabase(); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs index b8cc0ee6b..61866af9d 100644 --- a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs +++ b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs @@ -1,27 +1,28 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Ombi.Core.Models; using Ombi.Helpers; using Ombi.Store.Entities; using Ombi.Store.Repository; +using RecentlyAddedType = Ombi.Store.Entities.RecentlyAddedType; namespace Ombi.Core.Engine { public class RecentlyAddedEngine : IRecentlyAddedEngine { - public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby) + public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository recentlyAdded) { _plex = plex; _emby = emby; + _recentlyAddedLog = recentlyAdded; } private readonly IPlexContentRepository _plex; private readonly IEmbyContentRepository _emby; + private readonly IRepository _recentlyAddedLog; public IEnumerable GetRecentlyAddedMovies(DateTime from, DateTime to) { @@ -55,6 +56,67 @@ namespace Ombi.Core.Engine return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason); } + public async Task UpdateRecentlyAddedDatabase() + { + var plexContent = _plex.GetAll().Include(x => x.Episodes); + var embyContent = _emby.GetAll().Include(x => x.Episodes); + var recentlyAddedLog = new HashSet(); + foreach (var p in plexContent) + { + if (p.Type == PlexMediaTypeEntity.Movie) + { + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentId = p.Id + }); + } + else + { + // Add the episodes + foreach (var ep in p.Episodes) + { + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentId = ep.Id + }); + } + } + } + + foreach (var e in embyContent) + { + if (e.Type == EmbyMediaType.Movie) + { + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Emby, + ContentId = e.Id + }); + } + else + { + // Add the episodes + foreach (var ep in e.Episodes) + { + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentId = ep.Id + }); + } + } + } + await _recentlyAddedLog.AddRange(recentlyAddedLog); + + return true; + } + private IEnumerable GetRecentlyAddedTv(IQueryable plexTv, IQueryable embyTv, bool groupBySeason) { diff --git a/src/Ombi.Core/Models/UI/NewsletterNotificationViewModel.cs b/src/Ombi.Core/Models/UI/NewsletterNotificationViewModel.cs new file mode 100644 index 000000000..a40044670 --- /dev/null +++ b/src/Ombi.Core/Models/UI/NewsletterNotificationViewModel.cs @@ -0,0 +1,23 @@ + +using System.Collections.Generic; +using Ombi.Settings.Settings.Models.Notifications; +using Ombi.Store.Entities; + +namespace Ombi.Core.Models.UI +{ + /// + /// The view model for the notification settings page + /// + /// + public class NewsletterNotificationViewModel : NewsletterSettings + { + /// + /// Gets or sets the notification templates. + /// + /// + /// The notification templates. + /// + public NotificationTemplates NotificationTemplate { get; set; } + + } +} diff --git a/src/Ombi.Helpers/OmbiRoles.cs b/src/Ombi.Helpers/OmbiRoles.cs index 1b88b5d67..e7527279d 100644 --- a/src/Ombi.Helpers/OmbiRoles.cs +++ b/src/Ombi.Helpers/OmbiRoles.cs @@ -9,5 +9,6 @@ public const string RequestTv = nameof(RequestTv); public const string RequestMovie = nameof(RequestMovie); public const string Disabled = nameof(Disabled); + public const string RecievesNewsletter = nameof(RecievesNewsletter); } } \ No newline at end of file diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index d958cfc74..9dd3cc486 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -38,6 +38,13 @@ namespace Ombi.Notifications AdditionalInformation = opts?.AdditionalInformation ?? string.Empty; } + public void SetupNewsletter(CustomizationSettings s, string username) + { + ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; + ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; + RequestedUser = username; + } + public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s) { LoadIssues(opts); diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 544bbc6dd..693c8ae7a 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; @@ -22,7 +24,8 @@ namespace Ombi.Schedule.Jobs.Ombi { public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository addedLog, IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService custom, - ISettingsService emailSettings, INotificationTemplatesRepository templateRepo) + ISettingsService emailSettings, INotificationTemplatesRepository templateRepo, + UserManager um, ISettingsService newsletter) { _plex = plex; _emby = emby; @@ -33,6 +36,8 @@ namespace Ombi.Schedule.Jobs.Ombi _customizationSettings = custom; _templateRepo = templateRepo; _emailSettings = emailSettings; + _newsletterSettings = newsletter; + _userManager = um; } private readonly IPlexContentRepository _plex; @@ -44,9 +49,16 @@ namespace Ombi.Schedule.Jobs.Ombi private readonly ISettingsService _customizationSettings; private readonly INotificationTemplatesRepository _templateRepo; private readonly ISettingsService _emailSettings; + private readonly ISettingsService _newsletterSettings; + private readonly UserManager _userManager; public async Task Start() { + var newsletterSettings = await _newsletterSettings.GetSettingsAsync(); + if (!newsletterSettings.Enabled) + { + return; + } var template = await _templateRepo.GetTemplate(NotificationAgent.Email, NotificationType.Newsletter); if (!template.Enabled) { @@ -59,40 +71,146 @@ namespace Ombi.Schedule.Jobs.Ombi return; } + var customization = await _customizationSettings.GetSettingsAsync(); + // Get the Content var plexContent = _plex.GetAll().Include(x => x.Episodes); var embyContent = _emby.GetAll().Include(x => x.Episodes); var addedLog = _recentlyAddedLog.GetAll(); - var addedPlexLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex).Select(x => x.ContentId); - var addedEmbyLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby).Select(x => x.ContentId); + var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + + var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode).Select(x => x.ContentId); + var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode).Select(x => x.ContentId); // Filter out the ones that we haven't sent yet - var plexContentToSend = plexContent.Where(x => !addedPlexLogIds.Contains(x.Id)); - var embyContentToSend = embyContent.Where(x => !addedEmbyLogIds.Contains(x.Id)); + var plexContentMoviesToSend = plexContent.Where(x => !addedPlexMovieLogIds.Contains(x.Id)); + var embyContentMoviesToSend = embyContent.Where(x => !addedEmbyMoviesLogIds.Contains(x.Id)); + + var plexContentTvToSend = plexContent.Where(x => x.Episodes.Any(e => !addedPlexEpisodesLogIds.Contains(e.Id))); + var embyContentTvToSend = embyContent.Where(x => x.Episodes.Any(e => !addedEmbyEpisodesLogIds.Contains(e.Id))); + + var plexContentToSend = plexContentMoviesToSend.Union(plexContentTvToSend); + var embyContentToSend = embyContentMoviesToSend.Union(embyContentTvToSend); + var body = await BuildHtml(plexContentToSend, embyContentToSend); + // Get the users to send it to + var users = await _userManager.GetUsersInRoleAsync(OmbiRoles.RecievesNewsletter); + if (!users.Any()) + { + return; + } + var emailTasks = new List(); + foreach (var user in users) + { + if (user.Email.IsNullOrEmpty()) + { + continue; + } + + var html = LoadTemplate(body, template, customization, user.Alias); + + emailTasks.Add(_email.Send(new NotificationMessage { Message = html, Subject = template.Subject, To = user.Email }, emailSettings)); + } + + // Now add all of this to the Recently Added log + var recentlyAddedLog = new HashSet(); + foreach (var p in plexContentMoviesToSend) + { + if (p.Type == PlexMediaTypeEntity.Movie) + { + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentId = p.Id + }); + } + else + { + // Add the episodes + foreach (var ep in p.Episodes) + { + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentId = ep.Id + }); + } + } + } + + foreach (var e in embyContentMoviesToSend) + { + if (e.Type == EmbyMediaType.Movie) + { + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Emby, + ContentId = e.Id + }); + } + else + { + // Add the episodes + foreach (var ep in e.Episodes) + { + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentId = ep.Id + }); + } + } + } + await _recentlyAddedLog.AddRange(recentlyAddedLog); + + await Task.WhenAll(emailTasks.ToArray()); + } + + private string LoadTemplate(string body, NotificationTemplates template, CustomizationSettings settings, string username) + { var email = new NewsletterTemplate(); - var customization = await _customizationSettings.GetSettingsAsync(); + var resolver = new NotificationMessageResolver(); + var curlys = new NotificationMessageCurlys(); + + curlys.SetupNewsletter(settings, username); - var html = email.LoadTemplate(template.Subject, template.Message, body, customization.Logo); + var parsed = resolver.ParseMessage(template, curlys); - await _email.Send(new NotificationMessage {Message = html, Subject = template.Subject, To = "tidusjar@gmail.com"}, emailSettings); + var html = email.LoadTemplate(parsed.Subject, parsed.Message, body, settings.Logo); + + return html; } private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend) { var sb = new StringBuilder(); - sb.Append("

New Movies:



"); - await ProcessPlexMovies(plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie), sb); - await ProcessEmbyMovies(embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie), sb); + var plexMovies = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie); + var embyMovies = embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie); + if (plexMovies.Any() || embyMovies.Any()) + { + sb.Append("

New Movies:



"); + await ProcessPlexMovies(plexMovies, sb); + await ProcessEmbyMovies(embyMovies, sb); + } - sb.Append("

New Episodes:



"); - await ProcessPlexTv(plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Show), sb); - await ProcessEmbyMovies(embyContentToSend.Where(x => x.Type == EmbyMediaType.Series), sb); + var plexTv = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Show); + var embyTv = embyContentToSend.Where(x => x.Type == EmbyMediaType.Series); + if (plexTv.Any() || embyTv.Any()) + { + sb.Append("

New Episodes:



"); + await ProcessPlexTv(plexTv, sb); + await ProcessEmbyMovies(embyTv, sb); + } return sb.ToString(); } @@ -330,7 +448,7 @@ namespace Ombi.Schedule.Jobs.Ombi sb.Append(""); sb.Append( ""); - + Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/"); Header(sb, 3, t.Title); EndTag(sb, "a"); @@ -426,6 +544,12 @@ namespace Ombi.Schedule.Jobs.Ombi { _plex?.Dispose(); _emby?.Dispose(); + _newsletterSettings?.Dispose(); + _customizationSettings?.Dispose(); + _emailSettings.Dispose(); + _recentlyAddedLog.Dispose(); + _templateRepo?.Dispose(); + _userManager?.Dispose(); } _disposed = true; } diff --git a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs new file mode 100644 index 000000000..380e2d743 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs @@ -0,0 +1,7 @@ +namespace Ombi.Settings.Settings.Models.Notifications +{ + public class NewsletterSettings : Settings + { + public bool Enabled { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/RecentlyAddedLog.cs b/src/Ombi.Store/Entities/RecentlyAddedLog.cs index 52b047993..ba26eb566 100644 --- a/src/Ombi.Store/Entities/RecentlyAddedLog.cs +++ b/src/Ombi.Store/Entities/RecentlyAddedLog.cs @@ -7,13 +7,20 @@ namespace Ombi.Store.Entities public class RecentlyAddedLog : Entity { public RecentlyAddedType Type { get; set; } + public ContentType ContentType { get; set; } public int ContentId { get; set; } // This is dependant on the type public DateTime AddedAt { get; set; } } public enum RecentlyAddedType { - Plex, - Emby + Plex = 0, + Emby = 1 + } + + public enum ContentType + { + Parent = 0, + Episode = 1 } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs b/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs index 861c6b9bd..2ef0e09cf 100644 --- a/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs +++ b/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Ombi.Helpers; @@ -6,7 +7,7 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public interface INotificationTemplatesRepository + public interface INotificationTemplatesRepository : IDisposable { IQueryable All(); Task> GetAllTemplates(); diff --git a/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs index e4b484967..de892e934 100644 --- a/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs +++ b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs @@ -60,5 +60,26 @@ namespace Ombi.Store.Repository await Db.SaveChangesAsync().ConfigureAwait(false); return settings.Entity; } + + private bool _disposed; + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + Db?.Dispose(); + } + + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts index 4446f6340..67a2594d2 100644 --- a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts @@ -54,6 +54,10 @@ export interface IDiscordNotifcationSettings extends INotificationSettings { notificationTemplates: INotificationTemplates[]; } +export interface INewsletterNotificationSettings extends INotificationSettings { + notificationTemplate: INotificationTemplates; +} + export interface ITelegramNotifcationSettings extends INotificationSettings { botApi: string; chatId: string; diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index 54f3458d5..f6f770a19 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -20,6 +20,7 @@ import { ILandingPageSettings, IMattermostNotifcationSettings, IMobileNotifcationSettings, + INewsletterNotificationSettings, IOmbiSettings, IPlexSettings, IPushbulletNotificationSettings, @@ -265,4 +266,17 @@ export class SettingsService extends ServiceHelpers { return this.http .post(`${this.url}/issues`, JSON.stringify(settings), {headers: this.headers}); } + + public getNewsletterSettings(): Observable { + return this.http.get(`${this.url}/notifications/newsletter`, {headers: this.headers}); + } + + public updateNewsletterDatabase(): Observable { + return this.http.post(`${this.url}/notifications/newsletterdatabase`, {headers: this.headers}); + } + + public saveNewsletterSettings(settings: INewsletterNotificationSettings): Observable { + return this.http + .post(`${this.url}/notifications/newsletter`, JSON.stringify(settings), {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html new file mode 100644 index 000000000..5f663dd20 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html @@ -0,0 +1,52 @@ + + +
+
+ Newsletter +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ + +
+
+
+
+ + +
+ +
+
+
\ 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 new file mode 100644 index 000000000..7742e6f4e --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts @@ -0,0 +1,71 @@ +import { Component, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup } from "@angular/forms"; + +import { INewsletterNotificationSettings, INotificationTemplates, NotificationType } from "../../interfaces"; +import { TesterService } from "../../services"; +import { NotificationService } from "../../services"; +import { SettingsService } from "../../services"; + +@Component({ + templateUrl: "./newsletter.component.html", +}) +export class NewsletterComponent implements OnInit { + + public NotificationType = NotificationType; + public template: INotificationTemplates; + public form: FormGroup; + + constructor(private settingsService: SettingsService, + private notificationService: NotificationService, + private fb: FormBuilder, + private testerService: TesterService) { } + + public ngOnInit() { + this.settingsService.getNewsletterSettings().subscribe(x => { + this.template = x.notificationTemplate; + + this.form = this.fb.group({ + enabled: [x.enabled], + }); + }); + } + + public updateDatabase() { + this.settingsService.updateNewsletterDatabase().subscribe(); + } + + public onSubmit(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + + const settings = form.value; + settings.notificationTemplate = this.template; + + this.settingsService.saveNewsletterSettings(settings).subscribe(x => { + if (x) { + this.notificationService.success("Successfully saved the Newsletter settings"); + } else { + this.notificationService.error("There was an error when saving the Newsletter settings"); + } + }); + + } + + public test(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + + this.testerService.discordTest(form.value).subscribe(x => { + if (x) { + this.notificationService.success("Successfully sent a Discord message, please check the discord channel"); + } else { + this.notificationService.error("There was an error when sending the Discord message. Please check your settings"); + } + }); + + } +} diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index c0148c1bb..7970252a3 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -32,6 +32,7 @@ using Ombi.Settings.Settings.Models.Notifications; using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Api.Github; +using Ombi.Core.Engine; namespace Ombi.Controllers { @@ -60,7 +61,8 @@ namespace Ombi.Controllers IEmbyApi embyApi, IRadarrSync radarrSync, ICacheService memCache, - IGithubApi githubApi) + IGithubApi githubApi, + IRecentlyAddedEngine engine) { SettingsResolver = resolver; Mapper = mapper; @@ -78,6 +80,7 @@ namespace Ombi.Controllers private readonly IRadarrSync _radarrSync; private readonly ICacheService _cache; private readonly IGithubApi _githubApi; + private readonly IRecentlyAddedEngine _recentlyAdded; ///
/// Gets the Ombi settings. @@ -865,13 +868,53 @@ namespace Ombi.Controllers return model; } + /// + /// Saves the Newsletter notification settings. + /// + /// The model. + /// + [HttpPost("notifications/newsletter")] + public async Task NewsletterSettings([FromBody] NewsletterNotificationViewModel model) + { + // Save the email settings + var settings = Mapper.Map(model); + var result = await Save(settings); + + // Save the templates + await TemplateRepository.Update(model.NotificationTemplate); + + return result; + } + + [ApiExplorerSettings(IgnoreApi = true)] + [HttpPost("notifications/newsletterdatabase")] + public async Task UpdateNewsletterDatabase() + { + return await _recentlyAdded.UpdateRecentlyAddedDatabase(); + } + + /// + /// Gets the Newsletter Notification Settings. + /// + /// + [HttpGet("notifications/newsletter")] + public async Task NewsletterSettings() + { + var settings = await Get(); + var model = Mapper.Map(settings); + + // Lookup to see if we have any templates saved + var templates = await BuildTemplates(NotificationAgent.Email); + model.NotificationTemplate = templates.FirstOrDefault(x => x.NotificationType == NotificationType.Newsletter); + return model; + } + private async Task> BuildTemplates(NotificationAgent agent) { var templates = await TemplateRepository.GetAllTemplates(agent); return templates.OrderBy(x => x.NotificationType.ToString()).ToList(); } - private async Task Get() { var settings = SettingsResolver.Resolve(); From b99a5a668bedc550054c72fec1e29ebae32ad81b Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 22 Mar 2018 13:00:25 +0000 Subject: [PATCH 22/54] Think i've finished the ui now too !wip needs testing --- src/Ombi.Mapping/Profiles/SettingsProfile.cs | 1 + .../Settings/Models/CustomizationSettings.cs | 1 + .../INotificationTemplatesRepository.cs | 4 +-- .../NotificationTemplatesRepository.cs | 8 +++--- src/Ombi/ClientApp/app/app.component.html | 15 ++++++----- .../app/interfaces/INotificationSettings.ts | 1 + .../ClientApp/app/interfaces/ISettings.ts | 1 + .../customization.component.html | 7 +++++ .../ClientApp/app/settings/settings.module.ts | 4 +++ .../app/settings/settingsmenu.component.html | 2 +- src/Ombi/Controllers/SettingsController.cs | 27 +++++++++++-------- 11 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/Ombi.Mapping/Profiles/SettingsProfile.cs b/src/Ombi.Mapping/Profiles/SettingsProfile.cs index 62232ee19..139290f2b 100644 --- a/src/Ombi.Mapping/Profiles/SettingsProfile.cs +++ b/src/Ombi.Mapping/Profiles/SettingsProfile.cs @@ -18,6 +18,7 @@ namespace Ombi.Mapping.Profiles CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs b/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs index 16d6245f5..515c2fc85 100644 --- a/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs +++ b/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs @@ -18,6 +18,7 @@ namespace Ombi.Settings.Settings.Models public string PresetThemeName { get; set; } public string PresetThemeContent { get; set; } + public bool RecentlyAddedPage { get; set; } [NotMapped] public string PresetThemeVersion diff --git a/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs b/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs index 2ef0e09cf..2398158db 100644 --- a/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs +++ b/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs @@ -10,8 +10,8 @@ namespace Ombi.Store.Repository public interface INotificationTemplatesRepository : IDisposable { IQueryable All(); - Task> GetAllTemplates(); - Task> GetAllTemplates(NotificationAgent agent); + IQueryable GetAllTemplates(); + IQueryable GetAllTemplates(NotificationAgent agent); Task Insert(NotificationTemplates entity); Task Update(NotificationTemplates template); Task UpdateRange(IEnumerable template); diff --git a/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs index de892e934..3822f8001 100644 --- a/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs +++ b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs @@ -23,14 +23,14 @@ namespace Ombi.Store.Repository return Db.NotificationTemplates.AsQueryable(); } - public async Task> GetAllTemplates() + public IQueryable GetAllTemplates() { - return await Db.NotificationTemplates.ToListAsync(); + return Db.NotificationTemplates; } - public async Task> GetAllTemplates(NotificationAgent agent) + public IQueryable GetAllTemplates(NotificationAgent agent) { - return await Db.NotificationTemplates.Where(x => x.Agent == agent).ToListAsync(); + return Db.NotificationTemplates.Where(x => x.Agent == agent); } public async Task GetTemplate(NotificationAgent agent, NotificationType type) diff --git a/src/Ombi/ClientApp/app/app.component.html b/src/Ombi/ClientApp/app/app.component.html index bc44c3b49..0db13fa9c 100644 --- a/src/Ombi/ClientApp/app/app.component.html +++ b/src/Ombi/ClientApp/app/app.component.html @@ -34,13 +34,14 @@ {{ 'NavigationBar.Requests' | translate }} - - +