diff --git a/appveyor.yml b/appveyor.yml index 163d75c2c..44c8dcfc4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,7 +12,6 @@ before_build: - cmd: cd src/ombi - node --version - appveyor-retry dotnet restore -- appveyor-retry npm install bower -g - appveyor-retry npm install -g gulp - appveyor-retry npm install - gulp publish diff --git a/src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs b/src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs index 801472f4f..ec8f24d2d 100644 --- a/src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs +++ b/src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs @@ -16,7 +16,7 @@ namespace Ombi.Core.Tests.Engine { RequestService = new Mock>(); var requestService = new RequestService(null, RequestService.Object); - Engine = new MovieRequestEngine(null, requestService, null, null, null); + Engine = new MovieRequestEngine(null, requestService, null, null, null, null, null); } private MovieRequestEngine Engine { get; } diff --git a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs index 07d438278..e2041a57c 100644 --- a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs @@ -3,6 +3,7 @@ using Moq; using Ombi.Core.Claims; using Ombi.Core.Models.Requests; using Ombi.Core.Rule.Rules; +using Ombi.Core.Rule.Rules.Request; using Xunit; namespace Ombi.Core.Tests.Rule diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs index 75b448372..67768bd2c 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs @@ -17,5 +17,6 @@ namespace Ombi.Core Task> UpcomingMovies(); Task> LookupImdbInformation(IEnumerable movies); + Task LookupImdbInformation(int theMovieDbId); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 61358cecf..68cd27f57 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -96,7 +96,7 @@ namespace Ombi.Core.Engine Status = movieInfo.Status, RequestedDate = DateTime.UtcNow, Approved = false, - RequestedUsers = new List { Username }, + RequestedUser =Username, Issues = IssueState.None }; @@ -191,7 +191,7 @@ namespace Ombi.Core.Engine results.OtherMessage = request.OtherMessage; results.Overview = request.Overview; results.PosterPath = request.PosterPath; - results.RequestedUsers = request.RequestedUsers?.ToList() ?? new List(); + results.RequestedUser = request.RequestedUser; var model = MovieRequestService.UpdateRequest(results); return model; @@ -202,21 +202,6 @@ namespace Ombi.Core.Engine await MovieRequestService.DeleteRequestAsync(requestId); } - private IEnumerable GetListDifferences(IEnumerable existing, - IEnumerable request) - { - var newRequest = request - .Select(r => - new EpisodesModel - { - SeasonNumber = r.SeasonNumber, - EpisodeNumber = r.EpisodeNumber - }) - .ToList(); - - return newRequest.Except(existing); - } - private async Task AddMovieRequest(MovieRequestModel model, string message) { await MovieRequestService.AddRequestAsync(model); diff --git a/src/Ombi.Core/Engine/MovieSearchEngine.cs b/src/Ombi.Core/Engine/MovieSearchEngine.cs index 9f5e1eb43..e94ec8e8e 100644 --- a/src/Ombi.Core/Engine/MovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/MovieSearchEngine.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Threading.Tasks; +using StackExchange.Profiling; namespace Ombi.Core.Engine { @@ -63,17 +64,41 @@ namespace Ombi.Core.Engine return retVal; } + public async Task LookupImdbInformation(int theMovieDbId) + { + var dbMovies = await GetMovieRequests(); + + var plexSettings = await PlexSettings.GetSettingsAsync(); + var embySettings = await EmbySettings.GetSettingsAsync(); + + var movieInfo = await MovieApi.GetMovieInformationWithVideo(theMovieDbId); + var viewMovie = Mapper.Map(movieInfo); + + return await ProcessSingleMovie(viewMovie, dbMovies, plexSettings, embySettings, true); + } + public async Task> Search(string search) { - var result = await MovieApi.SearchMovie(search); - if (result != null) + using (MiniProfiler.Current.Step("Starting Movie Search Engine")) + using (MiniProfiler.Current.Step("Searching Movie")) { - Logger.LogDebug("Search Result: {result}", result); - return await TransformMovieResultsToResponse(result); + var result = await MovieApi.SearchMovie(search); + + using (MiniProfiler.Current.Step("Fin API, Transforming")) + { + if (result != null) + { + Logger.LogDebug("Search Result: {result}", result); + return await TransformMovieResultsToResponse(result); + } + } + + + return null; } - return null; } + public async Task> PopularMovies() { var result = await MovieApi.PopularMovies(); @@ -121,28 +146,41 @@ namespace Ombi.Core.Engine private async Task> TransformMovieResultsToResponse( IEnumerable movies) { + var viewMovies = new List(); - var dbMovies = await GetMovieRequests(); + Dictionary dbMovies; + Settings.Models.External.PlexSettings plexSettings; + Settings.Models.External.EmbySettings embySettings; + using (MiniProfiler.Current.Step("Gettings Movies and Settings")) + { + dbMovies = await GetMovieRequests(); - var plexSettings = await PlexSettings.GetSettingsAsync(); - var embySettings = await EmbySettings.GetSettingsAsync(); + plexSettings = await PlexSettings.GetSettingsAsync(); + embySettings = await EmbySettings.GetSettingsAsync(); + } foreach (var movie in movies) + { viewMovies.Add(await ProcessSingleMovie(movie, dbMovies, plexSettings, embySettings)); + } return viewMovies; } private async Task ProcessSingleMovie(SearchMovieViewModel viewMovie, - Dictionary existingRequests, PlexSettings plexSettings, EmbySettings embySettings) + Dictionary existingRequests, PlexSettings plexSettings, EmbySettings embySettings, bool lookupExtraInfo = false) { - var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id); - viewMovie.Id = showInfo.Id; // TheMovieDbId + if (plexSettings.Enable) { - var item = await PlexContentRepo.Get(showInfo.ImdbId); - if (item != null) + if (lookupExtraInfo) { - viewMovie.Available = true; - viewMovie.PlexUrl = item.Url; + var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id); + viewMovie.Id = showInfo.Id; // TheMovieDbId + var item = await PlexContentRepo.Get(showInfo.ImdbId); + if (item != null) + { + viewMovie.Available = true; + viewMovie.PlexUrl = item.Url; + } } // var content = PlexContentRepository.GetAll(); @@ -180,10 +218,11 @@ namespace Ombi.Core.Engine } RunSearchRules(viewMovie); - + return viewMovie; } + private async Task ProcessSingleMovie(MovieSearchResult movie, Dictionary existingRequests, PlexSettings plexSettings, EmbySettings embySettings) { diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index ab319be75..6ff32684c 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -41,6 +41,25 @@ namespace Ombi.Core.Engine // For some reason the poster path is always http var posterPath = showInfo.image?.medium.Replace("http:", "https:"); + var tvRequests = new List(); + // Only have the TV requests we actually requested and not everything + foreach (var season in tv.SeasonRequests) + { + for (int i = season.Episodes.Count - 1; i >= 0; i--) + { + if (!season.Episodes[i].Requested) + { + season.Episodes.RemoveAt(i); // Remove the episode since it's not requested + } + } + + if (season.Episodes.Any()) + { + tvRequests.Add(season); + } + } + + var childRequest = new ChildTvRequest { Id = tv.Id, @@ -52,11 +71,11 @@ namespace Ombi.Core.Engine Status = showInfo.status, RequestedDate = DateTime.UtcNow, Approved = false, - RequestedUsers = new List { Username }, + RequestedUser = Username, Issues = IssueState.None, ProviderId = tv.Id, RequestAll = tv.RequestAll, - SeasonRequests = tv.SeasonRequests + SeasonRequests = tvRequests }; var model = new TvRequestModel @@ -76,22 +95,22 @@ namespace Ombi.Core.Engine model.ChildRequests.Add(childRequest); - if (childRequest.SeasonRequests.Any()) - { - var episodes = await TvApi.EpisodeLookup(showInfo.id); + //if (childRequest.SeasonRequests.Any()) + //{ + // var episodes = await TvApi.EpisodeLookup(showInfo.id); - foreach (var e in episodes) - { - var season = childRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == e.season); - season?.Episodes.Add(new EpisodesRequested - { - Url = e.url, - Title = e.name, - AirDate = DateTime.Parse(e.airstamp), - EpisodeNumber = e.number - }); - } - } + // foreach (var e in episodes) + // { + // var season = childRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == e.season); + // season?.Episodes.Add(new EpisodesRequested + // { + // Url = e.url, + // Title = e.name, + // AirDate = DateTime.Parse(e.airstamp), + // EpisodeNumber = e.number + // }); + // } + //} if (tv.LatestSeason) { @@ -165,6 +184,8 @@ namespace Ombi.Core.Engine var results = allRequests.FirstOrDefault(x => x.Id == request.Id); results = Mapper.Map(request); + // TODO need to check if we need to approve any child requests since they may have updated + var model = TvRequestService.UpdateRequest(results); return model; } @@ -192,26 +213,52 @@ namespace Ombi.Core.Engine existingRequest.ChildRequests.AddRange(newRequest.ChildRequests); TvRequestService.UpdateRequest(existingRequest); - if (ShouldAutoApprove(RequestType.TvShow)) + if (newRequest.Approved) // The auto approve rule { // TODO Auto Approval Code } return await AfterRequest(newRequest); } - private IEnumerable GetListDifferences(IEnumerable existing, - IEnumerable request) + private IEnumerable GetListDifferences(List existing, + List request) { - var newRequest = request - .Select(r => - new SeasonRequestModel + var requestsToRemove = new List(); + foreach (var r in request) + { + // Do we have an existing season? + var existingSeason = existing.FirstOrDefault(x => x.SeasonNumber == r.SeasonNumber); + if (existingSeason == null) + { + continue; + } + + // Compare the episodes + for (var i = r.Episodes.Count - 1; i >= 0; i--) + { + var existingEpisode = existingSeason.Episodes.FirstOrDefault(x => x.EpisodeNumber == r.Episodes[i].EpisodeNumber); + if (existingEpisode == null) + { + // we are fine, we have not yet requested this + } + else { - SeasonNumber = r.SeasonNumber, - Episodes = r.Episodes - }) - .ToList(); + // We already have this request + r.Episodes.RemoveAt(i); + } + } + + if (!r.Episodes.Any()) + { + requestsToRemove.Add(r); + } + } - return newRequest.Except(existing); + foreach (var remove in requestsToRemove) + { + request.Remove(remove); + } + return request; } private async Task AddRequest(TvRequestModel model) diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index 953c242ec..01e735b9b 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -77,13 +77,12 @@ namespace Ombi.Core.Engine } else { - season.Episodes.Add(new EpisodesRequested - { - Url = e.url, - Title = e.name, - AirDate = DateTime.Parse(e.airstamp), - EpisodeNumber = e.number, - }); + // Find the episode + var ep = season.Episodes.FirstOrDefault(x => x.EpisodeNumber == e.number); + ep.Url = e.url; + ep.Title = e.name; + ep.AirDate = DateTime.Parse(e.airstamp); + ep.EpisodeNumber = e.number; } } @@ -173,6 +172,7 @@ namespace Ombi.Core.Engine // Find the existing request season var existingSeason = existingRequestChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber); + if(existingSeason == null) continue; foreach (var ep in existingSeason.Episodes) { diff --git a/src/Ombi.Core/Models/Requests/BaseRequestModel.cs b/src/Ombi.Core/Models/Requests/BaseRequestModel.cs index ac4732fa9..f40488af5 100644 --- a/src/Ombi.Core/Models/Requests/BaseRequestModel.cs +++ b/src/Ombi.Core/Models/Requests/BaseRequestModel.cs @@ -1,8 +1,6 @@ using Newtonsoft.Json; using Ombi.Store.Entities; using System; -using System.Collections.Generic; -using System.Linq; namespace Ombi.Core.Models.Requests { @@ -22,7 +20,7 @@ namespace Ombi.Core.Models.Requests public IssueState Issues { get; set; } public string OtherMessage { get; set; } public string AdminNote { get; set; } - public List RequestedUsers { get; set; } = new List(); + public string RequestedUser { get; set; } public int IssueId { get; set; } public bool Denied { get; set; } public string DeniedReason { get; set; } @@ -30,24 +28,13 @@ namespace Ombi.Core.Models.Requests [JsonIgnore] public bool Released => DateTime.UtcNow > ReleaseDate; - [JsonIgnore] - public IEnumerable AllUsers - { - get - { - var u = new List(); - if (RequestedUsers != null && RequestedUsers.Any()) - u.AddRange(RequestedUsers); - return u; - } - } [JsonIgnore] public bool CanApprove => !Approved && !Available; - + public bool UserHasRequested(string username) { - return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase)); + return RequestedUser.Equals(username, StringComparison.OrdinalIgnoreCase); } } } \ No newline at end of file diff --git a/src/Ombi.Core/MovieSender.cs b/src/Ombi.Core/MovieSender.cs index 2b8974cbd..8db810101 100644 --- a/src/Ombi.Core/MovieSender.cs +++ b/src/Ombi.Core/MovieSender.cs @@ -66,7 +66,7 @@ namespace Ombi.Core //var rootFolderPath = model.RootFolderSelected <= 0 ? settings.FullRootPath : GetRootPath(model.RootFolderSelected, settings); var rootFolderPath = settings.DefaultRootPath; // TODO Allow changing in the UI - var result = await RadarrApi.AddMovie(model.ProviderId, model.Title, model.ReleaseDate.Year, qualityProfile, rootFolderPath, settings.ApiKey, settings.FullUri, true); + var result = await RadarrApi.AddMovie(model.ProviderId, model.Title, model.ReleaseDate.Year, qualityProfile, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly); if (!string.IsNullOrEmpty(result.Error?.message)) { diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index 4fe9a1753..5748422d0 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs index 3aa4a6b5a..78291e771 100644 --- a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs @@ -1,11 +1,10 @@ -using Ombi.Core.Claims; +using System.Security.Principal; +using Ombi.Core.Claims; using Ombi.Core.Models.Requests; -using Ombi.Core.Rules; -using Ombi.Store.Entities; -using System.Security.Principal; using Ombi.Core.Rule.Interfaces; +using Ombi.Store.Entities; -namespace Ombi.Core.Rule.Rules +namespace Ombi.Core.Rule.Rules.Request { public class AutoApproveRule : BaseRequestRule, IRequestRules { diff --git a/src/Ombi.Notification.Discord/Class1.cs b/src/Ombi.Notification.Agents/DiscordNotification.cs similarity index 99% rename from src/Ombi.Notification.Discord/Class1.cs rename to src/Ombi.Notification.Agents/DiscordNotification.cs index aa693b363..c82fb55d4 100644 --- a/src/Ombi.Notification.Discord/Class1.cs +++ b/src/Ombi.Notification.Agents/DiscordNotification.cs @@ -8,7 +8,7 @@ using Ombi.Notifications; using Ombi.Notifications.Models; using Ombi.Settings.Settings.Models.Notifications; -namespace Ombi.Notification.Discord +namespace Ombi.Notification.Agents { public class DiscordNotification : BaseNotification { diff --git a/src/Ombi.Notification.Agents/EmailNotification.cs b/src/Ombi.Notification.Agents/EmailNotification.cs new file mode 100644 index 000000000..f5986dc34 --- /dev/null +++ b/src/Ombi.Notification.Agents/EmailNotification.cs @@ -0,0 +1,227 @@ +using System; +using System.Threading.Tasks; +using MailKit.Net.Smtp; +using MimeKit; +using Ombi.Core.Models.Requests; +using Ombi.Core.Settings; +using Ombi.Notifications.Models; +using Ombi.Notifications.Templates; +using Ombi.Settings.Settings.Models.Notifications; + +namespace Ombi.Notifications.Email +{ + public class EmailNotification : BaseNotification + { + public EmailNotification(ISettingsService settings) : base(settings) + { + } + + public override string NotificationName => nameof(EmailNotification); + + protected override bool ValidateConfiguration(EmailNotificationSettings settings) + { + if (!settings.Enabled) + { + return false; + } + if (settings.Authentication) + { + if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword)) + { + return false; + } + } + if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) + { + return false; + } + + return true; + } + + protected override async Task NewRequest(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + $"Ombi: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", + $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime:f}", + model.ImgSrc); + + + var message = new NotificationMessage + { + Message = html, + Subject = $"Ombi: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", + To = settings.RecipientEmail, + }; + + message.Other.Add("PlainTextBody", $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime:f}"); + + await Send(message, settings); + } + + protected override async Task Issue(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + $"Ombi: New issue for {model.Title}!", + $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!", + model.ImgSrc); + + var message = new NotificationMessage + { + Message = html, + Subject = $"Ombi: New issue for {model.Title}!", + To = settings.RecipientEmail, + }; + + message.Other.Add("PlainTextBody", $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!"); + + await Send(message, settings); + } + + protected override async Task AddedToRequestQueue(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + "Ombi: A request could not be added.", + $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying", + model.ImgSrc); + + var message = new NotificationMessage + { + Message = html, + Subject = $"Ombi: A request could not be added", + To = settings.RecipientEmail, + }; + + message.Other.Add("PlainTextBody", $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"); + + + await Send(message, settings); + } + + protected override async Task RequestDeclined(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + "Ombi: Your request has been declined", + $"Hello! Your request for {model.Title} has been declined, Sorry!", + model.ImgSrc); + + var message = new NotificationMessage + { + Message = html, + Subject = $"Ombi: Your request has been declined", + To = model.UserEmail, + }; + + message.Other.Add("PlainTextBody", $"Hello! Your request for {model.Title} has been declined, Sorry!"); + + + await Send(message, settings); + } + + protected override async Task RequestApproved(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + "Ombi: Your request has been approved!", + $"Hello! Your request for {model.Title} has been approved!", + model.ImgSrc); + + var message = new NotificationMessage + { + Message = html, + Subject = $"Ombi: Your request has been approved!", + To = model.UserEmail, + }; + + message.Other.Add("PlainTextBody", $"Hello! Your request for {model.Title} has been approved!"); + + await Send(message, settings); + } + + protected override async Task AvailableRequest(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + $"Ombi: {model.Title} is now available!", + $"Hello! You requested {model.Title} on Ombi! This is now available on Plex! :)", + model.ImgSrc); + + + var message = new NotificationMessage + { + Message = html, + Subject = $"Ombi: {model.Title} is now available!", + To = model.UserEmail, + }; + + message.Other.Add("PlainTextBody", $"Hello! You requested {model.Title} on Ombi! This is now available on Plex! :)"); + + await Send(message, settings); + } + + protected override async Task Send(NotificationMessage model, EmailNotificationSettings settings) + { + try + { + var body = new BodyBuilder + { + HtmlBody = model.Message, + TextBody = model.Other["PlainTextBody"] + }; + + var message = new MimeMessage + { + Body = body.ToMessageBody(), + Subject = model.Subject + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(model.To, model.To)); + + using (var client = new SmtpClient()) + { + client.Connect(settings.EmailHost, settings.EmailPort); // Let MailKit figure out the correct SecureSocketOptions. + + // Note: since we don't have an OAuth2 token, disable + // the XOAUTH2 authentication mechanism. + client.AuthenticationMechanisms.Remove("XOAUTH2"); + + if (settings.Authentication) + { + client.Authenticate(settings.EmailUsername, settings.EmailPassword); + } + //Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated); + await client.SendAsync(message); + await client.DisconnectAsync(true); + } + } + catch (Exception e) + { + //Log.Error(e); + throw new InvalidOperationException(e.Message); + } + } + + protected override async Task Test(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + "Test Message", + "This is just a test! Success!", + model.ImgSrc); + var message = new NotificationMessage + { + Message = html, + Subject = $"Ombi: Test", + To = settings.RecipientEmail, + }; + + message.Other.Add("PlainTextBody", "This is just a test! Success!"); + + await Send(message, settings); + } + } +} diff --git a/src/Ombi.Notification.Discord/Ombi.Notification.Discord.csproj b/src/Ombi.Notification.Agents/Ombi.Notification.Agents.csproj similarity index 59% rename from src/Ombi.Notification.Discord/Ombi.Notification.Discord.csproj rename to src/Ombi.Notification.Agents/Ombi.Notification.Agents.csproj index ecacf8bcf..29ae93c81 100644 --- a/src/Ombi.Notification.Discord/Ombi.Notification.Discord.csproj +++ b/src/Ombi.Notification.Agents/Ombi.Notification.Agents.csproj @@ -6,7 +6,10 @@ + + + \ No newline at end of file diff --git a/src/Ombi.Notifications/Ombi.Notifications.csproj b/src/Ombi.Notifications/Ombi.Notifications.csproj index d687e46fd..ea899160a 100644 --- a/src/Ombi.Notifications/Ombi.Notifications.csproj +++ b/src/Ombi.Notifications/Ombi.Notifications.csproj @@ -5,6 +5,11 @@ + + + + + diff --git a/src/Ombi.Schedule/Jobs/PlexContentCacher.cs b/src/Ombi.Schedule/Jobs/PlexContentCacher.cs index 249b22869..c890939dc 100644 --- a/src/Ombi.Schedule/Jobs/PlexContentCacher.cs +++ b/src/Ombi.Schedule/Jobs/PlexContentCacher.cs @@ -77,45 +77,6 @@ namespace Ombi.Schedule.Jobs Logger.LogWarning(LoggingEvents.CacherException, e, "Exception thrown when attempting to cache the Plex Content"); } } - //private List CachedLibraries(PlexSettings plexSettings) - //{ - // var results = new List(); - - // results = GetLibraries(plexSettings); - // foreach (PlexLibraries t in results) - // { - // foreach (var t1 in t.MediaContainer.Directory) - // { - // var currentItem = t1; - // var metaData = PlexApi.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, - // currentItem.ratingKey).Result; - - // // Get the seasons for each show - // if (currentItem.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)) - // { - // var seasons = PlexApi.GetSeasons(plexSettings.PlexAuthToken, plexSettings.FullUri, - // currentItem.ratingKey).Result; - - // // We do not want "all episodes" this as a season - // var filtered = seasons.MediaContainer.Directory.Where(x => !x.title.Equals("All episodes", StringComparison.CurrentCultureIgnoreCase)); - - // t1.seasons.AddRange(filtered); - // } - - // var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer); - // t1.providerId = providerId; - // } - // foreach (Video t1 in t.Video) - // { - // var currentItem = t1; - // var metaData = PlexApi.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, - // currentItem.RatingKey); - // var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Video.Guid); - // t1.ProviderId = providerId; - // } - // } - - //} private async Task StartTheCache(PlexSettings plexSettings) { @@ -154,18 +115,16 @@ namespace Ombi.Schedule.Jobs var itemAdded = false; foreach (var season in seasonsContent) { - var seasonExists = existingContent.Seasons.Where(x => x.SeasonKey == season.SeasonKey); + var seasonExists = existingContent.Seasons.FirstOrDefault(x => x.SeasonKey == season.SeasonKey); if (seasonExists != null) { // We already have this season continue; } - else - { - existingContent.Seasons.Add(season); - itemAdded = true; - } + + existingContent.Seasons.Add(season); + itemAdded = true; } if (itemAdded) await Repo.Update(existingContent); diff --git a/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs index b76e56f6e..919ea0377 100644 --- a/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs @@ -9,5 +9,6 @@ namespace Ombi.Settings.Settings.Models.External public string DefaultQualityProfile { get; set; } public string DefaultRootPath { get; set; } public string FullRootPath { get; set; } + public bool AddOnly { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/PlexContent.cs b/src/Ombi.Store/Entities/PlexContent.cs index f5efa2f5d..211b2e611 100644 --- a/src/Ombi.Store/Entities/PlexContent.cs +++ b/src/Ombi.Store/Entities/PlexContent.cs @@ -44,7 +44,7 @@ namespace Ombi.Store.Entities /// /// Only used for TV Shows /// - public virtual ICollection Seasons { get; set; } + public virtual ICollection Seasons { get; set; } /// /// Plex's internal ID for this item @@ -53,7 +53,8 @@ namespace Ombi.Store.Entities public DateTime AddedAt { get; set; } } - public class SeasonsContent : Entity + [Table("PlexSeasonsContent")] + public class PlexSeasonsContent : Entity { public int PlexContentId { get; set; } public int SeasonNumber { get; set; } diff --git a/src/Ombi.Store/Entities/RequestHistory.cs b/src/Ombi.Store/Entities/RequestHistory.cs new file mode 100644 index 000000000..c8f3cc2c1 --- /dev/null +++ b/src/Ombi.Store/Entities/RequestHistory.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ombi.Store.Entities +{ + public class RequestHistory : Entity + { + public int UserId { get; set; } + public RequestType Type { get; set; } + public DateTime RequestedDate { get; set; } + public int RequestId { get; set; } + + public virtual RequestBlobs Request { get; set; } + public virtual User User { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/SqlTables.sql b/src/Ombi.Store/SqlTables.sql index 3e3f2590a..99c020f87 100644 --- a/src/Ombi.Store/SqlTables.sql +++ b/src/Ombi.Store/SqlTables.sql @@ -23,9 +23,10 @@ CREATE TABLE IF NOT EXISTS RadarrCache TheMovieDbId INTEGER NOT NULL ); -CREATE TABLE IF NOT EXISTS SeasonsContent +CREATE TABLE IF NOT EXISTS PlexSeasonsContent ( Id INTEGER PRIMARY KEY AUTOINCREMENT, + PlexContentId integer not null, SeasonNumber INTEGER NOT NULL, SeasonKey INTEGER NOT NULL, ParentKey INTEGER NOT NULL @@ -52,4 +53,14 @@ CREATE TABLE IF NOT EXISTS Users Salt BLOB NULL, UserType INTEGER NOT NULL -); \ No newline at end of file +); + +CREATE TABLE IF NOT EXISTS RequestHistory +{ + + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Type INTEGER NOT NULL, + RequestedDate varchar(50) NOT NULL, + RequestId INTEGER NOT NULL + +} \ No newline at end of file diff --git a/src/Ombi.sln b/src/Ombi.sln index da95c8add..9d31c29d4 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -49,8 +49,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notifications", "Ombi. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Notifications", "Notifications", "{EA30DD15-6280-4687-B370-2956EC2E54E5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notifications.Email", "Ombi.Notifications.Email\Ombi.Notifications.Email.csproj", "{72DB97D7-2D60-4B96-8C57-6C0E20E892EB}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notifications.Templates", "Ombi.Notifications.Templates\Ombi.Notifications.Templates.csproj", "{6EE01B17-0966-4E11-8BC1-A5318A92AB1D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Settings", "Ombi.Settings\Ombi.Settings.csproj", "{AE3AA23D-5B66-42AF-B44E-B9B4D8856C6F}" @@ -63,7 +61,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Radarr", "Ombi.Api EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notification.Discord", "Ombi.Notification.Discord\Ombi.Notification.Discord.csproj", "{D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notification.Agents", "Ombi.Notification.Agents\Ombi.Notification.Agents.csproj", "{DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -127,10 +125,6 @@ Global {E6EE2830-E4AC-4F2E-AD93-2C9305605761}.Debug|Any CPU.Build.0 = Debug|Any CPU {E6EE2830-E4AC-4F2E-AD93-2C9305605761}.Release|Any CPU.ActiveCfg = Release|Any CPU {E6EE2830-E4AC-4F2E-AD93-2C9305605761}.Release|Any CPU.Build.0 = Release|Any CPU - {72DB97D7-2D60-4B96-8C57-6C0E20E892EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {72DB97D7-2D60-4B96-8C57-6C0E20E892EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {72DB97D7-2D60-4B96-8C57-6C0E20E892EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {72DB97D7-2D60-4B96-8C57-6C0E20E892EB}.Release|Any CPU.Build.0 = Release|Any CPU {6EE01B17-0966-4E11-8BC1-A5318A92AB1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6EE01B17-0966-4E11-8BC1-A5318A92AB1D}.Debug|Any CPU.Build.0 = Debug|Any CPU {6EE01B17-0966-4E11-8BC1-A5318A92AB1D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -155,10 +149,10 @@ Global {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Debug|Any CPU.Build.0 = Debug|Any CPU {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.ActiveCfg = Release|Any CPU {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.Build.0 = Release|Any CPU - {D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}.Release|Any CPU.Build.0 = Release|Any CPU + {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -173,12 +167,11 @@ Global {CFB5E008-D0D0-43C0-AA06-89E49D17F384} = {9293CA11-360A-4C20-A674-B9E794431BF5} {0E8EF835-E4F0-4EE5-A2B6-678DEE973721} = {9293CA11-360A-4C20-A674-B9E794431BF5} {E6EE2830-E4AC-4F2E-AD93-2C9305605761} = {EA30DD15-6280-4687-B370-2956EC2E54E5} - {72DB97D7-2D60-4B96-8C57-6C0E20E892EB} = {EA30DD15-6280-4687-B370-2956EC2E54E5} {6EE01B17-0966-4E11-8BC1-A5318A92AB1D} = {EA30DD15-6280-4687-B370-2956EC2E54E5} {3880375C-1A7E-4D75-96EC-63B954C42FEA} = {9293CA11-360A-4C20-A674-B9E794431BF5} {FC6A8F7C-9722-4AE4-960D-277ACB0E81CB} = {6F42AB98-9196-44C4-B888-D5E409F415A1} {94D04C1F-E35A-499C-B0A0-9FADEBDF8336} = {9293CA11-360A-4C20-A674-B9E794431BF5} {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194} = {9293CA11-360A-4C20-A674-B9E794431BF5} - {D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A} = {EA30DD15-6280-4687-B370-2956EC2E54E5} + {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D} = {EA30DD15-6280-4687-B370-2956EC2E54E5} EndGlobalSection EndGlobal diff --git a/src/Ombi/.gitignore b/src/Ombi/.gitignore index 3c9623388..ae9772467 100644 --- a/src/Ombi/.gitignore +++ b/src/Ombi/.gitignore @@ -2,8 +2,7 @@ /wwwroot/fonts/** /wwwroot/lib/** /wwwroot/maps/** -/wwwroot/app/**/*.js -/wwwroot/app/**/*.js.map +/wwwroot/dist/** /wwwroot/*.js.map /wwwroot/*.js diff --git a/src/Ombi/ClientApp/.gitignore b/src/Ombi/ClientApp/.gitignore new file mode 100644 index 000000000..40d175d32 --- /dev/null +++ b/src/Ombi/ClientApp/.gitignore @@ -0,0 +1,3 @@ +**/*.js +**/*.js.map +**/*.css \ No newline at end of file diff --git a/src/Ombi/wwwroot/app/app.component.html b/src/Ombi/ClientApp/app/app.component.html similarity index 97% rename from src/Ombi/wwwroot/app/app.component.html rename to src/Ombi/ClientApp/app/app.component.html index cdd4cc697..80b50eee4 100644 --- a/src/Ombi/wwwroot/app/app.component.html +++ b/src/Ombi/ClientApp/app/app.component.html @@ -1,4 +1,4 @@ - +