diff --git a/README.md b/README.md index 814bb9bb9..e8e269e7f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ___ Follow me developing Ombi! -[![Twitch](https://img.shields.io/badge/Twitch-Watch-blue.svg?style=flat-square&logo=twitch)](https://twitch.tv/tiusjar) +[![Twitch](https://img.shields.io/badge/Twitch-Watch-blue.svg?style=flat-square&logo=twitch)](https://www.twitch.tv/tidusjar) ___ @@ -68,6 +68,7 @@ We integrate with the following applications: Supported notifications: * SMTP Notifications (Email) * Discord +* Gotify * Slack * Pushbullet * Pushover diff --git a/build.cake b/build.cake index e8e4bb8c0..b2ae8842c 100644 --- a/build.cake +++ b/build.cake @@ -3,7 +3,7 @@ #addin "Cake.Gulp" #addin "SharpZipLib" #addin nuget:?package=Cake.Compression&version=0.1.4 -#addin "Cake.Incubator" +#addin "Cake.Incubator&version=3.1.0" #addin "Cake.Yarn" ////////////////////////////////////////////////////////////////////// @@ -81,9 +81,9 @@ Task("SetVersionInfo") versionInfo = GitVersion(settings); - Information("GitResults -> {0}", versionInfo.Dump()); +// Information("GitResults -> {0}", versionInfo.Dump()); - Information(@"Build:{0}",AppVeyor.Environment.Build.Dump()); +//Information(@"Build:{0}",AppVeyor.Environment.Build.Dump()); var buildVersion = string.Empty; if(string.IsNullOrEmpty(AppVeyor.Environment.Build.Version)) diff --git a/src/Ombi.Api.Emby/Models/Media/Tv/EmbyEpisodes.cs b/src/Ombi.Api.Emby/Models/Media/Tv/EmbyEpisodes.cs index d76915923..83d64ed15 100644 --- a/src/Ombi.Api.Emby/Models/Media/Tv/EmbyEpisodes.cs +++ b/src/Ombi.Api.Emby/Models/Media/Tv/EmbyEpisodes.cs @@ -16,6 +16,7 @@ public class EmbyEpisodes public int ProductionYear { get; set; } public bool IsPlaceHolder { get; set; } public int IndexNumber { get; set; } + public int? IndexNumberEnd { get; set; } public int ParentIndexNumber { get; set; } public bool IsHD { get; set; } public bool IsFolder { get; set; } diff --git a/src/Ombi.Api.Gotify/GotifyApi.cs b/src/Ombi.Api.Gotify/GotifyApi.cs new file mode 100644 index 000000000..8cd79a689 --- /dev/null +++ b/src/Ombi.Api.Gotify/GotifyApi.cs @@ -0,0 +1,36 @@ +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ombi.Api.Gotify +{ + public class GotifyApi : IGotifyApi + { + public GotifyApi(IApi api) + { + _api = api; + } + + private readonly IApi _api; + + public async Task PushAsync(string baseUrl, string accessToken, string subject, string body, sbyte priority) + { + var request = new Request("/message", baseUrl, HttpMethod.Post); + request.AddQueryString("token", accessToken); + + request.AddHeader("Access-Token", accessToken); + request.ApplicationJsonContentType(); + + + var jsonBody = new + { + message = body, + title = subject, + priority = priority + }; + + request.AddJsonBody(jsonBody); + + await _api.Request(request); + } + } +} diff --git a/src/Ombi.Api.Gotify/IGotifyApi.cs b/src/Ombi.Api.Gotify/IGotifyApi.cs new file mode 100644 index 000000000..e6a6b4060 --- /dev/null +++ b/src/Ombi.Api.Gotify/IGotifyApi.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Api.Gotify +{ + public interface IGotifyApi + { + Task PushAsync(string endpoint, string accessToken, string subject, string body, sbyte priority); + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Gotify/Ombi.Api.Gotify.csproj b/src/Ombi.Api.Gotify/Ombi.Api.Gotify.csproj new file mode 100644 index 000000000..ce5475fae --- /dev/null +++ b/src/Ombi.Api.Gotify/Ombi.Api.Gotify.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + 3.0.0.0 + 3.0.0.0 + + + + + + + + + diff --git a/src/Ombi.Api.Lidarr/ILidarrApi.cs b/src/Ombi.Api.Lidarr/ILidarrApi.cs index 4a23c6200..826cfdec3 100644 --- a/src/Ombi.Api.Lidarr/ILidarrApi.cs +++ b/src/Ombi.Api.Lidarr/ILidarrApi.cs @@ -23,5 +23,6 @@ public interface ILidarrApi Task> GetLanguageProfile(string apiKey, string baseUrl); Task Status(string apiKey, string baseUrl); Task AlbumSearch(int[] albumIds, string apiKey, string baseUrl); + Task AlbumInformation(string albumId, string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs index a6a283703..0f03aa1b0 100644 --- a/src/Ombi.Api.Lidarr/LidarrApi.cs +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -84,7 +84,7 @@ public async Task GetAlbumByForeignId(string foreignArtistId, strin public Task GetAlbumsByArtist(string foreignArtistId) { - var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.3/artist/{foreignArtistId}", + var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.4/artist/{foreignArtistId}", HttpMethod.Get) {IgnoreBaseUrlAppend = true}; return Api.Request(request); } @@ -105,6 +105,31 @@ public Task> GetAllAlbums(string apiKey, string baseUrl) return Api.Request>(request); } + public async Task AlbumInformation(string albumId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); + request.AddQueryString("foreignAlbumId", albumId); + AddHeaders(request, apiKey); + var albums = await Api.Request>(request); + return albums.FirstOrDefault(); + } + + + /// + /// THIS ONLY SUPPORTS ALBUMS THAT THE ARTIST IS IN LIDARR + /// + /// + /// + /// + /// + public Task> GetTracksForAlbum(int albumId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); + request.AddQueryString("albumId", albumId.ToString()); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + public Task AddArtist(ArtistAdd artist, string apiKey, string baseUrl) { var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post); diff --git a/src/Ombi.Api.Lidarr/Models/AlbumByForeignId.cs b/src/Ombi.Api.Lidarr/Models/AlbumByForeignId.cs new file mode 100644 index 000000000..27a479d2f --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/AlbumByForeignId.cs @@ -0,0 +1,31 @@ +using System; +using System.Net.Mime; + +namespace Ombi.Api.Lidarr.Models +{ + public class AlbumByForeignId + { + public string title { get; set; } + public string disambiguation { get; set; } + public string overview { get; set; } + public int artistId { get; set; } + public string foreignAlbumId { get; set; } + public bool monitored { get; set; } + public bool anyReleaseOk { get; set; } + public int profileId { get; set; } + public int duration { get; set; } + public string albumType { get; set; } + public object[] secondaryTypes { get; set; } + public int mediumCount { get; set; } + public Ratings ratings { get; set; } + public DateTime releaseDate { get; set; } + public Release[] releases { get; set; } + public object[] genres { get; set; } + public Medium[] media { get; set; } + public Artist artist { get; set; } + public Image[] images { get; set; } + public Link[] links { get; set; } + public Statistics statistics { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs b/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs index b2394eb5f..1e909cf4c 100644 --- a/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs +++ b/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs @@ -1,10 +1,15 @@ using System; +using System.Collections.Generic; namespace Ombi.Api.Lidarr.Models { public class AlbumLookup { public string title { get; set; } + public string status { get; set; } + public string artistType { get; set; } + public string disambiguation { get; set; } + public List links { get; set; } public int artistId { get; set; } public string foreignAlbumId { get; set; } public bool monitored { get; set; } diff --git a/src/Ombi.Api.Lidarr/Models/LidarrLinks.cs b/src/Ombi.Api.Lidarr/Models/LidarrLinks.cs new file mode 100644 index 000000000..e8bdb8975 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrLinks.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrLinks + { + public string url { get; set; } + public string name { get; set; } + } +} diff --git a/src/Ombi.Api.Lidarr/Models/LidarrRatings.cs b/src/Ombi.Api.Lidarr/Models/LidarrRatings.cs new file mode 100644 index 000000000..7728d58bf --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrRatings.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrRatings + { + public int votes { get; set; } + public decimal value { get; set; } + } +} diff --git a/src/Ombi.Api.Lidarr/Models/LidarrTrack.cs b/src/Ombi.Api.Lidarr/Models/LidarrTrack.cs new file mode 100644 index 000000000..367e8cc7c --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrTrack.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrTrack + { + public int artistId { get; set; } + public int trackFileId { get; set; } + public int albumId { get; set; } + public bool _explicit { get; set; } + public int absoluteTrackNumber { get; set; } + public string trackNumber { get; set; } + public string title { get; set; } + public int duration { get; set; } + public int mediumNumber { get; set; } + public bool hasFile { get; set; } + public bool monitored { get; set; } + public int id { get; set; } + } +} diff --git a/src/Ombi.Api.Pushover/PushoverApi.cs b/src/Ombi.Api.Pushover/PushoverApi.cs index 9f91bc7ca..41df0f647 100644 --- a/src/Ombi.Api.Pushover/PushoverApi.cs +++ b/src/Ombi.Api.Pushover/PushoverApi.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using System.Web; using Ombi.Api.Pushover.Models; namespace Ombi.Api.Pushover @@ -18,11 +19,7 @@ public PushoverApi(IApi api) public async Task PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound) { - if (message.Contains("'")) - { - message = message.Replace("'", "'"); - } - var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post); + var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.UrlEncode(message)}", PushoverEndpoint, HttpMethod.Post); var result = await _api.Request(request); return result; diff --git a/src/Ombi.Api/Api.cs b/src/Ombi.Api/Api.cs index e405b6bca..8748530ec 100644 --- a/src/Ombi.Api/Api.cs +++ b/src/Ombi.Api/Api.cs @@ -72,6 +72,7 @@ public async Task Request(Request request) // do something with the response var receivedString = await httpResponseMessage.Content.ReadAsStringAsync(); + LogDebugContent(receivedString); if (request.ContentType == ContentType.Json) { request.OnBeforeDeserialization?.Invoke(receivedString); @@ -110,7 +111,7 @@ public async Task RequestContent(Request request) } // do something with the response var data = httpResponseMessage.Content; - + await LogDebugContent(httpResponseMessage); return await data.ReadAsStringAsync(); } @@ -122,6 +123,7 @@ public async Task Request(Request request) { AddHeadersBody(request, httpRequestMessage); var httpResponseMessage = await _client.SendAsync(httpRequestMessage); + await LogDebugContent(httpResponseMessage); if (!httpResponseMessage.IsSuccessStatusCode) { if (!request.IgnoreErrors) @@ -132,11 +134,12 @@ public async Task Request(Request request) } } - private static void AddHeadersBody(Request request, HttpRequestMessage httpRequestMessage) + private void AddHeadersBody(Request request, HttpRequestMessage httpRequestMessage) { // Add the Json Body if (request.JsonBody != null) { + LogDebugContent("REQUEST: " + request.JsonBody); httpRequestMessage.Content = new JsonContent(request.JsonBody); httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); // Emby connect fails if we have the charset in the header @@ -153,11 +156,24 @@ private async Task LogError(Request request, HttpResponseMessage httpResponseMes { Logger.LogError(LoggingEvents.Api, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}, RequestUri: {request.FullUri}"); + await LogDebugContent(httpResponseMessage); + } + + private async Task LogDebugContent(HttpResponseMessage message) + { if (Logger.IsEnabled(LogLevel.Debug)) { - var content = await httpResponseMessage.Content.ReadAsStringAsync(); + var content = await message.Content.ReadAsStringAsync(); Logger.LogDebug(content); } } + + private void LogDebugContent(string message) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(message); + } + } } } diff --git a/src/Ombi.Api/HttpRequestExtnesions.cs b/src/Ombi.Api/HttpRequestExtensions.cs similarity index 100% rename from src/Ombi.Api/HttpRequestExtnesions.cs rename to src/Ombi.Api/HttpRequestExtensions.cs diff --git a/src/Ombi.Api/Ombi.Api.csproj b/src/Ombi.Api/Ombi.Api.csproj index e20dd6ccd..c7b905522 100644 --- a/src/Ombi.Api/Ombi.Api.csproj +++ b/src/Ombi.Api/Ombi.Api.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs index 7ff8283da..34c21e008 100644 --- a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs @@ -4,6 +4,7 @@ using Ombi.Core.Rule.Rules.Request; using Ombi.Store.Entities.Requests; using NUnit.Framework; +using Ombi.Core.Authentication; using Ombi.Helpers; namespace Ombi.Core.Tests.Rule.Request @@ -16,7 +17,7 @@ public void Setup() { PrincipalMock = new Mock(); - Rule = new AutoApproveRule(PrincipalMock.Object); + Rule = new AutoApproveRule(PrincipalMock.Object, null); } diff --git a/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs index c9db5875a..f2781c8d2 100644 --- a/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs @@ -3,6 +3,7 @@ using Moq; using NUnit.Framework; using Ombi.Core.Rule.Rules; +using Ombi.Core.Rule.Rules.Request; using Ombi.Helpers; using Ombi.Store.Entities.Requests; @@ -15,7 +16,7 @@ public void Setup() { PrincipalMock = new Mock(); - Rule = new CanRequestRule(PrincipalMock.Object); + Rule = new CanRequestRule(PrincipalMock.Object, null); } diff --git a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs index 99ff5b6bd..0171e37a1 100644 --- a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs @@ -16,7 +16,7 @@ public class EmbyAvailabilityRuleTests public void Setup() { ContextMock = new Mock(); - Rule = new EmbyAvailabilityRule(ContextMock.Object); + Rule = new EmbyAvailabilityRule(ContextMock.Object, null); } private EmbyAvailabilityRule Rule { get; set; } diff --git a/src/Ombi.Core.Tests/Rule/Search/PlexAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/PlexAvailabilityRuleTests.cs index 55177a6ac..5bd35473c 100644 --- a/src/Ombi.Core.Tests/Rule/Search/PlexAvailabilityRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/PlexAvailabilityRuleTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Ombi.Core.Models.Search; @@ -14,7 +15,7 @@ public class PlexAvailabilityRuleTests public void Setup() { ContextMock = new Mock(); - Rule = new PlexAvailabilityRule(ContextMock.Object); + Rule = new PlexAvailabilityRule(ContextMock.Object, new Mock>().Object); } private PlexAvailabilityRule Rule { get; set; } diff --git a/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs index 03294982a..71c02af9d 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs @@ -12,5 +12,6 @@ public interface IMusicSearchEngine Task> GetArtistAlbums(string foreignArtistId); Task> SearchAlbum(string search); Task> SearchArtist(string search); + Task GetAlbumInformation(string foreignAlbumId); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieSearchEngine.cs b/src/Ombi.Core/Engine/MovieSearchEngine.cs index 84cb8afcf..520f052a9 100644 --- a/src/Ombi.Core/Engine/MovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/MovieSearchEngine.cs @@ -227,7 +227,7 @@ private async Task CheckForSubscription(SearchMovieViewModel viewModel) } var request = await RequestService.MovieRequestService.GetAll() .AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id); - if (request) + if (request || viewModel.Available) { viewModel.ShowSubscribe = false; } diff --git a/src/Ombi.Core/Engine/MusicSearchEngine.cs b/src/Ombi.Core/Engine/MusicSearchEngine.cs index d0e577801..da41d5bf1 100644 --- a/src/Ombi.Core/Engine/MusicSearchEngine.cs +++ b/src/Ombi.Core/Engine/MusicSearchEngine.cs @@ -60,6 +60,18 @@ public async Task> SearchAlbum(string search) return vm; } + public async Task GetAlbumInformation(string foreignAlbumId) + { + var settings = await GetSettings(); + var result = await _lidarrApi.AlbumInformation(foreignAlbumId, settings.ApiKey, settings.FullUri); + + + var vm = await MapIntoAlbumVm(result, settings); + + + return vm; + } + /// /// Searches the specified artist /// @@ -143,6 +155,47 @@ private async Task MapIntoArtistVm(ArtistLookup a) return vm; } + + // TODO + private async Task MapIntoAlbumVm(AlbumByForeignId a, LidarrSettings settings) + { + var vm = new SearchAlbumViewModel + { + ForeignAlbumId = a.foreignAlbumId, + Monitored = a.monitored, + Rating = a.ratings?.value ?? 0m, + ReleaseDate = a.releaseDate, + Title = a.title, + Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.Replace("http", "https"), + Genres = a.genres, + AlbumType = a.albumType, + ArtistName = a.artist.artistName, + ForeignArtistId = a.artist.foreignArtistId, + }; + if (a.artistId > 0) + { + //TODO THEY HAVE FIXED THIS IN DEV + // The JSON is different for some stupid reason + // Need to lookup the artist now and all the images -.-" + var artist = await _lidarrApi.GetArtist(a.artistId, settings.ApiKey, settings.FullUri); + vm.ArtistName = artist.artistName; + vm.ForeignArtistId = artist.foreignArtistId; + } + else + { + //vm.ForeignArtistId = a.artistId?.foreignArtistId; + //vm.ArtistName = a.artist?.artistName; + } + + vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.Replace("http", "https"); + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + + await RunSearchRules(vm); + + return vm; + } + private async Task MapIntoAlbumVm(AlbumLookup a, LidarrSettings settings) { var vm = new SearchAlbumViewModel @@ -152,7 +205,8 @@ private async Task MapIntoAlbumVm(AlbumLookup a, LidarrSet Rating = a.ratings?.value ?? 0m, ReleaseDate = a.releaseDate, Title = a.title, - Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url + Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.Replace("http", "https"), + Genres = a.genres }; if (a.artistId > 0) { @@ -169,7 +223,7 @@ private async Task MapIntoAlbumVm(AlbumLookup a, LidarrSet vm.ArtistName = a.artist?.artistName; } - vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url; + vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.Replace("http", "https"); if (vm.Cover.IsNullOrEmpty()) { vm.Cover = a.remoteCover; diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 58144d3ce..796e2fe6a 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -31,14 +31,13 @@ public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine { public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user, INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, - ITvSender sender, IAuditRepository audit, IRepository rl, ISettingsService settings, ICacheService cache, + ITvSender sender, IRepository rl, ISettingsService settings, ICacheService cache, IRepository sub) : base(user, requestService, rule, manager, cache, settings, sub) { TvApi = tvApi; MovieDbApi = movApi; NotificationHelper = helper; TvSender = sender; - Audit = audit; _requestLog = rl; } @@ -46,7 +45,6 @@ public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain private ITvMazeApi TvApi { get; } private IMovieDbApi MovieDbApi { get; } private ITvSender TvSender { get; } - private IAuditRepository Audit { get; } private readonly IRepository _requestLog; public async Task RequestTvShow(TvRequestViewModel tv) @@ -84,8 +82,6 @@ public async Task RequestTvShow(TvRequestViewModel tv) } } - await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username); - var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId); if (existingRequest != null) { @@ -351,7 +347,6 @@ public async Task UpdateQualityProfile(int requestId, int profileId) public async Task UpdateTvRequest(TvRequests request) { - await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username); var allRequests = TvRepository.Get(); var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id); @@ -394,7 +389,6 @@ public async Task ApproveChildRequest(int id) if (request.Approved) { NotificationHelper.Notify(request, NotificationType.RequestApproved); - await Audit.Record(AuditType.Approved, AuditArea.TvRequest, $"Approved Request {request.Title}", Username); // Autosend await TvSender.Send(request); } @@ -426,9 +420,7 @@ public async Task DenyChildRequest(int requestId, string re public async Task UpdateChildRequest(ChildRequests request) { - await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username); - - await TvRepository.UpdateChild(request); + await TvRepository.UpdateChild(request); return request; } @@ -446,16 +438,14 @@ public async Task RemoveTvChild(int requestId) // Delete the parent TvRepository.Db.TvRequests.Remove(parent); } - await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username); - + await TvRepository.Db.SaveChangesAsync(); } public async Task RemoveTvRequest(int requestId) { var request = await TvRepository.Get().FirstOrDefaultAsync(x => x.Id == requestId); - await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username); - await TvRepository.Delete(request); + await TvRepository.Delete(request); } public async Task UserHasRequest(string userId) diff --git a/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs b/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs index a494a3cb5..e9f9c5f9e 100644 --- a/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs +++ b/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs @@ -16,8 +16,13 @@ public class SearchAlbumViewModel : SearchViewModel public string Cover { get; set; } public string Disk { get; set; } public decimal PercentOfTracks { get; set; } + public object[] Genres { get; set; } public override RequestType Type => RequestType.Album; public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; public bool FullyAvailable => PercentOfTracks == 100; + + + // Below is from the INFO call NEED A SEPERATE VM FOR THIS IN V4 TODO + // TODO ADD TRACK COUNT } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/UI/GotifyNotificationViewModel.cs b/src/Ombi.Core/Models/UI/GotifyNotificationViewModel.cs new file mode 100644 index 000000000..93ce66724 --- /dev/null +++ b/src/Ombi.Core/Models/UI/GotifyNotificationViewModel.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 GotifyNotificationViewModel : GotifySettings + { + /// + /// Gets or sets the notification templates. + /// + /// + /// The notification templates. + /// + public List NotificationTemplates { get; set; } + + } +} diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index 35688a6fe..790aa8500 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -11,9 +11,9 @@ - + - + diff --git a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs index a55868db8..685f02b54 100644 --- a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs @@ -1,5 +1,7 @@ using System.Security.Principal; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; using Ombi.Core.Models.Requests; using Ombi.Core.Rule.Interfaces; using Ombi.Helpers; @@ -10,28 +12,31 @@ namespace Ombi.Core.Rule.Rules.Request { public class AutoApproveRule : BaseRequestRule, IRules { - public AutoApproveRule(IPrincipal principal) + public AutoApproveRule(IPrincipal principal, OmbiUserManager um) { User = principal; + _manager = um; } private IPrincipal User { get; } + private readonly OmbiUserManager _manager; - public Task Execute(BaseRequest obj) + public async Task Execute(BaseRequest obj) { - if (User.IsInRole(OmbiRoles.Admin)) + var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name); + if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin)) { obj.Approved = true; - return Task.FromResult(Success()); + return Success(); } - if (obj.RequestType == RequestType.Movie && User.IsInRole(OmbiRoles.AutoApproveMovie)) + if (obj.RequestType == RequestType.Movie && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie)) obj.Approved = true; - if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv)) + if (obj.RequestType == RequestType.TvShow && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv)) obj.Approved = true; - if (obj.RequestType == RequestType.Album && User.IsInRole(OmbiRoles.AutoApproveMusic)) + if (obj.RequestType == RequestType.Album && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic)) obj.Approved = true; - return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve + return Success(); // We don't really care, we just don't set the obj to approve } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs index 1cdf03955..2b316cfc5 100644 --- a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs @@ -1,46 +1,62 @@ -using Ombi.Store.Entities; +using Ombi.Store.Entities; +using System.IO; +using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; using Ombi.Core.Rule.Interfaces; using Ombi.Helpers; +using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; -namespace Ombi.Core.Rule.Rules +namespace Ombi.Core.Rule.Rules.Request { public class CanRequestRule : BaseRequestRule, IRules { - public CanRequestRule(IPrincipal principal) + public CanRequestRule(IPrincipal principal, OmbiUserManager manager) { User = principal; + _manager = manager; } private IPrincipal User { get; } + private readonly OmbiUserManager _manager; - public Task Execute(BaseRequest obj) + public async Task Execute(BaseRequest obj) { - if (User.IsInRole(OmbiRoles.Admin)) - return Task.FromResult(Success()); + var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name); + if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin)) + return Success(); if (obj.RequestType == RequestType.Movie) { - if (User.IsInRole(OmbiRoles.RequestMovie) || User.IsInRole(OmbiRoles.AutoApproveMovie)) - return Task.FromResult(Success()); - return Task.FromResult(Fail("You do not have permissions to Request a Movie")); + if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie)) + return Success(); + return Fail("You do not have permissions to Request a Movie"); } if (obj.RequestType == RequestType.TvShow) { - if (User.IsInRole(OmbiRoles.RequestTv) || User.IsInRole(OmbiRoles.AutoApproveTv)) - return Task.FromResult(Success()); + if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestTv) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv)) + { + return Success(); + } + + return Fail("You do not have permissions to Request a TV Show"); } if (obj.RequestType == RequestType.Album) { - if (User.IsInRole(OmbiRoles.RequestMusic) || User.IsInRole(OmbiRoles.AutoApproveMusic)) - return Task.FromResult(Success()); + if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMusic) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic)) + { + return Success(); + } + + return Fail("You do not have permissions to Request an Album"); } - return Task.FromResult(Fail("You do not have permissions to Request a TV Show")); + throw new InvalidDataException("Permission check failed: unknown RequestType"); } } -} \ No newline at end of file +} diff --git a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs index 428de9ce5..ab1a0af98 100644 --- a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs +++ b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Query; +using Microsoft.Extensions.Logging; using Ombi.Core.Models.Search; using Ombi.Store.Entities; using Ombi.Store.Repository.Requests; @@ -24,7 +24,7 @@ public static void CheckForUnairedEpisodes(SearchTvShowViewModel search) if (!airedButNotAvailable) { var unairedEpisodes = search.SeasonRequests.Any(x => - x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date)); + x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date || c.AirDate != DateTime.MinValue)); if (unairedEpisodes) { search.FullyAvailable = true; @@ -34,28 +34,36 @@ public static void CheckForUnairedEpisodes(SearchTvShowViewModel search) } public static async Task SingleEpisodeCheck(bool useImdb, IQueryable allEpisodes, EpisodeRequests episode, - SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb) + SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb, ILogger log) { PlexEpisode epExists = null; - if (useImdb) + try { - epExists = await allEpisodes.FirstOrDefaultAsync(x => - x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.ImdbId == item.ImdbId.ToString()); - } - if (useTheMovieDb) - { - epExists = await allEpisodes.FirstOrDefaultAsync(x => - x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.TheMovieDbId == item.TheMovieDbId.ToString()); - } + if (useImdb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(x => + x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && + x.Series.ImdbId == item.ImdbId); + } - if (useTvDb) + if (useTheMovieDb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(x => + x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && + x.Series.TheMovieDbId == item.TheMovieDbId); + } + + if (useTvDb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(x => + x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && + x.Series.TvDbId == item.TvDbId); + } + } + catch (Exception e) { - epExists = await allEpisodes.FirstOrDefaultAsync(x => - x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.TvDbId == item.TvDbId.ToString()); + log.LogError(e, "Exception thrown when attempting to check if something is available"); } if (epExists != null) @@ -71,21 +79,21 @@ public static async Task SingleEpisodeCheck(bool useImdb, IQueryable x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.ImdbId == item.ImdbId.ToString()); + x.Series.ImdbId == item.ImdbId); } if (useTheMovieDb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.TheMovieDbId == item.TheMovieDbId.ToString()); + x.Series.TheMovieDbId == item.TheMovieDbId); } if (useTvDb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.TvDbId == item.TvDbId.ToString()); + x.Series.TvDbId == item.TvDbId); } if (epExists != null) diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs index 3171c6ada..f80bded7a 100644 --- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs @@ -3,6 +3,8 @@ using Microsoft.EntityFrameworkCore; using Ombi.Core.Models.Search; using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.Store.Entities; using Ombi.Store.Repository; @@ -11,12 +13,14 @@ namespace Ombi.Core.Rule.Rules.Search { public class EmbyAvailabilityRule : BaseSearchRule, IRules { - public EmbyAvailabilityRule(IEmbyContentRepository repo) + public EmbyAvailabilityRule(IEmbyContentRepository repo, ISettingsService s) { EmbyContentRepository = repo; + EmbySettings = s; } private IEmbyContentRepository EmbyContentRepository { get; } + private ISettingsService EmbySettings { get; } public async Task Execute(SearchViewModel obj) { @@ -60,7 +64,16 @@ public async Task Execute(SearchViewModel obj) if (item != null) { obj.Available = true; - obj.EmbyUrl = item.Url; + var s = await EmbySettings.GetSettingsAsync(); + var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null); + if ((server?.ServerHostname ?? string.Empty).HasValue()) + { + obj.EmbyUrl = $"{server.ServerHostname}#!/itemdetails.html?id={item.EmbyId}"; + } + else + { + obj.EmbyUrl = $"https://app.emby.media/#!/itemdetails.html?id={item.EmbyId}"; + } if (obj.Type == RequestType.TvShow) { diff --git a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs index 7f79e4165..2a239d1d3 100644 --- a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Ombi.Core.Models.Search; using Ombi.Core.Rule.Interfaces; using Ombi.Helpers; @@ -10,12 +11,14 @@ namespace Ombi.Core.Rule.Rules.Search { public class PlexAvailabilityRule : BaseSearchRule, IRules { - public PlexAvailabilityRule(IPlexContentRepository repo) + public PlexAvailabilityRule(IPlexContentRepository repo, ILogger log) { PlexContentRepository = repo; + Log = log; } private IPlexContentRepository PlexContentRepository { get; } + private ILogger Log { get; } public async Task Execute(SearchViewModel obj) { @@ -72,7 +75,7 @@ public async Task Execute(SearchViewModel obj) { foreach (var episode in season.Episodes) { - await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb); + await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb, Log); } } diff --git a/src/Ombi.Core/Senders/MusicSender.cs b/src/Ombi.Core/Senders/MusicSender.cs index 76a9fc14c..0e9db9192 100644 --- a/src/Ombi.Core/Senders/MusicSender.cs +++ b/src/Ombi.Core/Senders/MusicSender.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using EnsureThat; using Microsoft.Extensions.Logging; using Ombi.Api.Lidarr; using Ombi.Api.Lidarr.Models; @@ -87,6 +88,11 @@ private async Task SendToLidarr(AlbumRequest model, LidarrSettings if (artist == null || artist.id <= 0) { + EnsureArg.IsNotNullOrEmpty(model.ForeignArtistId, nameof(model.ForeignArtistId)); + EnsureArg.IsNotNullOrEmpty(model.ForeignAlbumId, nameof(model.ForeignAlbumId)); + EnsureArg.IsNotNullOrEmpty(model.ArtistName, nameof(model.ArtistName)); + EnsureArg.IsNotNullOrEmpty(rootFolderPath, nameof(rootFolderPath)); + // Create artist var newArtist = new ArtistAdd { diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 68a363706..f5705969b 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -32,6 +32,7 @@ using Ombi.Api.DogNzb; using Ombi.Api.FanartTv; using Ombi.Api.Github; +using Ombi.Api.Gotify; using Ombi.Api.Lidarr; using Ombi.Api.Mattermost; using Ombi.Api.Notifications; @@ -60,6 +61,7 @@ using Ombi.Schedule.Jobs.SickRage; using Ombi.Schedule.Processor; using Ombi.Store.Entities; +using Quartz.Spi; namespace Ombi.DependencyInjection { @@ -120,6 +122,7 @@ public static void RegisterApi(this IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -132,28 +135,28 @@ public static void RegisterApi(this IServiceCollection services) } public static void RegisterStore(this IServiceCollection services) { - services.AddEntityFrameworkSqlite().AddDbContext(); - services.AddEntityFrameworkSqlite().AddDbContext(); - services.AddEntityFrameworkSqlite().AddDbContext(); + services.AddDbContext(); + services.AddDbContext(); + services.AddDbContext(); services.AddScoped(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6 services.AddScoped(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6 services.AddScoped(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6 - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>)); - services.AddTransient(typeof(IRepository<>), typeof(Repository<>)); - services.AddTransient(typeof(IExternalRepository<>), typeof(ExternalRepository<>)); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(typeof(ISettingsService<>), typeof(SettingsService<>)); + services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(typeof(IExternalRepository<>), typeof(ExternalRepository<>)); } public static void RegisterServices(this IServiceCollection services) { @@ -161,7 +164,7 @@ public static void RegisterServices(this IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddSingleton(); services.AddTransient(); services.AddTransient(); @@ -170,6 +173,7 @@ public static void RegisterServices(this IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -177,6 +181,7 @@ public static void RegisterServices(this IServiceCollection services) public static void RegisterJobs(this IServiceCollection services) { + services.AddSingleton(provider => new IoCJobFactory(provider)); services.AddTransient(); services.AddTransient(); @@ -197,7 +202,7 @@ public static void RegisterJobs(this IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + //services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Ombi.Helpers/CacheService.cs b/src/Ombi.Helpers/CacheService.cs index 4eef62bda..ae57a9f61 100644 --- a/src/Ombi.Helpers/CacheService.cs +++ b/src/Ombi.Helpers/CacheService.cs @@ -28,18 +28,15 @@ public CacheService(IMemoryCache memoryCache) return result; } - using (await _mutex.LockAsync()) + if (_memoryCache.TryGetValue(cacheKey, out result)) { - if (_memoryCache.TryGetValue(cacheKey, out result)) - { - return result; - } - - result = await factory(); - _memoryCache.Set(cacheKey, result, absoluteExpiration); - return result; } + + result = await factory(); + _memoryCache.Set(cacheKey, result, absoluteExpiration); + + return result; } public void Remove(string key) @@ -47,34 +44,34 @@ public void Remove(string key) _memoryCache.Remove(key); } - - - public T GetOrAdd(string cacheKey, Func factory, DateTime absoluteExpiration) + + + public T GetOrAdd(string cacheKey, Func factory, DateTime absoluteExpiration) + { + // locks get and set internally + if (_memoryCache.TryGetValue(cacheKey, out var result)) { - // locks get and set internally - if (_memoryCache.TryGetValue(cacheKey, out var result)) + return result; + } + + lock (TypeLock.Lock) + { + if (_memoryCache.TryGetValue(cacheKey, out result)) { return result; } - lock (TypeLock.Lock) - { - if (_memoryCache.TryGetValue(cacheKey, out result)) - { - return result; - } - - result = factory(); - _memoryCache.Set(cacheKey, result, absoluteExpiration); + result = factory(); + _memoryCache.Set(cacheKey, result, absoluteExpiration); - return result; - } + return result; } + } + + private static class TypeLock + { + public static object Lock { get; } = new object(); + } - private static class TypeLock - { - public static object Lock { get; } = new object(); - } - } } diff --git a/src/Ombi.Helpers/Cron.cs b/src/Ombi.Helpers/Cron.cs index 35b141eda..9b6339391 100644 --- a/src/Ombi.Helpers/Cron.cs +++ b/src/Ombi.Helpers/Cron.cs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . - using System; /// /// Helper class that provides common values for the cron expressions. /// @@ -44,7 +43,7 @@ public static string Hourly() /// The minute in which the schedule will be activated (0-59). public static string Hourly(int minute) { - return $"{minute} * * * *"; + return $"0 {minute} 0/1 1/1 * ? *"; } /// @@ -73,7 +72,7 @@ public static string Daily(int hour) /// The minute in which the schedule will be activated (0-59). public static string Daily(int hour, int minute) { - return $"{minute} {hour} * * *"; + return $"0 {minute} {hour} 1/1 * ? *"; } /// @@ -114,7 +113,7 @@ public static string Weekly(DayOfWeek dayOfWeek, int hour) /// The minute in which the schedule will be activated (0-59). public static string Weekly(DayOfWeek dayOfWeek, int hour, int minute) { - return $"{minute} {hour} * * {(int)dayOfWeek}"; + return $"0 {minute} {hour} ? * {(int)dayOfWeek} *"; } /// @@ -219,7 +218,7 @@ public static string Yearly(int month, int day, int hour, int minute) /// The number of minutes to wait between every activation. public static string MinuteInterval(int interval) { - return $"*/{interval} * * * *"; + return $"0 0/{interval} * 1/1 * ? *"; } /// @@ -228,7 +227,7 @@ public static string MinuteInterval(int interval) /// The number of hours to wait between every activation. public static string HourInterval(int interval) { - return $"0 */{interval} * * *"; + return $"0 0 0/{interval} 1/1 * ? *"; } /// @@ -237,7 +236,7 @@ public static string HourInterval(int interval) /// The number of days to wait between every activation. public static string DayInterval(int interval) { - return $"0 0 */{interval} * *"; + return $"0 0 12 1/{interval} * ? *"; } /// @@ -249,4 +248,39 @@ public static string MonthInterval(int interval) return $"0 0 1 */{interval} *"; } } + + // + // Summary: + // Specifies the day of the week. + public enum DayOfWeek + { + // + // Summary: + // Indicates Sunday. + Sunday = 1, + // + // Summary: + // Indicates Monday. + Monday = 2, + // + // Summary: + // Indicates Tuesday. + Tuesday = 3, + // + // Summary: + // Indicates Wednesday. + Wednesday = 4, + // + // Summary: + // Indicates Thursday. + Thursday = 5, + // + // Summary: + // Indicates Friday. + Friday = 6, + // + // Summary: + // Indicates Saturday. + Saturday = 7 + } } \ No newline at end of file diff --git a/src/Ombi.Helpers/LoggingEvents.cs b/src/Ombi.Helpers/LoggingEvents.cs index 3893dc879..0723800ab 100644 --- a/src/Ombi.Helpers/LoggingEvents.cs +++ b/src/Ombi.Helpers/LoggingEvents.cs @@ -32,6 +32,7 @@ public class LoggingEvents public static EventId MattermostNotification => new EventId(4004); public static EventId PushoverNotification => new EventId(4005); public static EventId TelegramNotifcation => new EventId(4006); + public static EventId GotifyNotification => new EventId(4007); public static EventId TvSender => new EventId(5000); public static EventId SonarrSender => new EventId(5001); diff --git a/src/Ombi.Helpers/NotificationAgent.cs b/src/Ombi.Helpers/NotificationAgent.cs index 8990eeba9..18f28105a 100644 --- a/src/Ombi.Helpers/NotificationAgent.cs +++ b/src/Ombi.Helpers/NotificationAgent.cs @@ -10,5 +10,6 @@ public enum NotificationAgent Slack = 5, Mattermost = 6, Mobile = 7, + Gotify = 8, } } \ No newline at end of file diff --git a/src/Ombi.Mapping/Profiles/SettingsProfile.cs b/src/Ombi.Mapping/Profiles/SettingsProfile.cs index 139290f2b..f460ce78b 100644 --- a/src/Ombi.Mapping/Profiles/SettingsProfile.cs +++ b/src/Ombi.Mapping/Profiles/SettingsProfile.cs @@ -19,6 +19,7 @@ public SettingsProfile() CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } } \ No newline at end of file diff --git a/src/Ombi.Notifications/Agents/GotifyNotification.cs b/src/Ombi.Notifications/Agents/GotifyNotification.cs new file mode 100644 index 000000000..e1c9fc1db --- /dev/null +++ b/src/Ombi.Notifications/Agents/GotifyNotification.cs @@ -0,0 +1,116 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Ombi.Api.Gotify; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Notifications.Models; +using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.Notifications; +using Ombi.Store.Entities; +using Ombi.Store.Repository; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Notifications.Agents +{ + public class GotifyNotification : BaseNotification, IGotifyNotification + { + public GotifyNotification(IGotifyApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, + ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) + { + Api = api; + Logger = log; + } + + public override string NotificationName => "GotifyNotification"; + + private IGotifyApi Api { get; } + private ILogger Logger { get; } + + protected override bool ValidateConfiguration(GotifySettings settings) + { + return settings.Enabled && !string.IsNullOrEmpty(settings.BaseUrl) && !string.IsNullOrEmpty(settings.ApplicationToken); + } + + protected override async Task NewRequest(NotificationOptions model, GotifySettings settings) + { + await Run(model, settings, NotificationType.NewRequest); + } + + + protected override async Task NewIssue(NotificationOptions model, GotifySettings settings) + { + await Run(model, settings, NotificationType.Issue); + } + + protected override async Task IssueComment(NotificationOptions model, GotifySettings settings) + { + await Run(model, settings, NotificationType.IssueComment); + } + + protected override async Task IssueResolved(NotificationOptions model, GotifySettings settings) + { + await Run(model, settings, NotificationType.IssueResolved); + } + + protected override async Task AddedToRequestQueue(NotificationOptions model, GotifySettings settings) + { + await Run(model, settings, NotificationType.ItemAddedToFaultQueue); + } + + protected override async Task RequestDeclined(NotificationOptions model, GotifySettings settings) + { + await Run(model, settings, NotificationType.RequestDeclined); + } + + protected override async Task RequestApproved(NotificationOptions model, GotifySettings settings) + { + await Run(model, settings, NotificationType.RequestApproved); + } + + protected override async Task AvailableRequest(NotificationOptions model, GotifySettings settings) + { + await Run(model, settings, NotificationType.RequestAvailable); + } + + protected override async Task Send(NotificationMessage model, GotifySettings settings) + { + try + { + await Api.PushAsync(settings.BaseUrl, settings.ApplicationToken, model.Subject, model.Message, settings.Priority); + } + catch (Exception e) + { + Logger.LogError(LoggingEvents.GotifyNotification, e, "Failed to send Gotify notification"); + } + } + + protected override async Task Test(NotificationOptions model, GotifySettings settings) + { + var message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!"; + var notification = new NotificationMessage + { + Message = message, + }; + await Send(notification, settings); + } + + private async Task Run(NotificationOptions model, GotifySettings settings, NotificationType type) + { + var parsed = await LoadTemplate(NotificationAgent.Gotify, type, model); + if (parsed.Disabled) + { + Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Gotify}"); + return; + } + + var notification = new NotificationMessage + { + Message = parsed.Message, + }; + + await Send(notification, settings); + } + } +} diff --git a/src/Ombi.Notifications/Agents/Interfaces/IGotifyNotification.cs b/src/Ombi.Notifications/Agents/Interfaces/IGotifyNotification.cs new file mode 100644 index 000000000..a85421938 --- /dev/null +++ b/src/Ombi.Notifications/Agents/Interfaces/IGotifyNotification.cs @@ -0,0 +1,6 @@ +namespace Ombi.Notifications.Agents +{ + public interface IGotifyNotification : INotification + { + } +} \ No newline at end of file diff --git a/src/Ombi.Notifications/Agents/MattermostNotification.cs b/src/Ombi.Notifications/Agents/MattermostNotification.cs index f64a0fe21..ed3084c19 100644 --- a/src/Ombi.Notifications/Agents/MattermostNotification.cs +++ b/src/Ombi.Notifications/Agents/MattermostNotification.cs @@ -52,7 +52,7 @@ protected override async Task NewRequest(NotificationOptions model, MattermostNo private void AddOtherInformation(NotificationOptions model, NotificationMessage notification, NotificationMessageContent parsed) { - notification.Other.Add("image", parsed.Image); + notification.Other.Add("image", parsed?.Image ?? string.Empty); notification.Other.Add("title", model.RequestType == RequestType.Movie ? MovieRequest.Title : TvRequest.Title); } diff --git a/src/Ombi.Notifications/BaseNotification.cs b/src/Ombi.Notifications/BaseNotification.cs index 001f68f45..c9404eb2c 100644 --- a/src/Ombi.Notifications/BaseNotification.cs +++ b/src/Ombi.Notifications/BaseNotification.cs @@ -30,6 +30,7 @@ protected BaseNotification(ISettingsService settings, INotificationTemplatesR _log = log; AlbumRepository = album; UserNotificationPreferences = notificationUserPreferences; + Settings.ClearCache(); } protected ISettingsService Settings { get; } diff --git a/src/Ombi.Notifications/GenericEmailProvider.cs b/src/Ombi.Notifications/GenericEmailProvider.cs index 653670ef7..15f17af92 100644 --- a/src/Ombi.Notifications/GenericEmailProvider.cs +++ b/src/Ombi.Notifications/GenericEmailProvider.cs @@ -4,6 +4,7 @@ using MailKit.Net.Smtp; using Microsoft.Extensions.Logging; using MimeKit; +using MimeKit.Utils; using Ombi.Core.Settings; using Ombi.Helpers; using Ombi.Notifications.Models; @@ -37,6 +38,15 @@ public async Task SendAdHoc(NotificationMessage model, EmailNotificationSettings var customization = await CustomizationSettings.GetSettingsAsync(); var html = email.LoadTemplate(model.Subject, model.Message, null, customization.Logo); + + var messageId = MimeUtils.GenerateMessageId(); + if (customization.ApplicationUrl.HasValue()) + { + if (Uri.TryCreate(customization.ApplicationUrl, UriKind.RelativeOrAbsolute, out var url)) + { + messageId = MimeUtils.GenerateMessageId(url.IdnHost); + } + } var textBody = string.Empty; @@ -50,7 +60,8 @@ public async Task SendAdHoc(NotificationMessage model, EmailNotificationSettings var message = new MimeMessage { Body = body.ToMessageBody(), - Subject = model.Subject + Subject = model.Subject, + MessageId = messageId }; message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress)); message.To.Add(new MailboxAddress(model.To, model.To)); diff --git a/src/Ombi.Notifications/Ombi.Notifications.csproj b/src/Ombi.Notifications/Ombi.Notifications.csproj index 2b5c95154..3fa4b4830 100644 --- a/src/Ombi.Notifications/Ombi.Notifications.csproj +++ b/src/Ombi.Notifications/Ombi.Notifications.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Ombi.Schedule.Tests/IssuesPurgeTests.cs b/src/Ombi.Schedule.Tests/IssuesPurgeTests.cs index 932022cd8..1606cb2d5 100644 --- a/src/Ombi.Schedule.Tests/IssuesPurgeTests.cs +++ b/src/Ombi.Schedule.Tests/IssuesPurgeTests.cs @@ -32,7 +32,7 @@ public void Setup() [Test] public async Task DoesNotRun_WhenDisabled() { - await Job.Start(); + await Job.Execute(null); Repo.Verify(x => x.GetAll(),Times.Never); } @@ -50,7 +50,7 @@ public async Task Deletes_Correct_Issue() Settings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new IssueSettings { DeleteIssues = true, DaysAfterResolvedToDelete = 5 }); Repo.Setup(x => x.GetAll()).Returns(new EnumerableQuery(issues)); - await Job.Start(); + await Job.Execute(null); Assert.That(issues.First().Status, Is.EqualTo(IssueStatus.Deleted)); Repo.Verify(x => x.SaveChangesAsync(), Times.Once); @@ -75,7 +75,7 @@ public async Task DoesNot_Delete_AnyIssues() Settings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new IssueSettings { DeleteIssues = true, DaysAfterResolvedToDelete = 5 }); Repo.Setup(x => x.GetAll()).Returns(new EnumerableQuery(issues)); - await Job.Start(); + await Job.Execute(null); Assert.That(issues[0].Status, Is.Not.EqualTo(IssueStatus.Deleted)); Assert.That(issues[1].Status, Is.EqualTo(IssueStatus.Deleted)); diff --git a/src/Ombi.Schedule.Tests/OmbiQuartzTests.cs b/src/Ombi.Schedule.Tests/OmbiQuartzTests.cs new file mode 100644 index 000000000..6d00f8ef6 --- /dev/null +++ b/src/Ombi.Schedule.Tests/OmbiQuartzTests.cs @@ -0,0 +1,36 @@ +using Moq; +using NUnit.Framework; +using Quartz; +using System.Threading; +using System.Threading.Tasks; + +namespace Ombi.Schedule.Tests +{ + [TestFixture] + public class OmbiQuartzTests + { + + [Test] + [Ignore("Cannot get this to work")] + public async Task Test() + { + var scheduleMock = new Mock(); + scheduleMock.Setup(x => x.TriggerJob(It.IsAny(), + It.IsAny())); + var sut = new QuartzMock(scheduleMock); + + //await QuartzMock.TriggerJob("ABC"); + + scheduleMock.Verify(x => x.TriggerJob(It.Is(j => j.Name == "ABC"), + default(CancellationToken)), Times.Once); + } + } + public class QuartzMock : OmbiQuartz + { + public QuartzMock(Mock mock) + { + _instance = this; + _scheduler = mock.Object; + } + } +} diff --git a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs index 55c9dc288..028c044b7 100644 --- a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs +++ b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs @@ -46,7 +46,7 @@ public async Task ProcessMovies_ShouldMarkAvailable_WhenInPlex() _movie.Setup(x => x.GetAll()).Returns(new List { request }.AsQueryable()); _repo.Setup(x => x.Get("test")).ReturnsAsync(new PlexServerContent()); - await Checker.Start(); + await Checker.Execute(null); _movie.Verify(x => x.Save(), Times.Once); @@ -62,8 +62,8 @@ public async Task ProcessMovies_ShouldNotBeAvailable_WhenInNotPlex() }; _movie.Setup(x => x.GetAll()).Returns(new List { request }.AsQueryable()); - await Checker.Start(); - + await Checker.Execute(null); + Assert.False(request.Available); } @@ -107,7 +107,7 @@ public async Task ProcessTv_ShouldMark_Episode_Available_WhenInPlex() }.AsQueryable); _repo.Setup(x => x.Include(It.IsAny>(),It.IsAny>>())); - await Checker.Start(); + await Checker.Execute(null); _tv.Verify(x => x.Save(), Times.Once); diff --git a/src/Ombi.Schedule/IocJobFactory.cs b/src/Ombi.Schedule/IocJobFactory.cs new file mode 100644 index 000000000..795c1fec5 --- /dev/null +++ b/src/Ombi.Schedule/IocJobFactory.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Quartz; +using Quartz.Spi; + +namespace Ombi.Schedule +{ + public class IoCJobFactory : IJobFactory + { + private readonly IServiceProvider _factory; + + public IoCJobFactory(IServiceProvider factory) + { + _factory = factory; + } + public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) + { + var scopeFactory = _factory.GetService(); + var scope = scopeFactory.CreateScope(); + var scopedContainer = scope.ServiceProvider; + + var implementation = scopedContainer.GetRequiredService(bundle.JobDetail.JobType) as IJob; + return implementation; + } + + public void ReturnJob(IJob job) + { + var disposable = job as IDisposable; + disposable?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/JobSetup.cs b/src/Ombi.Schedule/JobSetup.cs index 5cc818441..f0f53f128 100644 --- a/src/Ombi.Schedule/JobSetup.cs +++ b/src/Ombi.Schedule/JobSetup.cs @@ -65,25 +65,24 @@ public void Setup() { 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(false), JobSettingsHelper.PlexContent(s)); - RecurringJob.AddOrUpdate(() => _plexRecentlyAddedSync.Start(), JobSettingsHelper.PlexRecentlyAdded(s)); - RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s)); - RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s)); - RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s)); - RecurringJob.AddOrUpdate(() => _lidarrArtistSync.CacheContent(), JobSettingsHelper.LidarrArtistSync(s)); - RecurringJob.AddOrUpdate(() => _issuesPurge.Start(), JobSettingsHelper.IssuePurge(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.Execute(null), JobSettingsHelper.PlexContent(s)); + // //RecurringJob.AddOrUpdate(() => _plexRecentlyAddedSync.Start(), JobSettingsHelper.PlexRecentlyAdded(s)); + // RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s)); + // RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s)); + // RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s)); + // RecurringJob.AddOrUpdate(() => _lidarrArtistSync.CacheContent(), JobSettingsHelper.LidarrArtistSync(s)); + // RecurringJob.AddOrUpdate(() => _issuesPurge.Start(), JobSettingsHelper.IssuePurge(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(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s)); - RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s)); - RecurringJob.AddOrUpdate(() => _resender.Start(), JobSettingsHelper.ResendFailedRequests(s)); - RecurringJob.AddOrUpdate(() => _mediaDatabaseRefresh.Start(), JobSettingsHelper.MediaDatabaseRefresh(s)); + // RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s)); + // RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s)); + // RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s)); + //// RecurringJob.AddOrUpdate(() => _resender.Start(), JobSettingsHelper.ResendFailedRequests(s)); + // RecurringJob.AddOrUpdate(() => _mediaDatabaseRefresh.Start(), JobSettingsHelper.MediaDatabaseRefresh(s)); } private bool _disposed; @@ -94,7 +93,6 @@ protected virtual void Dispose(bool disposing) if (disposing) { - _plexContentSync?.Dispose(); _radarrSync?.Dispose(); _updater?.Dispose(); _plexUserImporter?.Dispose(); diff --git a/src/Ombi.Schedule/Jobs/Couchpotato/CouchPotatoSync.cs b/src/Ombi.Schedule/Jobs/Couchpotato/CouchPotatoSync.cs index 5e9f13534..5c3cf342a 100644 --- a/src/Ombi.Schedule/Jobs/Couchpotato/CouchPotatoSync.cs +++ b/src/Ombi.Schedule/Jobs/Couchpotato/CouchPotatoSync.cs @@ -36,6 +36,7 @@ using Ombi.Settings.Settings.Models.External; using Ombi.Store.Context; using Ombi.Store.Entities; +using Quartz; namespace Ombi.Schedule.Jobs.Couchpotato { @@ -56,7 +57,7 @@ public CouchPotatoSync(ISettingsService cpSettings, private readonly ILogger _log; private readonly IExternalContext _ctx; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { var settings = await _settings.GetSettingsAsync(); if (!settings.Enabled) @@ -71,7 +72,11 @@ public async Task Start() if (movies != null) { // Let's remove the old cached data - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM CouchPotatoCache"); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM CouchPotatoCache"); + tran.Commit(); + } // Save var movieIds = new List(); @@ -91,9 +96,14 @@ public async Task Start() _log.LogError("TMDBId is not > 0 for movie {0}", m.title); } } - await _ctx.CouchPotatoCache.AddRangeAsync(movieIds); - await _ctx.SaveChangesAsync(); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.CouchPotatoCache.AddRangeAsync(movieIds); + + await _ctx.SaveChangesAsync(); + tran.Commit(); + } } } catch (Exception e) diff --git a/src/Ombi.Schedule/Jobs/Couchpotato/ICouchPotatoSync.cs b/src/Ombi.Schedule/Jobs/Couchpotato/ICouchPotatoSync.cs index caa390834..fa23d5030 100644 --- a/src/Ombi.Schedule/Jobs/Couchpotato/ICouchPotatoSync.cs +++ b/src/Ombi.Schedule/Jobs/Couchpotato/ICouchPotatoSync.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Couchpotato { public interface ICouchPotatoSync : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs index 7007b3743..929f8d7b9 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs @@ -37,6 +37,7 @@ using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; +using Quartz; namespace Ombi.Schedule.Jobs.Emby { @@ -58,7 +59,7 @@ public EmbyAvaliabilityChecker(IEmbyContentRepository repo, ITvRequestRepository private readonly INotificationService _notificationService; private readonly ILogger _log; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { await ProcessMovies(); await ProcessTv(); diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs index e22c0ca51..a64b7bb30 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs @@ -12,6 +12,7 @@ using Ombi.Schedule.Jobs.Ombi; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; using Serilog; using EmbyMediaType = Ombi.Store.Entities.EmbyMediaType; @@ -20,25 +21,21 @@ namespace Ombi.Schedule.Jobs.Emby public class EmbyContentSync : IEmbyContentSync { public EmbyContentSync(ISettingsService settings, IEmbyApi api, ILogger logger, - IEmbyContentRepository repo, IEmbyEpisodeSync epSync, IRefreshMetadata metadata) + IEmbyContentRepository repo) { _logger = logger; _settings = settings; _api = api; _repo = repo; - _episodeSync = epSync; - _metadata = metadata; } private readonly ILogger _logger; private readonly ISettingsService _settings; private readonly IEmbyApi _api; private readonly IEmbyContentRepository _repo; - private readonly IEmbyEpisodeSync _episodeSync; - private readonly IRefreshMetadata _metadata; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { var embySettings = await _settings.GetSettingsAsync(); if (!embySettings.Enable) @@ -57,8 +54,9 @@ public async Task Start() } // Episodes - BackgroundJob.Enqueue(() => _episodeSync.Start()); - BackgroundJob.Enqueue(() => _metadata.Start()); + + await OmbiQuartz.TriggerJob(nameof(IEmbyEpisodeSync), "Emby"); + await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System"); } diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs index 962b08cda..69ebdc42c 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs @@ -36,29 +36,27 @@ using Ombi.Core.Settings.Models.External; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; namespace Ombi.Schedule.Jobs.Emby { public class EmbyEpisodeSync : IEmbyEpisodeSync { - public EmbyEpisodeSync(ISettingsService s, IEmbyApi api, ILogger l, IEmbyContentRepository repo, - IEmbyAvaliabilityChecker checker) + public EmbyEpisodeSync(ISettingsService s, IEmbyApi api, ILogger l, IEmbyContentRepository repo) { _api = api; _logger = l; _settings = s; _repo = repo; - _avaliabilityChecker = checker; } private readonly ISettingsService _settings; private readonly IEmbyApi _api; private readonly ILogger _logger; private readonly IEmbyContentRepository _repo; - private readonly IEmbyAvaliabilityChecker _avaliabilityChecker; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { var settings = await _settings.GetSettingsAsync(); @@ -67,7 +65,8 @@ public async Task Start() await CacheEpisodes(server); } - BackgroundJob.Enqueue(() => _avaliabilityChecker.Start()); + + await OmbiQuartz.TriggerJob(nameof(IEmbyAvaliabilityChecker), "Emby"); } private async Task CacheEpisodes(EmbyServers server) @@ -118,6 +117,22 @@ private async Task CacheEpisodes(EmbyServers server) Title = ep.Name, AddedAt = DateTime.UtcNow }); + + if (ep.IndexNumberEnd.HasValue && ep.IndexNumberEnd.Value != ep.IndexNumber) + { + epToAdd.Add(new EmbyEpisode + { + EmbyId = ep.Id, + EpisodeNumber = ep.IndexNumberEnd.Value, + SeasonNumber = ep.ParentIndexNumber, + ParentId = ep.SeriesId, + TvDbId = ep.ProviderIds.Tvdb, + TheMovieDbId = ep.ProviderIds.Tmdb, + ImdbId = ep.ProviderIds.Imdb, + Title = ep.Name, + AddedAt = DateTime.UtcNow + }); + } } } @@ -142,7 +157,6 @@ protected virtual void Dispose(bool disposing) { _settings?.Dispose(); _repo?.Dispose(); - _avaliabilityChecker?.Dispose(); } _disposed = true; } diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs index 280a61ab4..5cb7b368d 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs @@ -37,6 +37,7 @@ using Ombi.Helpers; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; +using Quartz; namespace Ombi.Schedule.Jobs.Emby { @@ -58,7 +59,7 @@ public EmbyUserImporter(IEmbyApi api, UserManager um, ILogger _embySettings; private readonly ISettingsService _userManagementSettings; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { var userManagementSettings = await _userManagementSettings.GetSettingsAsync(); if (!userManagementSettings.ImportEmbyUsers) diff --git a/src/Ombi.Schedule/Jobs/Emby/IEmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/IEmbyAvaliabilityChecker.cs index ce9792f3b..ce2d7dba7 100644 --- a/src/Ombi.Schedule/Jobs/Emby/IEmbyAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Emby/IEmbyAvaliabilityChecker.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Emby { public interface IEmbyAvaliabilityChecker : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Emby/IEmbyContentSync.cs b/src/Ombi.Schedule/Jobs/Emby/IEmbyContentSync.cs index 5ba8ea6f9..8dd85bab4 100644 --- a/src/Ombi.Schedule/Jobs/Emby/IEmbyContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/IEmbyContentSync.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Emby { public interface IEmbyContentSync : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Emby/IEmbyEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Emby/IEmbyEpisodeSync.cs index 42d761832..bff623ff8 100644 --- a/src/Ombi.Schedule/Jobs/Emby/IEmbyEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/IEmbyEpisodeSync.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Emby { public interface IEmbyEpisodeSync : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Emby/IEmbyUserImporter.cs b/src/Ombi.Schedule/Jobs/Emby/IEmbyUserImporter.cs index c8bd6fc0e..1c3a51340 100644 --- a/src/Ombi.Schedule/Jobs/Emby/IEmbyUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Emby/IEmbyUserImporter.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Emby { public interface IEmbyUserImporter : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/IBaseJob.cs b/src/Ombi.Schedule/Jobs/IBaseJob.cs index d43d21b9a..bec0db7a0 100644 --- a/src/Ombi.Schedule/Jobs/IBaseJob.cs +++ b/src/Ombi.Schedule/Jobs/IBaseJob.cs @@ -25,11 +25,12 @@ // ************************************************************************/ #endregion +using Quartz; using System; namespace Ombi.Schedule { - public interface IBaseJob : IDisposable + public interface IBaseJob : IJob, IDisposable { } diff --git a/src/Ombi.Schedule/Jobs/Lidarr/ILidarrArtistSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrArtistSync.cs index 1d3424756..3ba74e7cf 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/ILidarrArtistSync.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrArtistSync.cs @@ -1,12 +1,12 @@ using System.Collections.Generic; using System.Threading.Tasks; using Ombi.Store.Entities; +using Quartz; namespace Ombi.Schedule.Jobs.Lidarr { - public interface ILidarrArtistSync + public interface ILidarrArtistSync : IJob { - Task CacheContent(); void Dispose(); Task> GetCachedContent(); } diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs index 2a50b5b38..0d48efbc0 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs @@ -48,7 +48,11 @@ public async Task CacheContent() if (albums != null && albums.Any()) { // Let's remove the old cached data - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrAlbumCache"); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrAlbumCache"); + tran.Commit(); + } var albumCache = new List(); foreach (var a in albums) @@ -60,7 +64,7 @@ public async Task CacheContent() ArtistId = a.artistId, ForeignAlbumId = a.foreignAlbumId, ReleaseDate = a.releaseDate, - TrackCount = a.currentRelease.trackCount, + TrackCount = a.currentRelease?.trackCount ?? 0, Monitored = a.monitored, Title = a.title, PercentOfTracks = a.statistics?.percentOfEpisodes ?? 0m, @@ -68,9 +72,14 @@ public async Task CacheContent() }); } } - await _ctx.LidarrAlbumCache.AddRangeAsync(albumCache); - await _ctx.SaveChangesAsync(); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.LidarrAlbumCache.AddRangeAsync(albumCache); + + await _ctx.SaveChangesAsync(); + tran.Commit(); + } } } catch (System.Exception ex) diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs index e9a64f2a3..1602f28b5 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs @@ -11,6 +11,7 @@ using Ombi.Settings.Settings.Models.External; using Ombi.Store.Context; using Ombi.Store.Entities; +using Quartz; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Ombi.Schedule.Jobs.Lidarr @@ -35,7 +36,7 @@ public LidarrArtistSync(ISettingsService lidarr, ILidarrApi lida private readonly IBackgroundJobClient _job; private readonly ILidarrAlbumSync _albumSync; - public async Task CacheContent() + public async Task Execute(IJobExecutionContext job) { try { @@ -48,7 +49,11 @@ public async Task CacheContent() if (artists != null && artists.Any()) { // Let's remove the old cached data - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrArtistCache"); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrArtistCache"); + tran.Commit(); + } var artistCache = new List(); foreach (var a in artists) @@ -64,9 +69,14 @@ public async Task CacheContent() }); } } - await _ctx.LidarrArtistCache.AddRangeAsync(artistCache); - await _ctx.SaveChangesAsync(); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.LidarrArtistCache.AddRangeAsync(artistCache); + + await _ctx.SaveChangesAsync(); + tran.Commit(); + } } } catch (Exception ex) diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IIssuesPurge.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IIssuesPurge.cs index fbd1e3aaf..d72b062ce 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IIssuesPurge.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IIssuesPurge.cs @@ -3,7 +3,6 @@ namespace Ombi.Schedule.Jobs.Ombi { public interface IIssuesPurge : IBaseJob - { - Task Start(); + { } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IMediaDatabaseRefresh.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IMediaDatabaseRefresh.cs index 11fe7c51a..06ac9421c 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IMediaDatabaseRefresh.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IMediaDatabaseRefresh.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Plex.Interfaces { public interface IMediaDatabaseRefresh : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/INewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/INewsletterJob.cs index 887508d34..d8b5c3f3f 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/INewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/INewsletterJob.cs @@ -5,7 +5,6 @@ namespace Ombi.Schedule.Jobs.Ombi { public interface INewsletterJob : IBaseJob { - Task Start(); Task Start(NewsletterSettings settings, bool test); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs index 48f03b65d..484700c85 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs @@ -5,7 +5,6 @@ namespace Ombi.Schedule.Jobs.Ombi { public interface IOmbiAutomaticUpdater : IBaseJob { - Task Update(PerformContext context); string[] GetVersion(); Task UpdateAvailable(string branch, string currentVersion); } diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IRefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IRefreshMetadata.cs index ed13280b0..a7e7d5fdc 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IRefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IRefreshMetadata.cs @@ -5,7 +5,6 @@ namespace Ombi.Schedule.Jobs.Ombi { public interface IRefreshMetadata : IBaseJob { - Task Start(); Task ProcessPlexServerContent(IEnumerable contentIds); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IResendFailedRequests.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IResendFailedRequests.cs index b55c0f69b..c740b71d9 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IResendFailedRequests.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IResendFailedRequests.cs @@ -1,9 +1,9 @@ -using System.Threading.Tasks; +using Quartz; +using System.Threading.Tasks; namespace Ombi.Schedule.Jobs.Ombi { - public interface IResendFailedRequests + public interface IResendFailedRequests : IJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IWelcomeEmail.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IWelcomeEmail.cs index 6b1270669..b4be7cbef 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IWelcomeEmail.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IWelcomeEmail.cs @@ -3,7 +3,7 @@ namespace Ombi.Schedule.Jobs.Ombi { - public interface IWelcomeEmail : IBaseJob + public interface IWelcomeEmail { Task SendEmail(OmbiUser user); } diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IssuesPurge.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IssuesPurge.cs index 5af4b565d..6a16aad70 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IssuesPurge.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IssuesPurge.cs @@ -5,6 +5,7 @@ using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; +using Quartz; namespace Ombi.Schedule.Jobs.Ombi { @@ -20,7 +21,7 @@ public IssuesPurge(IRepository issuesRepo, ISettingsService _issuesRepository; private readonly ISettingsService _settings; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { var settings = await _settings.GetSettingsAsync(); if (!settings.DeleteIssues) diff --git a/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs b/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs index ed0bf227f..cf8cbd831 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs @@ -9,28 +9,28 @@ using Ombi.Schedule.Jobs.Emby; using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Store.Repository; +using Quartz; namespace Ombi.Schedule.Jobs.Ombi { public class MediaDatabaseRefresh : IMediaDatabaseRefresh { public MediaDatabaseRefresh(ISettingsService s, ILogger log, - IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IEmbyContentSync embySync) + IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo) { _settings = s; _log = log; _plexRepo = plexRepo; _embyRepo = embyRepo; - _embyContentSync = embySync; + _settings.ClearCache(); } private readonly ISettingsService _settings; private readonly ILogger _log; private readonly IPlexContentRepository _plexRepo; private readonly IEmbyContentRepository _embyRepo; - private readonly IEmbyContentSync _embyContentSync; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { try { @@ -53,13 +53,12 @@ private async Task RemoveEmbyData() { return; } - const string episodeSQL = "DELETE FROM EmbyEpisode"; const string mainSql = "DELETE FROM EmbyContent"; await _embyRepo.ExecuteSql(episodeSQL); await _embyRepo.ExecuteSql(mainSql); - BackgroundJob.Enqueue(() => _embyContentSync.Start()); + await OmbiQuartz.TriggerJob(nameof(IEmbyContentSync), "Emby"); } catch (Exception e) { diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 6c59f4c0f..f3756bb0f 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -26,6 +26,7 @@ using Ombi.Settings.Settings.Models.Notifications; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; using ContentType = Ombi.Store.Entities.ContentType; namespace Ombi.Schedule.Jobs.Ombi @@ -57,6 +58,10 @@ public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, I _ombiSettings = ombiSettings; _plexSettings = plexSettings; _embySettings = embySettings; + _ombiSettings.ClearCache(); + _plexSettings.ClearCache(); + _emailSettings.ClearCache(); + _customizationSettings.ClearCache(); } private readonly IPlexContentRepository _plex; @@ -284,7 +289,7 @@ await _email.Send( } } - public async Task Start() + public async Task Execute(IJobExecutionContext job) { var newsletterSettings = await _newsletterSettings.GetSettingsAsync(); await Start(newsletterSettings, false); @@ -359,7 +364,7 @@ private async Task BuildHtml(IQueryable plexContentTo if (embySettings.Enable) { - await ProcessEmbyMovies(embyMovies, sb, ombiSettings.DefaultLanguageCode); + await ProcessEmbyMovies(embyMovies, sb, ombiSettings.DefaultLanguageCode, embySettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty); } sb.Append(""); @@ -385,7 +390,7 @@ private async Task BuildHtml(IQueryable plexContentTo if (embySettings.Enable) { - await ProcessEmbyTv(embyEp, sb); + await ProcessEmbyTv(embyEp, sb, embySettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty); } sb.Append(""); @@ -490,7 +495,7 @@ private async Task ProcessAlbums(HashSet albumsToSend, StringB } } - private async Task ProcessEmbyMovies(IQueryable embyContent, StringBuilder sb, string defaultLangaugeCode) + private async Task ProcessEmbyMovies(IQueryable embyContent, StringBuilder sb, string defaultLangaugeCode, string customUrl) { int count = 0; var ordered = embyContent.OrderByDescending(x => x.AddedAt); @@ -511,6 +516,10 @@ private async Task ProcessEmbyMovies(IQueryable embyContent, String } var mediaurl = content.Url; + if (customUrl.HasValue()) + { + mediaurl = customUrl; + } var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId), defaultLangaugeCode); if (info == null) { @@ -754,7 +763,7 @@ private async Task ProcessPlexTv(HashSet plexContent, StringBuilder - private async Task ProcessEmbyTv(HashSet embyContent, StringBuilder sb) + private async Task ProcessEmbyTv(HashSet embyContent, StringBuilder sb, string serverUrl) { var series = new List(); foreach (var episode in embyContent) @@ -809,7 +818,7 @@ private async Task ProcessEmbyTv(HashSet embyContent, StringBuilder AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w1280/"); } AddPosterInsideTable(sb, banner); - AddMediaServerUrl(sb, t.Url, banner); + AddMediaServerUrl(sb, serverUrl.HasValue() ? serverUrl : t.Url, banner); AddInfoTable(sb); var title = ""; diff --git a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs index 783fe5f9d..4c66b2e2f 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs @@ -20,6 +20,7 @@ using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Updater; +using Quartz; using SharpCompress.Readers; using SharpCompress.Readers.Tar; @@ -41,7 +42,6 @@ public OmbiAutomaticUpdater(ILogger log, IChangeLogProcess private IChangeLogProcessor Processor { get; } private ISettingsService Settings { get; } private readonly IProcessProvider _processProvider; - private static PerformContext Ctx { get; set; } private readonly IApplicationConfigRepository _appConfig; public string[] GetVersion() @@ -59,10 +59,8 @@ public async Task UpdateAvailable(string branch, string currentVersion) } - [AutomaticRetry(Attempts = 1)] - public async Task Update(PerformContext c) + public async Task Execute(IJobExecutionContext job) { - Ctx = c; Logger.LogDebug(LoggingEvents.Updater, "Starting Update job"); var settings = await Settings.GetSettingsAsync(); @@ -182,7 +180,7 @@ public async Task Update(PerformContext c) } Logger.LogDebug(LoggingEvents.Updater, "Starting Download"); - await DownloadAsync(download.Url, zipDir, c); + await DownloadAsync(download.Url, zipDir); Logger.LogDebug(LoggingEvents.Updater, "Finished Download"); } catch (Exception e) @@ -321,7 +319,7 @@ private void Extract(string zipDir, string tempPath) } } - public async Task DownloadAsync(string requestUri, string filename, PerformContext ctx) + public async Task DownloadAsync(string requestUri, string filename) { Logger.LogDebug(LoggingEvents.Updater, "Starting the DownloadAsync"); using (var client = new WebClient()) diff --git a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs index c9ba5c6b3..e4f175855 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs @@ -15,6 +15,7 @@ using Ombi.Schedule.Jobs.Plex; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; namespace Ombi.Schedule.Jobs.Ombi { @@ -22,8 +23,7 @@ public class RefreshMetadata : IRefreshMetadata { public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, ILogger log, ITvMazeApi tvApi, ISettingsService plexSettings, - IMovieDbApi movieApi, ISettingsService embySettings, IPlexAvailabilityChecker plexAvailability, IEmbyAvaliabilityChecker embyAvaliability, - IEmbyApi embyApi) + IMovieDbApi movieApi, ISettingsService embySettings, IEmbyApi embyApi) { _plexRepo = plexRepo; _embyRepo = embyRepo; @@ -32,15 +32,11 @@ public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository e _tvApi = tvApi; _plexSettings = plexSettings; _embySettings = embySettings; - _plexAvailabilityChecker = plexAvailability; - _embyAvaliabilityChecker = embyAvaliability; _embyApi = embyApi; } private readonly IPlexContentRepository _plexRepo; private readonly IEmbyContentRepository _embyRepo; - private readonly IPlexAvailabilityChecker _plexAvailabilityChecker; - private readonly IEmbyAvaliabilityChecker _embyAvaliabilityChecker; private readonly ILogger _log; private readonly IMovieDbApi _movieApi; private readonly ITvMazeApi _tvApi; @@ -48,7 +44,7 @@ public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository e private readonly ISettingsService _embySettings; private readonly IEmbyApi _embyApi; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { _log.LogInformation("Starting the Metadata refresh"); try @@ -93,12 +89,12 @@ public async Task ProcessPlexServerContent(IEnumerable contentIds) { if (plexSettings.Enable) { - BackgroundJob.Enqueue(() => _plexAvailabilityChecker.Start()); + await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex"); } if (embySettings.Enable) { - BackgroundJob.Enqueue(() => _embyAvaliabilityChecker.Start()); + await OmbiQuartz.TriggerJob(nameof(IEmbyAvaliabilityChecker), "Emby"); } } diff --git a/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs b/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs index bc8f93a97..344e3a874 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs @@ -7,6 +7,7 @@ using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; +using Quartz; namespace Ombi.Schedule.Jobs.Ombi { @@ -32,7 +33,7 @@ public ResendFailedRequests(IRepository queue, IMovieSender movieS private readonly ITvRequestRepository _tvRequestRepository; private readonly IMusicRequestRepository _musicRequestRepository; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { // Get all the failed ones! var failedRequests = _requestQueue.GetAll().Where(x => !x.Completed.HasValue); diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexAvailabilityChecker.cs index 2d115ab88..112e3be38 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexAvailabilityChecker.cs @@ -5,6 +5,5 @@ namespace Ombi.Schedule.Jobs.Plex { public interface IPlexAvailabilityChecker : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs index 17a8bbb4f..9d2752d85 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs @@ -1,9 +1,9 @@ using System.Threading.Tasks; +using Quartz; namespace Ombi.Schedule.Jobs { - public interface IPlexContentSync : IBaseJob + public interface IPlexContentSync : IJob { - Task CacheContent(bool recentlyAddedSearch = false); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs index 7de7c3c0c..8eed35066 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs @@ -9,7 +9,6 @@ namespace Ombi.Schedule.Jobs.Plex.Interfaces { public interface IPlexEpisodeSync : IBaseJob { - Task Start(); Task> ProcessEpsiodes(Metadata[] episodes, IQueryable currentEpisodes); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexUserImporter.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexUserImporter.cs index 431ce3ee3..fede60475 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexUserImporter.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Plex { public interface IPlexUserImporter : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs index e0278f854..9a7a8601a 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs @@ -12,6 +12,7 @@ using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; +using Quartz; namespace Ombi.Schedule.Jobs.Plex { @@ -35,7 +36,7 @@ public PlexAvailabilityChecker(IPlexContentRepository repo, ITvRequestRepository private readonly IBackgroundJobClient _backgroundJobClient; private readonly ILogger _log; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { try { diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index f3b0b4f26..7f5d095ef 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -42,13 +42,14 @@ using Ombi.Schedule.Jobs.Plex.Models; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; namespace Ombi.Schedule.Jobs.Plex { public class PlexContentSync : IPlexContentSync { public PlexContentSync(ISettingsService plex, IPlexApi plexApi, ILogger logger, IPlexContentRepository repo, - IPlexEpisodeSync epsiodeSync, IRefreshMetadata metadataRefresh, IPlexAvailabilityChecker checker) + IPlexEpisodeSync epsiodeSync, IRefreshMetadata metadataRefresh) { Plex = plex; PlexApi = plexApi; @@ -56,7 +57,7 @@ public PlexContentSync(ISettingsService plex, IPlexApi plexApi, IL Repo = repo; EpisodeSync = epsiodeSync; Metadata = metadataRefresh; - Checker = checker; + Plex.ClearCache(); } private ISettingsService Plex { get; } @@ -65,10 +66,12 @@ public PlexContentSync(ISettingsService plex, IPlexApi plexApi, IL private IPlexContentRepository Repo { get; } private IPlexEpisodeSync EpisodeSync { get; } private IRefreshMetadata Metadata { get; } - private IPlexAvailabilityChecker Checker { get; } - public async Task CacheContent(bool recentlyAddedSearch = false) + public async Task Execute(IJobExecutionContext context) { + JobDataMap dataMap = context.JobDetail.JobDataMap; + var recentlyAddedSearch = dataMap.GetBooleanValueFromString("recentlyAddedSearch"); + var plexSettings = await Plex.GetSettingsAsync(); if (!plexSettings.Enable) { @@ -100,19 +103,23 @@ public async Task CacheContent(bool recentlyAddedSearch = false) if (!recentlyAddedSearch) { Logger.LogInformation("Starting EP Cacher"); - BackgroundJob.Enqueue(() => EpisodeSync.Start()); + + await OmbiQuartz.TriggerJob(nameof(IPlexEpisodeSync), "Plex"); } if ((processedContent?.HasProcessedContent ?? false) && recentlyAddedSearch) { // Just check what we send it - BackgroundJob.Enqueue(() => Metadata.ProcessPlexServerContent(processedContent.Content)); + await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System"); } - if ((processedContent?.HasProcessedEpisodes ?? false) && recentlyAddedSearch) + if ((processedContent?.HasProcessedContent ?? false) && recentlyAddedSearch) { - BackgroundJob.Enqueue(() => Checker.Start()); + + await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex"); } + + Logger.LogInformation("Finished Plex Content Cacher, with processed content: {0}, episodes: {0}", processedContent.Content.Count(), processedContent.Episodes.Count()); } private async Task StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch) @@ -190,10 +197,10 @@ private async Task ProcessServer(PlexServers servers, bool rec } contentToAdd.Clear(); } - if (count > 200) + if (count > 30) { await Repo.SaveChangesAsync(); - + count = 0; } } @@ -227,7 +234,7 @@ private async Task ProcessServer(PlexServers servers, bool rec } contentToAdd.Clear(); } - if (count > 200) + if (count > 30) { await Repo.SaveChangesAsync(); } diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs index 6ab5a5941..7414294be 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs @@ -13,28 +13,28 @@ using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; namespace Ombi.Schedule.Jobs.Plex { public class PlexEpisodeSync : IPlexEpisodeSync { public PlexEpisodeSync(ISettingsService s, ILogger log, IPlexApi plexApi, - IPlexContentRepository repo, IPlexAvailabilityChecker a) + IPlexContentRepository repo) { _settings = s; _log = log; _api = plexApi; _repo = repo; - _availabilityChecker = a; + _settings.ClearCache(); } private readonly ISettingsService _settings; private readonly ILogger _log; private readonly IPlexApi _api; private readonly IPlexContentRepository _repo; - private readonly IPlexAvailabilityChecker _availabilityChecker; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { try { @@ -55,7 +55,8 @@ public async Task Start() _log.LogError(LoggingEvents.Cacher, e, "Caching Episodes Failed"); } - BackgroundJob.Enqueue(() => _availabilityChecker.Start()); + + await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex"); } private async Task Cache(PlexServers settings) diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs index 51596f891..e26f07ba6 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs @@ -1,40 +1,40 @@ -using System; -using System.Threading.Tasks; -using Hangfire; +//using System; +//using System.Threading.Tasks; +//using Hangfire; -namespace Ombi.Schedule.Jobs.Plex -{ - public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync - { - public PlexRecentlyAddedSync(IPlexContentSync sync) - { - _sync = sync; - } +//namespace Ombi.Schedule.Jobs.Plex +//{ +// public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync +// { +// public PlexRecentlyAddedSync(IPlexContentSync sync) +// { +// _sync = sync; +// } - private readonly IPlexContentSync _sync; +// private readonly IPlexContentSync _sync; - public void Start() - { - BackgroundJob.Enqueue(() => _sync.CacheContent(true)); - } +// public void Start() +// { +// BackgroundJob.Enqueue(() => _sync.CacheContent(true)); +// } - private bool _disposed; - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; +// private bool _disposed; +// protected virtual void Dispose(bool disposing) +// { +// if (_disposed) +// return; - if (disposing) - { - _sync?.Dispose(); - } - _disposed = true; - } +// if (disposing) +// { +// _sync?.Dispose(); +// } +// _disposed = true; +// } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file +// public void Dispose() +// { +// Dispose(true); +// GC.SuppressFinalize(this); +// } +// } +//} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs index 105e8876d..7d30d780a 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs @@ -11,6 +11,7 @@ using Ombi.Helpers; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; +using Quartz; namespace Ombi.Schedule.Jobs.Plex { @@ -24,6 +25,8 @@ public PlexUserImporter(IPlexApi api, UserManager um, ILogger um, ILogger _userManagementSettings; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { var userManagementSettings = await _userManagementSettings.GetSettingsAsync(); if (!userManagementSettings.ImportPlexUsers) diff --git a/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs b/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs index a9f8edfcd..1fb191fd1 100644 --- a/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs @@ -6,7 +6,6 @@ namespace Ombi.Schedule.Jobs.Radarr { public interface IRadarrSync : IBaseJob { - Task CacheContent(); Task> GetCachedContent(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs b/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs index f61747ac6..6fa4c1bc8 100644 --- a/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs @@ -10,6 +10,7 @@ using Ombi.Settings.Settings.Models.External; using Ombi.Store.Context; using Ombi.Store.Entities; +using Quartz; using Serilog; namespace Ombi.Schedule.Jobs.Radarr @@ -22,6 +23,7 @@ public RadarrSync(ISettingsService radarr, IRadarrApi radarrApi, RadarrApi = radarrApi; Logger = log; _ctx = ctx; + RadarrSettings.ClearCache(); } private ISettingsService RadarrSettings { get; } @@ -31,7 +33,7 @@ public RadarrSync(ISettingsService radarr, IRadarrApi radarrApi, private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1); - public async Task CacheContent() + public async Task Execute(IJobExecutionContext job) { await SemaphoreSlim.WaitAsync(); try @@ -45,12 +47,16 @@ public async Task CacheContent() if (movies != null) { // Let's remove the old cached data - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM RadarrCache"); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM RadarrCache"); + tran.Commit(); + } var movieIds = new List(); foreach (var m in movies) { - if (m.tmdbId > 0) + if (m.tmdbId > 0 && m.monitored) { movieIds.Add(new RadarrCache { @@ -63,9 +69,14 @@ public async Task CacheContent() Logger.LogError("TMDBId is not > 0 for movie {0}", m.title); } } - await _ctx.RadarrCache.AddRangeAsync(movieIds); - await _ctx.SaveChangesAsync(); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.RadarrCache.AddRangeAsync(movieIds); + + await _ctx.SaveChangesAsync(); + tran.Commit(); + } } } catch (System.Exception ex) @@ -109,4 +120,4 @@ public void Dispose() GC.SuppressFinalize(this); } } -} \ No newline at end of file +} diff --git a/src/Ombi.Schedule/Jobs/SickRage/ISickRageSync.cs b/src/Ombi.Schedule/Jobs/SickRage/ISickRageSync.cs index df51698fe..2fcd435a8 100644 --- a/src/Ombi.Schedule/Jobs/SickRage/ISickRageSync.cs +++ b/src/Ombi.Schedule/Jobs/SickRage/ISickRageSync.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.SickRage { public interface ISickRageSync : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs b/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs index 2c8d03b1d..b6cca4c78 100644 --- a/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs +++ b/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs @@ -11,6 +11,7 @@ using Ombi.Settings.Settings.Models.External; using Ombi.Store.Context; using Ombi.Store.Entities; +using Quartz; namespace Ombi.Schedule.Jobs.SickRage { @@ -22,6 +23,7 @@ public SickRageSync(ISettingsService s, ISickRageApi api, ILog _api = api; _log = l; _ctx = ctx; + _settings.ClearCache(); } private readonly ISettingsService _settings; @@ -29,7 +31,7 @@ public SickRageSync(ISettingsService s, ISickRageApi api, ILog private readonly ILogger _log; private readonly IExternalContext _ctx; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { try { @@ -44,8 +46,12 @@ public async Task Start() { var srShows = shows.data.Values; var ids = srShows.Select(x => x.tvdbid); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SickRageCache"); + tran.Commit(); + } - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SickRageCache"); var entites = ids.Select(id => new SickRageCache { TvDbId = id }).ToList(); await _ctx.SickRageCache.AddRangeAsync(entites); @@ -72,8 +78,12 @@ public async Task Start() } - await _ctx.SickRageEpisodeCache.AddRangeAsync(episodesToAdd); - await _ctx.SaveChangesAsync(); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.SickRageEpisodeCache.AddRangeAsync(episodesToAdd); + await _ctx.SaveChangesAsync(); + tran.Commit(); + } } } catch (Exception e) diff --git a/src/Ombi.Schedule/Jobs/Sonarr/ISonarrSync.cs b/src/Ombi.Schedule/Jobs/Sonarr/ISonarrSync.cs index a1ba9f3c2..f190be3c5 100644 --- a/src/Ombi.Schedule/Jobs/Sonarr/ISonarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Sonarr/ISonarrSync.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Sonarr { public interface ISonarrSync : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs index c77e23394..f375ef064 100644 --- a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs @@ -14,6 +14,7 @@ using Ombi.Settings.Settings.Models.External; using Ombi.Store.Context; using Ombi.Store.Entities; +using Quartz; namespace Ombi.Schedule.Jobs.Sonarr { @@ -25,6 +26,7 @@ public SonarrSync(ISettingsService s, ISonarrApi api, ILogger _settings; @@ -32,7 +34,7 @@ public SonarrSync(ISettingsService s, ISonarrApi api, ILogger _log; private readonly IExternalContext _ctx; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { try { @@ -46,14 +48,22 @@ public async Task Start() { var sonarrSeries = series as ImmutableHashSet ?? series.ToImmutableHashSet(); var ids = sonarrSeries.Select(x => x.tvdbId); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache"); + tran.Commit(); + } - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache"); var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToImmutableHashSet(); await _ctx.SonarrCache.AddRangeAsync(entites); entites.Clear(); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache"); + tran.Commit(); + } - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache"); foreach (var s in sonarrSeries) { if (!s.monitored) @@ -66,15 +76,20 @@ public async Task Start() // Add to DB _log.LogDebug("We have the episodes, adding to db transaction"); - await _ctx.SonarrEpisodeCache.AddRangeAsync(monitoredEpisodes.Select(episode => new SonarrEpisodeCache + using (var tran = await _ctx.Database.BeginTransactionAsync()) { - EpisodeNumber = episode.episodeNumber, - SeasonNumber = episode.seasonNumber, - TvDbId = s.tvdbId, - HasFile = episode.hasFile - })); - _log.LogDebug("Commiting the transaction"); - await _ctx.SaveChangesAsync(); + await _ctx.SonarrEpisodeCache.AddRangeAsync(monitoredEpisodes.Select(episode => + new SonarrEpisodeCache + { + EpisodeNumber = episode.episodeNumber, + SeasonNumber = episode.seasonNumber, + TvDbId = s.tvdbId, + HasFile = episode.hasFile + })); + _log.LogDebug("Commiting the transaction"); + await _ctx.SaveChangesAsync(); + tran.Commit(); + } } } diff --git a/src/Ombi.Schedule/Ombi.Schedule.csproj b/src/Ombi.Schedule/Ombi.Schedule.csproj index ff3a17115..ef6b1d447 100644 --- a/src/Ombi.Schedule/Ombi.Schedule.csproj +++ b/src/Ombi.Schedule/Ombi.Schedule.csproj @@ -10,13 +10,14 @@ - - + + + diff --git a/src/Ombi.Schedule/OmbiQuartz.cs b/src/Ombi.Schedule/OmbiQuartz.cs new file mode 100644 index 000000000..715bb187b --- /dev/null +++ b/src/Ombi.Schedule/OmbiQuartz.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Helpers; +using Quartz; +using Quartz.Impl; +using Quartz.Spi; + +namespace Ombi.Schedule +{ + public class OmbiQuartz + { + protected IScheduler _scheduler { get; set; } + + public static IScheduler Scheduler => Instance._scheduler; + + // Singleton + protected static OmbiQuartz _instance; + + /// + /// Singleton + /// + public static OmbiQuartz Instance => _instance ?? (_instance = new OmbiQuartz()); + + protected OmbiQuartz() + { + Init(); + } + + private async void Init() + { + _scheduler = await new StdSchedulerFactory().GetScheduler(); + } + + public IScheduler UseJobFactory(IJobFactory jobFactory) + { + Scheduler.JobFactory = jobFactory; + return Scheduler; + } + + public async Task AddJob(string name, string group, string cronExpression, Dictionary jobData = null) + where T : IJob + { + var jobBuilder = JobBuilder.Create() + .WithIdentity(new JobKey(name, group)); + if (jobData != null) + { + foreach (var o in jobData) + { + jobBuilder.UsingJobData(o.Key, o.Value); + } + } + + if(!cronExpression.HasValue()) + { + jobBuilder.StoreDurably(true); + } + + var job = jobBuilder.Build(); + if (cronExpression.HasValue()) + { + ITrigger jobTrigger = TriggerBuilder.Create() + .WithIdentity(name + "Trigger", group) + .WithCronSchedule(cronExpression, + x => x.WithMisfireHandlingInstructionFireAndProceed()) + .ForJob(name, group) + .StartNow() + .Build(); + await Scheduler.ScheduleJob(job, jobTrigger); + } + else + { + await Scheduler.AddJob(job, true); + } + + } + + public static async Task TriggerJob(string jobName, string group) + { + await Scheduler.TriggerJob(new JobKey(jobName, group)); + } + + public static async Task Start() + { + await Scheduler.Start(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/OmbiScheduler.cs b/src/Ombi.Schedule/OmbiScheduler.cs new file mode 100644 index 000000000..37240cd52 --- /dev/null +++ b/src/Ombi.Schedule/OmbiScheduler.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Ombi.Core.Settings; +using Ombi.Schedule.Jobs; +using Ombi.Schedule.Jobs.Couchpotato; +using Ombi.Schedule.Jobs.Emby; +using Ombi.Schedule.Jobs.Lidarr; +using Ombi.Schedule.Jobs.Ombi; +using Ombi.Schedule.Jobs.Plex; +using Ombi.Schedule.Jobs.Plex.Interfaces; +using Ombi.Schedule.Jobs.Radarr; +using Ombi.Schedule.Jobs.SickRage; +using Ombi.Schedule.Jobs.Sonarr; +using Ombi.Settings.Settings.Models; +using Quartz; +using Quartz.Spi; + +namespace Ombi.Schedule +{ + public static class OmbiScheduler + { + //public void Setup() + //{ + // CreateJobDefinitions(); + //} + + //public void CreateJobDefinitions() + //{ + // var contentSync = JobBuilder.Create() + // .UsingJobData("recentlyAddedSearch", false) + // .WithIdentity(nameof(PlexContentSync), "Plex") + // .Build(); + + // var recentlyAdded = JobBuilder.Create() + // .UsingJobData("recentlyAddedSearch", true) + // .WithIdentity("PlexRecentlyAdded", "Plex") + // .Build(); + //} + + public static async Task UseQuartz(this IApplicationBuilder app) + { + // Job Factory through IOC container + var jobFactory = (IJobFactory)app.ApplicationServices.GetService(typeof(IJobFactory)); + var service = (ISettingsService)app.ApplicationServices.GetService(typeof(ISettingsService)); + var s = service.GetSettings(); + // Set job factory + OmbiQuartz.Instance.UseJobFactory(jobFactory); + + // Run configuration + await AddPlex(s); + await AddEmby(s); + await AddDvrApps(s); + await AddSystem(s); + + // Run Quartz + await OmbiQuartz.Start(); + } + + private static async Task AddSystem(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(IRefreshMetadata), "System", JobSettingsHelper.RefreshMetadata(s)); + await OmbiQuartz.Instance.AddJob(nameof(IIssuesPurge), "System", JobSettingsHelper.IssuePurge(s)); + //OmbiQuartz.Instance.AddJob(nameof(IOmbiAutomaticUpdater), "System", JobSettingsHelper.Updater(s)); + await OmbiQuartz.Instance.AddJob(nameof(INewsletterJob), "System", JobSettingsHelper.Newsletter(s)); + await OmbiQuartz.Instance.AddJob(nameof(IResendFailedRequests), "System", JobSettingsHelper.ResendFailedRequests(s)); + await OmbiQuartz.Instance.AddJob(nameof(IMediaDatabaseRefresh), "System", JobSettingsHelper.MediaDatabaseRefresh(s)); + } + + private static async Task AddDvrApps(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(ISonarrSync), "DVR", JobSettingsHelper.Sonarr(s)); + await OmbiQuartz.Instance.AddJob(nameof(IRadarrSync), "DVR", JobSettingsHelper.Radarr(s)); + await OmbiQuartz.Instance.AddJob(nameof(ICouchPotatoSync), "DVR", JobSettingsHelper.CouchPotato(s)); + await OmbiQuartz.Instance.AddJob(nameof(ISickRageSync), "DVR", JobSettingsHelper.SickRageSync(s)); + await OmbiQuartz.Instance.AddJob(nameof(ILidarrArtistSync), "DVR", JobSettingsHelper.LidarrArtistSync(s)); + } + + private static async Task AddPlex(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(IPlexContentSync), "Plex", JobSettingsHelper.PlexContent(s), new Dictionary { { "recentlyAddedSearch", "false" } }); + await OmbiQuartz.Instance.AddJob(nameof(IPlexContentSync) + "RecentlyAdded", "Plex", JobSettingsHelper.PlexRecentlyAdded(s), new Dictionary { { "recentlyAddedSearch", "true" } }); + await OmbiQuartz.Instance.AddJob(nameof(IPlexUserImporter), "Plex", JobSettingsHelper.UserImporter(s)); + await OmbiQuartz.Instance.AddJob(nameof(IPlexEpisodeSync), "Plex", null); + await OmbiQuartz.Instance.AddJob(nameof(IPlexAvailabilityChecker), "Plex", null); + } + + private static async Task AddEmby(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(IEmbyContentSync), "Emby", JobSettingsHelper.EmbyContent(s)); + await OmbiQuartz.Instance.AddJob(nameof(IEmbyEpisodeSync), "Emby", null); + await OmbiQuartz.Instance.AddJob(nameof(IEmbyAvaliabilityChecker), "Emby", null); + await OmbiQuartz.Instance.AddJob(nameof(IEmbyUserImporter), "Emby", JobSettingsHelper.UserImporter(s)); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Settings/Ombi.Settings.csproj b/src/Ombi.Settings/Ombi.Settings.csproj index 6db0768aa..39e53c9ff 100644 --- a/src/Ombi.Settings/Ombi.Settings.csproj +++ b/src/Ombi.Settings/Ombi.Settings.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs index e2080e3cb..272ad3444 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs @@ -1,5 +1,6 @@ using System; using Ombi.Helpers; +using Quartz; namespace Ombi.Settings.Settings.Models { @@ -7,71 +8,93 @@ public static class JobSettingsHelper { public static string Radarr(JobSettings s) { - return Get(s.RadarrSync, Cron.Hourly(15)); + return ValidateCron(Get(s.RadarrSync, Cron.Hourly(15))); } public static string Sonarr(JobSettings s) { - return Get(s.SonarrSync, Cron.Hourly(10)); + return ValidateCron(Get(s.SonarrSync, Cron.Hourly(10))); } public static string EmbyContent(JobSettings s) { - return Get(s.EmbyContentSync, Cron.Hourly(5)); + return ValidateCron(Get(s.EmbyContentSync, Cron.Hourly(5))); } + public static string PlexContent(JobSettings s) { - return Get(s.PlexContentSync, Cron.Daily(2)); + return ValidateCron(Get(s.PlexContentSync, Cron.Daily(2))); } + public static string PlexRecentlyAdded(JobSettings s) { - return Get(s.PlexRecentlyAddedSync, Cron.MinuteInterval(30)); + return ValidateCron(Get(s.PlexRecentlyAddedSync, Cron.MinuteInterval(30))); } + public static string CouchPotato(JobSettings s) { - return Get(s.CouchPotatoSync, Cron.Hourly(30)); + return ValidateCron(Get(s.CouchPotatoSync, Cron.Hourly(30))); } public static string Updater(JobSettings s) { - return Get(s.AutomaticUpdater, Cron.HourInterval(6)); + return ValidateCron(Get(s.AutomaticUpdater, Cron.HourInterval(6))); } + public static string UserImporter(JobSettings s) { - return Get(s.UserImporter, Cron.Daily()); + return ValidateCron(Get(s.UserImporter, Cron.Daily())); } + public static string Newsletter(JobSettings s) { - return Get(s.Newsletter, Cron.Weekly(DayOfWeek.Friday, 12)); + return ValidateCron(Get(s.Newsletter, Cron.Weekly(Helpers.DayOfWeek.Friday, 12))); } + public static string SickRageSync(JobSettings s) { - return Get(s.SickRageSync, Cron.Hourly(35)); + return ValidateCron(Get(s.SickRageSync, Cron.Hourly(35))); } + public static string RefreshMetadata(JobSettings s) { - return Get(s.RefreshMetadata, Cron.DayInterval(3)); + return ValidateCron(Get(s.RefreshMetadata, Cron.DayInterval(3))); } + public static string LidarrArtistSync(JobSettings s) { - return Get(s.LidarrArtistSync, Cron.Hourly(40)); + return ValidateCron(Get(s.LidarrArtistSync, Cron.Hourly(40))); } public static string IssuePurge(JobSettings s) { - return Get(s.IssuesPurge, Cron.Daily()); + return ValidateCron(Get(s.IssuesPurge, Cron.Daily())); } + public static string ResendFailedRequests(JobSettings s) { - return Get(s.RetryRequests, Cron.Daily(6)); + return ValidateCron(Get(s.RetryRequests, Cron.Daily(6))); } + public static string MediaDatabaseRefresh(JobSettings s) { - return Get(s.MediaDatabaseRefresh, Cron.DayInterval(5)); + return ValidateCron(Get(s.MediaDatabaseRefresh, Cron.DayInterval(5))); } + private static string Get(string settings, string defaultCron) { return settings.HasValue() ? settings : defaultCron; } + + private const string _defaultCron = "0 0 12 1/1 * ? *"; + + private static string ValidateCron(string cron) + { + if (CronExpression.IsValidExpression(cron)) + { + return cron; + } + return _defaultCron; + } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/Notifications/GotifySettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/GotifySettings.cs new file mode 100644 index 000000000..f0325b0f2 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/Notifications/GotifySettings.cs @@ -0,0 +1,10 @@ +namespace Ombi.Settings.Settings.Models.Notifications +{ + public class GotifySettings : Settings + { + public bool Enabled { get; set; } + public string BaseUrl { get; set; } + public string ApplicationToken { get; set; } + public sbyte Priority { get; set; } = 4; + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/ExternalContext.cs b/src/Ombi.Store/Context/ExternalContext.cs index eb2be6450..19cb77fc4 100644 --- a/src/Ombi.Store/Context/ExternalContext.cs +++ b/src/Ombi.Store/Context/ExternalContext.cs @@ -13,6 +13,7 @@ public ExternalContext() if (_created) return; _created = true; + Database.SetCommandTimeout(60); Database.Migrate(); } @@ -62,7 +63,12 @@ public void Seed() { // VACUUM; Database.ExecuteSqlCommand("VACUUM;"); - SaveChanges(); + + using (var tran = Database.BeginTransaction()) + { + SaveChanges(); + tran.Commit(); + } } } } \ No newline at end of file diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 47b245603..ea61b253f 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -17,7 +17,9 @@ public OmbiContext() { if (_created) return; + _created = true; + Database.SetCommandTimeout(60); Database.Migrate(); } @@ -87,18 +89,23 @@ protected override void OnModelCreating(ModelBuilder builder) public void Seed() { - // Make sure we have the API User - var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase)); - if (!apiUserExists) + + using (var tran = Database.BeginTransaction()) { - Users.Add(new OmbiUser + // Make sure we have the API User + var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase)); + if (!apiUserExists) { - UserName = "Api", - UserType = UserType.SystemUser, - NormalizedUserName = "API", + Users.Add(new OmbiUser + { + UserName = "Api", + UserType = UserType.SystemUser, + NormalizedUserName = "API", - }); - SaveChanges(); + }); + SaveChanges(); + tran.Commit(); + } } //Check if templates exist @@ -107,6 +114,7 @@ public void Seed() var allAgents = Enum.GetValues(typeof(NotificationAgent)).Cast().ToList(); var allTypes = Enum.GetValues(typeof(NotificationType)).Cast().ToList(); + var needToSave = false; foreach (var agent in allAgents) { foreach (var notificationType in allTypes) @@ -116,6 +124,8 @@ public void Seed() // We already have this continue; } + + needToSave = true; NotificationTemplates notificationToAdd; switch (notificationType) { @@ -230,7 +240,16 @@ public void Seed() NotificationTemplates.Add(notificationToAdd); } } - SaveChanges(); + + if (needToSave) + { + + using (var tran = Database.BeginTransaction()) + { + SaveChanges(); + tran.Commit(); + } + } } } } \ No newline at end of file diff --git a/src/Ombi.Store/Context/SettingsContext.cs b/src/Ombi.Store/Context/SettingsContext.cs index 6a53e598f..48ef0e6c7 100644 --- a/src/Ombi.Store/Context/SettingsContext.cs +++ b/src/Ombi.Store/Context/SettingsContext.cs @@ -14,6 +14,7 @@ public SettingsContext() if (_created) return; _created = true; + Database.SetCommandTimeout(60); Database.Migrate(); } @@ -32,44 +33,45 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) public void Seed() { - // Add the tokens - var fanArt = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); - if (fanArt == null) + + using (var tran = Database.BeginTransaction()) { - ApplicationConfigurations.Add(new ApplicationConfiguration + // Add the tokens + var fanArt = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); + if (fanArt == null) { - Type = ConfigurationTypes.FanartTv, - Value = "4b6d983efa54d8f45c68432521335f15" - }); - SaveChanges(); - } - var movieDb = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); - if (movieDb == null) - { - ApplicationConfigurations.Add(new ApplicationConfiguration + ApplicationConfigurations.Add(new ApplicationConfiguration + { + Type = ConfigurationTypes.FanartTv, + Value = "4b6d983efa54d8f45c68432521335f15" + }); + SaveChanges(); + } + + var movieDb = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); + if (movieDb == null) { - Type = ConfigurationTypes.TheMovieDb, - Value = "b8eabaf5608b88d0298aa189dd90bf00" - }); - SaveChanges(); - } - var notification = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.Notification); - if (notification == null) - { - ApplicationConfigurations.Add(new ApplicationConfiguration + ApplicationConfigurations.Add(new ApplicationConfiguration + { + Type = ConfigurationTypes.TheMovieDb, + Value = "b8eabaf5608b88d0298aa189dd90bf00" + }); + SaveChanges(); + } + + var notification = + ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.Notification); + if (notification == null) { - Type = ConfigurationTypes.Notification, - Value = "4f0260c4-9c3d-41ab-8d68-27cb5a593f0e" - }); - SaveChanges(); + ApplicationConfigurations.Add(new ApplicationConfiguration + { + Type = ConfigurationTypes.Notification, + Value = "4f0260c4-9c3d-41ab-8d68-27cb5a593f0e" + }); + SaveChanges(); + } + tran.Commit(); } - - SaveChanges(); - } - - ~SettingsContext() - { - } } } \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.Designer.cs b/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.Designer.cs new file mode 100644 index 000000000..205a98b11 --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.Designer.cs @@ -0,0 +1,50 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.Settings +{ + [DbContext(typeof(SettingsContext))] + [Migration("20190416204533_ResetSchedules")] + partial class ResetSchedules + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.2-servicing-10034"); + + 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.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.cs b/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.cs new file mode 100644 index 000000000..0a3b9312c --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations.Settings +{ + public partial class ResetSchedules : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + DELETE FROM GlobalSettings + WHERE SettingsName = 'JobSettings' +"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs b/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs index fe063ef8b..7a605792d 100644 --- a/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs @@ -13,7 +13,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.2.0-rtm-35687"); + .HasAnnotation("ProductVersion", "2.2.2-servicing-10034"); modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => { diff --git a/src/Ombi.Store/Ombi.Store.csproj b/src/Ombi.Store/Ombi.Store.csproj index f905c9ffe..dd13d5ea7 100644 --- a/src/Ombi.Store/Ombi.Store.csproj +++ b/src/Ombi.Store/Ombi.Store.csproj @@ -12,9 +12,11 @@ - - + + + + diff --git a/src/Ombi.Store/Repository/AuditRepository.cs b/src/Ombi.Store/Repository/AuditRepository.cs index a6fc42c3f..858cd0eaf 100644 --- a/src/Ombi.Store/Repository/AuditRepository.cs +++ b/src/Ombi.Store/Repository/AuditRepository.cs @@ -24,16 +24,20 @@ public async Task Record(AuditType type, AuditArea area, string description) public async Task Record(AuditType type, AuditArea area, string description, string user) { - await Ctx.Audit.AddAsync(new Audit + using (var tran = await Ctx.Database.BeginTransactionAsync()) { - User = user, - AuditArea = area, - AuditType = type, - DateTime = DateTime.UtcNow, - Description = description - }); + await Ctx.Audit.AddAsync(new Audit + { + User = user, + AuditArea = area, + AuditType = type, + DateTime = DateTime.UtcNow, + Description = description + }); - await Ctx.SaveChangesAsync(); + await Ctx.SaveChangesAsync(); + tran.Commit(); + } } } } diff --git a/src/Ombi.Store/Repository/BaseRepository.cs b/src/Ombi.Store/Repository/BaseRepository.cs index 1679035dd..82661e0c3 100644 --- a/src/Ombi.Store/Repository/BaseRepository.cs +++ b/src/Ombi.Store/Repository/BaseRepository.cs @@ -3,10 +3,13 @@ using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Query; +using Ombi.Helpers; using Ombi.Store.Context; using Ombi.Store.Entities; +using Polly; namespace Ombi.Store.Repository { @@ -30,7 +33,7 @@ public IQueryable GetAll() return _db.AsQueryable(); } - public async Task FirstOrDefaultAsync(Expression> predicate) + public async Task FirstOrDefaultAsync(Expression> predicate) { return await _db.FirstOrDefaultAsync(predicate); } @@ -40,32 +43,32 @@ public async Task AddRange(IEnumerable content, bool save = true) _db.AddRange(content); if (save) { - await _ctx.SaveChangesAsync(); + await InternalSaveChanges(); } } public async Task Add(T content) { await _db.AddAsync(content); - await _ctx.SaveChangesAsync(); + await InternalSaveChanges(); return content; } public async Task Delete(T request) { _db.Remove(request); - await _ctx.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task DeleteRange(IEnumerable req) { _db.RemoveRange(req); - await _ctx.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task SaveChangesAsync() { - return await _ctx.SaveChangesAsync(); + return await InternalSaveChanges(); } public IIncludableQueryable Include( @@ -80,6 +83,29 @@ public async Task ExecuteSql(string sql) await _ctx.Database.ExecuteSqlCommandAsync(sql); } + protected async Task InternalSaveChanges() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(3) + }); + + var result = await policy.ExecuteAndCaptureAsync(async () => + { + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + var r = await _ctx.SaveChangesAsync(); + tran.Commit(); + return r; + } + }); + return result.Result; + } + private bool _disposed; // Protected implementation of Dispose pattern. @@ -92,7 +118,7 @@ protected virtual void Dispose(bool disposing) { _ctx?.Dispose(); } - + _disposed = true; } diff --git a/src/Ombi.Store/Repository/EmbyContentRepository.cs b/src/Ombi.Store/Repository/EmbyContentRepository.cs index 4d32e8da2..2ada709ab 100644 --- a/src/Ombi.Store/Repository/EmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/EmbyContentRepository.cs @@ -72,7 +72,7 @@ public async Task GetByEmbyId(string embyId) public async Task Update(EmbyContent existingContent) { Db.EmbyContent.Update(existingContent); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public IQueryable GetAllEpisodes() @@ -83,7 +83,7 @@ public IQueryable GetAllEpisodes() public async Task Add(EmbyEpisode content) { await Db.EmbyEpisode.AddAsync(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); return content; } public async Task GetEpisodeByEmbyId(string key) @@ -94,12 +94,13 @@ public async Task GetEpisodeByEmbyId(string key) public async Task AddRange(IEnumerable content) { Db.EmbyEpisode.AddRange(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public void UpdateWithoutSave(EmbyContent existingContent) { Db.EmbyContent.Update(existingContent); } + } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs index 175d0e6a9..8f3968dc0 100644 --- a/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs +++ b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs @@ -45,7 +45,7 @@ public async Task Update(NotificationTemplates template) Db.Attach(template); Db.Entry(template).State = EntityState.Modified; } - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task UpdateRange(IEnumerable templates) @@ -56,16 +56,21 @@ public async Task UpdateRange(IEnumerable templates) Db.Attach(t); Db.Entry(t).State = EntityState.Modified; } - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task Insert(NotificationTemplates entity) { var settings = await Db.NotificationTemplates.AddAsync(entity).ConfigureAwait(false); - await Db.SaveChangesAsync().ConfigureAwait(false); + await InternalSaveChanges().ConfigureAwait(false); return settings.Entity; } + private async Task InternalSaveChanges() + { + return await Db.SaveChangesAsync(); + } + private bool _disposed; // Protected implementation of Dispose pattern. protected virtual void Dispose(bool disposing) diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index 2c9c28d09..37275a47c 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -96,7 +96,7 @@ public async Task GetFirstContentByCustom(Expression existingContent) { Db.PlexServerContent.UpdateRange(existingContent); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public IQueryable GetAllEpisodes() @@ -127,14 +127,14 @@ public void DeleteWithoutSave(PlexEpisode content) public async Task Add(PlexEpisode content) { await Db.PlexEpisode.AddAsync(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); return content; } public async Task DeleteEpisode(PlexEpisode content) { Db.PlexEpisode.Remove(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task GetEpisodeByKey(int key) @@ -144,7 +144,7 @@ public async Task GetEpisodeByKey(int key) public async Task AddRange(IEnumerable content) { Db.PlexEpisode.AddRange(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs b/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs index d4a550528..2cea81200 100644 --- a/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; using Ombi.Store.Context; using Ombi.Store.Entities.Requests; @@ -70,12 +71,12 @@ public async Task Update(MovieRequests request) Db.MovieRequests.Attach(request); Db.Update(request); } - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task Save() { - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs b/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs index 59edf265a..971d53b39 100644 --- a/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; using Ombi.Store.Context; using Ombi.Store.Entities.Requests; @@ -61,12 +62,12 @@ public async Task Update(AlbumRequest request) Db.AlbumRequests.Attach(request); Db.Update(request); } - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task Save() { - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs b/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs index daac7d4df..6528f0969 100644 --- a/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs @@ -2,14 +2,15 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; using Ombi.Store.Context; using Ombi.Store.Entities.Requests; namespace Ombi.Store.Repository.Requests { - public class TvRequestRepository : ITvRequestRepository + public class TvRequestRepository : BaseRepository, ITvRequestRepository { - public TvRequestRepository(IOmbiContext ctx) + public TvRequestRepository(IOmbiContext ctx) : base(ctx) { Db = ctx; } @@ -101,20 +102,20 @@ public IQueryable GetChild(string userId) public async Task Save() { - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task Add(TvRequests request) { await Db.TvRequests.AddAsync(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); return request; } public async Task AddChild(ChildRequests request) { await Db.ChildRequests.AddAsync(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); return request; } @@ -122,33 +123,33 @@ public async Task AddChild(ChildRequests request) public async Task Delete(TvRequests request) { Db.TvRequests.Remove(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task DeleteChild(ChildRequests request) { Db.ChildRequests.Remove(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task DeleteChildRange(IEnumerable request) { Db.ChildRequests.RemoveRange(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task Update(TvRequests request) { Db.Update(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task UpdateChild(ChildRequests request) { Db.Update(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/SettingsJsonRepository.cs b/src/Ombi.Store/Repository/SettingsJsonRepository.cs index 66cf57b18..622e32997 100644 --- a/src/Ombi.Store/Repository/SettingsJsonRepository.cs +++ b/src/Ombi.Store/Repository/SettingsJsonRepository.cs @@ -24,17 +24,28 @@ public SettingsJsonRepository(ISettingsContext ctx, ICacheService mem) public GlobalSettings Insert(GlobalSettings entity) { //_cache.Remove(GetName(entity.SettingsName)); - var settings = Db.Settings.Add(entity); - Db.SaveChanges(); - return settings.Entity; + + using (var tran = Db.Database.BeginTransaction()) + { + var settings = Db.Settings.Add(entity); + Db.SaveChanges(); + tran.Commit(); + return settings.Entity; + } } public async Task InsertAsync(GlobalSettings entity) { - //_cache.Remove(GetName(entity.SettingsName)); - var settings = await Db.Settings.AddAsync(entity).ConfigureAwait(false); - await Db.SaveChangesAsync().ConfigureAwait(false); - return settings.Entity; + + using (var tran = Db.Database.BeginTransaction()) + { + //_cache.Remove(GetName(entity.SettingsName)); + var settings = await Db.Settings.AddAsync(entity); + await Db.SaveChangesAsync(); + tran.Commit(); + + return settings.Entity; + } } @@ -43,8 +54,8 @@ public GlobalSettings Get(string pageName) //return _cache.GetOrCreate(GetName(pageName), entry => //{ // entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); - var entity = Db.Settings.AsNoTracking().FirstOrDefault(x => x.SettingsName == pageName); - return entity; + var entity = Db.Settings.AsNoTracking().FirstOrDefault(x => x.SettingsName == pageName); + return entity; //}); } @@ -52,9 +63,9 @@ public async Task GetAsync(string settingsName) { //return await _cache.GetOrCreateAsync(GetName(settingsName), async entry => //{ - //entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); - var obj = await Db.Settings.AsNoTracking().FirstOrDefaultAsync(x => x.SettingsName == settingsName); - return obj; + //entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); + var obj = await Db.Settings.AsNoTracking().FirstOrDefaultAsync(x => x.SettingsName == settingsName); + return obj; //}); } @@ -62,28 +73,37 @@ public async Task DeleteAsync(GlobalSettings entity) { //_cache.Remove(GetName(entity.SettingsName)); Db.Settings.Remove(entity); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task UpdateAsync(GlobalSettings entity) { //_cache.Remove(GetName(entity.SettingsName)); Db.Update(entity); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public void Delete(GlobalSettings entity) { //_cache.Remove(GetName(entity.SettingsName)); - Db.Settings.Remove(entity); - Db.SaveChanges(); + + using (var tran = Db.Database.BeginTransaction()) + { + Db.Settings.Remove(entity); + Db.SaveChanges(); + tran.Commit(); + } } public void Update(GlobalSettings entity) { - Db.Update(entity); - //_cache.Remove(GetName(entity.SettingsName)); - Db.SaveChanges(); + using (var tran = Db.Database.BeginTransaction()) + { + Db.Update(entity); + //_cache.Remove(GetName(entity.SettingsName)); + Db.SaveChanges(); + tran.Commit(); + } } private string GetName(string entity) @@ -91,6 +111,17 @@ private string GetName(string entity) return $"{entity}Json"; } + private async Task InternalSaveChanges() + { + + using (var tran = Db.Database.BeginTransaction()) + { + var r = await Db.SaveChangesAsync(); + tran.Commit(); + return r; + } + } + private bool _disposed; protected virtual void Dispose(bool disposing) { diff --git a/src/Ombi.Store/Repository/TokenRepository.cs b/src/Ombi.Store/Repository/TokenRepository.cs index d766c5690..4e35b0aa8 100644 --- a/src/Ombi.Store/Repository/TokenRepository.cs +++ b/src/Ombi.Store/Repository/TokenRepository.cs @@ -4,12 +4,13 @@ using System; using System.Linq; using System.Threading.Tasks; +using Ombi.Helpers; namespace Ombi.Store.Repository { - public class TokenRepository : ITokenRepository + public class TokenRepository : BaseRepository, ITokenRepository { - public TokenRepository(IOmbiContext db) + public TokenRepository(IOmbiContext db) : base(db) { Db = db; } @@ -19,7 +20,7 @@ public TokenRepository(IOmbiContext db) public async Task CreateToken(Tokens token) { await Db.Tokens.AddAsync(token); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public IQueryable GetToken(string tokenId) diff --git a/src/Ombi.sln b/src/Ombi.sln index 2b9be2c42..f4f683c11 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -96,7 +96,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Notifications", "O EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Lidarr", "Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj", "{4FA21A20-92F4-462C-B929-2C517A88CC56}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Gotify", "Ombi.Api.Gotify\Ombi.Api.Gotify.csproj", "{105EA346-766E-45B8-928B-DE6991DCB7EB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -256,6 +258,10 @@ Global {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.Build.0 = Release|Any CPU + {105EA346-766E-45B8-928B-DE6991DCB7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {105EA346-766E-45B8-928B-DE6991DCB7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {105EA346-766E-45B8-928B-DE6991DCB7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {105EA346-766E-45B8-928B-DE6991DCB7EB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -293,6 +299,7 @@ Global {10D1FE9D-9124-42B7-B1E1-CEB99B832618} = {9293CA11-360A-4C20-A674-B9E794431BF5} {4FA21A20-92F4-462C-B929-2C517A88CC56} = {9293CA11-360A-4C20-A674-B9E794431BF5} {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3} = {6F42AB98-9196-44C4-B888-D5E409F415A1} + {105EA346-766E-45B8-928B-DE6991DCB7EB} = {9293CA11-360A-4C20-A674-B9E794431BF5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869} diff --git a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts index f368ee035..5472a6c7c 100644 --- a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts @@ -93,6 +93,14 @@ export interface IPushoverNotificationSettings extends INotificationSettings { sound: string; } +export interface IGotifyNotificationSettings extends INotificationSettings { + accessToken: string; + notificationTemplates: INotificationTemplates[]; + baseUrl: string; + applicationToken: string; + priority: number; +} + export interface IMattermostNotifcationSettings extends INotificationSettings { webhookUrl: string; username: string; diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index 070dabb8d..2145ba9e6 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -222,7 +222,6 @@ export interface IIssueCategory extends ISettings { export interface ICronTestModel { success: boolean; message: string; - schedule: Date[]; } export interface ICronViewModelBody { diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.html b/src/Ombi/ClientApp/app/requests/movierequests.component.html index 6f3b4dd00..3f8485fce 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.html +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.html @@ -198,8 +198,8 @@

{{request.title}} ({{request.releaseDate | amLocal | a
-
-
@@ -285,4 +285,4 @@

{{ 'Filter.FilterHeaderRequestStatus' | translate }}

- \ No newline at end of file + diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts index b10173042..2bc7b6282 100644 --- a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts @@ -90,11 +90,10 @@ export class MusicRequestsComponent implements OnInit { this.searchChanged.next(text.target.value); } - public removeRequest(request: IAlbumRequest) { - this.requestService.removeAlbumRequest(request).subscribe(x => { - this.removeRequestFromUi(request); - this.loadRequests(this.amountToLoad, this.currentlyLoaded = 0); - }); + public async removeRequest(request: IAlbumRequest) { + await this.requestService.removeAlbumRequest(request).toPromise(); + this.removeRequestFromUi(request); + this.loadRequests(this.amountToLoad, this.currentlyLoaded = 0); } public changeAvailability(request: IAlbumRequest, available: boolean) { @@ -335,7 +334,7 @@ export class MusicRequestsComponent implements OnInit { } private setAlbumBackground(req: IAlbumRequest) { if (req.disk === null) { - if(req.cover === null) { + if (req.cover === null) { req.disk = this.defaultPoster; } else { req.disk = req.cover; diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index 7d8a4653a..ed669e0f5 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -2,7 +2,7 @@
-
@@ -181,4 +181,4 @@

{{result.title}} ({{result.releaseDate | amLocal | amDateFormat: 'YYYY'}}) \ No newline at end of file + [issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"> diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html index 8f2fe73ce..2b31df040 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html @@ -80,7 +80,7 @@

{{ 'Common.Available' | translate }}
-
+
diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.html b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html index e0d95203b..89af8de01 100644 --- a/src/Ombi/ClientApp/app/search/music/musicsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html @@ -1,7 +1,7 @@ 
- +
@@ -49,4 +49,4 @@ \ No newline at end of file + [issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"> diff --git a/src/Ombi/ClientApp/app/search/search.component.html b/src/Ombi/ClientApp/app/search/search.component.html index 046635812..7e1d280d2 100644 --- a/src/Ombi/ClientApp/app/search/search.component.html +++ b/src/Ombi/ClientApp/app/search/search.component.html @@ -4,7 +4,7 @@

-

/// [HttpPost("plexuserimporter")] - public bool PlexUserImporter() + public async Task PlexUserImporter() { - BackgroundJob.Enqueue(() => _plexUserImporter.Start()); + await OmbiQuartz.TriggerJob(nameof(IPlexUserImporter), "Plex"); return true; } @@ -105,9 +96,9 @@ public bool PlexUserImporter() ///
/// [HttpPost("embyuserimporter")] - public bool EmbyUserImporter() + public async Task EmbyUserImporter() { - BackgroundJob.Enqueue(() => _embyUserImporter.Start()); + await OmbiQuartz.TriggerJob(nameof(IEmbyUserImporter), "Emby"); return true; } @@ -118,7 +109,7 @@ public bool EmbyUserImporter() [HttpPost("plexcontentcacher")] public bool StartPlexContentCacher() { - BackgroundJob.Enqueue(() => _plexContentSync.CacheContent(false)); + OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IPlexContentSync), "Plex"), new JobDataMap(new Dictionary { { "recentlyAddedSearch", "false" } })); return true; } @@ -129,7 +120,7 @@ public bool StartPlexContentCacher() [HttpPost("plexrecentlyadded")] public bool StartRecentlyAdded() { - BackgroundJob.Enqueue(() => _plexContentSync.CacheContent(true)); + OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IPlexContentSync), "Plex"), new JobDataMap(new Dictionary { { "recentlyAddedSearch", "true" } })); return true; } @@ -138,9 +129,9 @@ public bool StartRecentlyAdded() ///
/// [HttpPost("embycontentcacher")] - public bool StartEmbyContentCacher() + public async Task StartEmbyContentCacher() { - BackgroundJob.Enqueue(() => _embyContentSync.Start()); + await OmbiQuartz.TriggerJob(nameof(IEmbyContentSync), "Emby"); return true; } @@ -149,9 +140,9 @@ public bool StartEmbyContentCacher() ///
/// [HttpPost("newsletter")] - public bool StartNewsletter() + public async Task StartNewsletter() { - BackgroundJob.Enqueue(() => _newsletterJob.Start()); + await OmbiQuartz.TriggerJob(nameof(INewsletterJob), "System"); return true; } } diff --git a/src/Ombi/Controllers/SearchController.cs b/src/Ombi/Controllers/SearchController.cs index bdeacee2a..d5b5e3098 100644 --- a/src/Ombi/Controllers/SearchController.cs +++ b/src/Ombi/Controllers/SearchController.cs @@ -369,6 +369,19 @@ public async Task> SearchAlbum(string searchTe return await MusicEngine.SearchAlbum(searchTerm); } + /// + /// Returns the album information specified by the foreignAlbumId passed in + /// + /// We use Lidarr as the Provider + /// + [HttpGet("music/album/info/{foreignAlbumId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task GetAlbumInformation(string foreignAlbumId) + { + return await MusicEngine.GetAlbumInformation(foreignAlbumId); + } + /// /// Returns all albums for the artist using the ForeignArtistId /// diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index b5ec57e29..ebc2fbe66 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -27,6 +27,8 @@ using Ombi.Store.Repository; using Ombi.Api.Github; using Ombi.Core.Engine; +using Ombi.Schedule; +using Quartz; namespace Ombi.Controllers { @@ -44,7 +46,6 @@ public SettingsController(ISettingsResolver resolver, IMapper mapper, INotificationTemplatesRepository templateRepo, IEmbyApi embyApi, - IRadarrSync radarrSync, ICacheService memCache, IGithubApi githubApi, IRecentlyAddedEngine engine) @@ -53,7 +54,6 @@ public SettingsController(ISettingsResolver resolver, Mapper = mapper; TemplateRepository = templateRepo; _embyApi = embyApi; - _radarrSync = radarrSync; _cache = memCache; _githubApi = githubApi; _recentlyAdded = engine; @@ -63,7 +63,6 @@ public SettingsController(ISettingsResolver resolver, private IMapper Mapper { get; } private INotificationTemplatesRepository TemplateRepository { get; } private readonly IEmbyApi _embyApi; - private readonly IRadarrSync _radarrSync; private readonly ICacheService _cache; private readonly IGithubApi _githubApi; private readonly IRecentlyAddedEngine _recentlyAdded; @@ -387,7 +386,8 @@ public async Task RadarrSettings([FromBody]RadarrSettings settings) { _cache.Remove(CacheKeys.RadarrRootProfiles); _cache.Remove(CacheKeys.RadarrQualityProfiles); - BackgroundJob.Enqueue(() => _radarrSync.CacheContent()); + + await OmbiQuartz.TriggerJob(nameof(IRadarrSync), "DVR"); } return result; } @@ -547,8 +547,8 @@ public async Task JobSettings([FromBody]JobSettings settin try { - var r = CrontabSchedule.TryParse(expression); - if (r == null) + var isValid = CronExpression.IsValidExpression(expression); + if (!isValid) { return new JobSettingsViewModel { @@ -578,14 +578,15 @@ 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 isValid = CronExpression.IsValidExpression(body.Expression); + if (!isValid) { - var next = result.GetNextOccurrence(time); - model.Schedule.Add(next); - time = next; + return new CronTestModel + { + Message = $"CRON Expression {body.Expression} is not valid" + }; } + model.Success = true; return model; } @@ -596,8 +597,6 @@ public CronTestModel TestCron([FromBody] CronViewModelBody body) Message = $"CRON Expression {body.Expression} is not valid" }; } - - } @@ -947,6 +946,40 @@ public async Task MobileNotificationSettings() return model; } + /// + /// Saves the gotify notification settings. + /// + /// The model. + /// + [HttpPost("notifications/gotify")] + public async Task GotifyNotificationSettings([FromBody] GotifyNotificationViewModel model) + { + // Save the email settings + var settings = Mapper.Map(model); + var result = await Save(settings); + + // Save the templates + await TemplateRepository.UpdateRange(model.NotificationTemplates); + + return result; + } + + /// + /// Gets the gotify Notification Settings. + /// + /// + [HttpGet("notifications/gotify")] + public async Task GotifyNotificationSettings() + { + var settings = await Get(); + var model = Mapper.Map(settings); + + // Lookup to see if we have any templates saved + model.NotificationTemplates = BuildTemplates(NotificationAgent.Gotify); + + return model; + } + /// /// Saves the Newsletter notification settings. /// diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 44ec5686f..9f9747f0e 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -123,9 +123,6 @@ private async Task CreateToken(bool rememberMe, OmbiUser user) return new UnauthorizedResult(); } - user.LastLoggedIn = DateTime.UtcNow; - await _userManager.UpdateAsync(user); - var claims = new List { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), @@ -152,6 +149,9 @@ private async Task CreateToken(bool rememberMe, OmbiUser user) //await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); } + user.LastLoggedIn = DateTime.UtcNow; + //await GlobalMutex.Lock(async () => await _userManager.UpdateAsync(user)).ConfigureAwait(false); + return new JsonResult(new { access_token = accessToken, diff --git a/src/Ombi/Models/CronTestModel.cs b/src/Ombi/Models/CronTestModel.cs index 9698afbff..d8193aef5 100644 --- a/src/Ombi/Models/CronTestModel.cs +++ b/src/Ombi/Models/CronTestModel.cs @@ -7,6 +7,5 @@ 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/Ombi.csproj b/src/Ombi/Ombi.csproj index dc447dd20..02e11c84b 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -65,7 +65,7 @@ - + diff --git a/src/Ombi/Program.cs b/src/Ombi/Program.cs index acc904875..dd0baa8d4 100644 --- a/src/Ombi/Program.cs +++ b/src/Ombi/Program.cs @@ -49,6 +49,7 @@ public static void Main(string[] args) demoInstance.Demo = demo; instance.StoragePath = storagePath ?? string.Empty; // Check if we need to migrate the settings + DeleteSchedules(); CheckAndMigrate(); var ctx = new SettingsContext(); var config = ctx.ApplicationConfigurations.ToList(); @@ -61,15 +62,25 @@ public static void Main(string[] args) Type = ConfigurationTypes.Url, Value = "http://*:5000" }; + using (var tran = ctx.Database.BeginTransaction()) + { + ctx.ApplicationConfigurations.Add(url); + ctx.SaveChanges(); + tran.Commit(); + } - ctx.ApplicationConfigurations.Add(url); - ctx.SaveChanges(); urlValue = url.Value; } if (!url.Value.Equals(host)) { url.Value = UrlArgs; - ctx.SaveChanges(); + + using (var tran = ctx.Database.BeginTransaction()) + { + ctx.SaveChanges(); + tran.Commit(); + } + urlValue = url.Value; } @@ -82,23 +93,45 @@ public static void Main(string[] args) Type = ConfigurationTypes.BaseUrl, Value = baseUrl }; - ctx.ApplicationConfigurations.Add(dbBaseUrl); - ctx.SaveChanges(); + + using (var tran = ctx.Database.BeginTransaction()) + { + ctx.ApplicationConfigurations.Add(dbBaseUrl); + ctx.SaveChanges(); + tran.Commit(); + } } } else if (baseUrl.HasValue() && !baseUrl.Equals(dbBaseUrl.Value)) { dbBaseUrl.Value = baseUrl; - ctx.SaveChanges(); - } - DeleteSchedulesDb(); + using (var tran = ctx.Database.BeginTransaction()) + { + ctx.SaveChanges(); + tran.Commit(); + } + } Console.WriteLine($"We are running on {urlValue}"); CreateWebHostBuilder(args).Build().Run(); } + private static void DeleteSchedules() + { + try + { + if (File.Exists("Schedules.db")) + { + File.Delete("Schedules.db"); + } + } + catch (Exception) + { + } + } + /// /// This is to remove the Settings from the Ombi.db to the "new" /// OmbiSettings.db @@ -117,25 +150,30 @@ private static void CheckAndMigrate() try { - if (ombi.Settings.Any()) + + using (var tran = settings.Database.BeginTransaction()) { - // OK migrate it! - var allSettings = ombi.Settings.ToList(); - settings.Settings.AddRange(allSettings); - doneGlobal = true; - } + if (ombi.Settings.Any() && !settings.Settings.Any()) + { + // OK migrate it! + var allSettings = ombi.Settings.ToList(); + settings.Settings.AddRange(allSettings); + doneGlobal = true; + } - // Check for any application settings + // Check for any application settings - if (ombi.ApplicationConfigurations.Any()) - { - // OK migrate it! - var allSettings = ombi.ApplicationConfigurations.ToList(); - settings.ApplicationConfigurations.AddRange(allSettings); - doneConfig = true; - } + if (ombi.ApplicationConfigurations.Any() && !settings.ApplicationConfigurations.Any()) + { + // OK migrate it! + var allSettings = ombi.ApplicationConfigurations.ToList(); + settings.ApplicationConfigurations.AddRange(allSettings); + doneConfig = true; + } - settings.SaveChanges(); + settings.SaveChanges(); + tran.Commit(); + } } catch (Exception e) { @@ -143,81 +181,100 @@ private static void CheckAndMigrate() throw; } - // Now delete the old stuff - if (doneGlobal) - ombi.Database.ExecuteSqlCommand("DELETE FROM GlobalSettings"); - if (doneConfig) - ombi.Database.ExecuteSqlCommand("DELETE FROM ApplicationConfiguration"); + + using (var tran = ombi.Database.BeginTransaction()) + { + // Now delete the old stuff + if (doneGlobal) + ombi.Database.ExecuteSqlCommand("DELETE FROM GlobalSettings"); + if (doneConfig) + ombi.Database.ExecuteSqlCommand("DELETE FROM ApplicationConfiguration"); + tran.Commit(); + } // Now migrate all the external stuff var external = new ExternalContext(); try { - if (ombi.PlexEpisode.Any()) - { - external.PlexEpisode.AddRange(ombi.PlexEpisode.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM PlexEpisode"); - } - if (ombi.PlexSeasonsContent.Any()) - { - external.PlexSeasonsContent.AddRange(ombi.PlexSeasonsContent.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM PlexSeasonsContent"); - } - if (ombi.PlexServerContent.Any()) + using (var tran = external.Database.BeginTransaction()) { - external.PlexServerContent.AddRange(ombi.PlexServerContent.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM PlexServerContent"); - } - if (ombi.EmbyEpisode.Any()) - { - external.EmbyEpisode.AddRange(ombi.EmbyEpisode.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyEpisode"); - } + if (ombi.PlexEpisode.Any()) + { + external.PlexEpisode.AddRange(ombi.PlexEpisode.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM PlexEpisode"); + } - if (ombi.EmbyContent.Any()) - { - external.EmbyContent.AddRange(ombi.EmbyContent.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyContent"); - } - if (ombi.RadarrCache.Any()) - { - external.RadarrCache.AddRange(ombi.RadarrCache.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM RadarrCache"); - } - if (ombi.SonarrCache.Any()) - { - external.SonarrCache.AddRange(ombi.SonarrCache.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM SonarrCache"); - } - if (ombi.LidarrAlbumCache.Any()) - { - external.LidarrAlbumCache.AddRange(ombi.LidarrAlbumCache.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrAlbumCache"); - } - if (ombi.LidarrArtistCache.Any()) - { - external.LidarrArtistCache.AddRange(ombi.LidarrArtistCache.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrArtistCache"); - } - if (ombi.SickRageEpisodeCache.Any()) - { - external.SickRageEpisodeCache.AddRange(ombi.SickRageEpisodeCache.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageEpisodeCache"); - } - if (ombi.SickRageCache.Any()) - { - external.SickRageCache.AddRange(ombi.SickRageCache.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageCache"); - } - if (ombi.CouchPotatoCache.Any()) - { - external.CouchPotatoCache.AddRange(ombi.CouchPotatoCache.ToList()); - ombi.Database.ExecuteSqlCommand("DELETE FROM CouchPotatoCache"); - } + if (ombi.PlexSeasonsContent.Any()) + { + external.PlexSeasonsContent.AddRange(ombi.PlexSeasonsContent.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM PlexSeasonsContent"); + } + + if (ombi.PlexServerContent.Any()) + { + external.PlexServerContent.AddRange(ombi.PlexServerContent.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM PlexServerContent"); + } + + if (ombi.EmbyEpisode.Any()) + { + external.EmbyEpisode.AddRange(ombi.EmbyEpisode.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyEpisode"); + } + + if (ombi.EmbyContent.Any()) + { + external.EmbyContent.AddRange(ombi.EmbyContent.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyContent"); + } + + if (ombi.RadarrCache.Any()) + { + external.RadarrCache.AddRange(ombi.RadarrCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM RadarrCache"); + } + + if (ombi.SonarrCache.Any()) + { + external.SonarrCache.AddRange(ombi.SonarrCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM SonarrCache"); + } + + if (ombi.LidarrAlbumCache.Any()) + { + external.LidarrAlbumCache.AddRange(ombi.LidarrAlbumCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrAlbumCache"); + } + + if (ombi.LidarrArtistCache.Any()) + { + external.LidarrArtistCache.AddRange(ombi.LidarrArtistCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrArtistCache"); + } + + if (ombi.SickRageEpisodeCache.Any()) + { + external.SickRageEpisodeCache.AddRange(ombi.SickRageEpisodeCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageEpisodeCache"); + } + + if (ombi.SickRageCache.Any()) + { + external.SickRageCache.AddRange(ombi.SickRageCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageCache"); + } + + if (ombi.CouchPotatoCache.Any()) + { + external.CouchPotatoCache.AddRange(ombi.CouchPotatoCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM CouchPotatoCache"); + } - external.SaveChanges(); + external.SaveChanges(); + tran.Commit(); + } } catch (Exception e) { @@ -226,20 +283,6 @@ private static void CheckAndMigrate() } } - private static void DeleteSchedulesDb() - { - try - { - if (File.Exists("Schedules.db")) - { - File.Delete("Schedules.db"); - } - } - catch (Exception) - { - } - } - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs old mode 100644 new mode 100755 index 507a9ef83..e0ef4767b --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -57,9 +57,6 @@ public Startup(IHostingEnvironment env) // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { - // Add framework services. - services.AddDbContext(); - services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders() @@ -126,6 +123,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); + app.UseQuartz().GetAwaiter().GetResult(); + var ctx = serviceProvider.GetService(); loggerFactory.AddSerilog(); @@ -193,8 +192,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3 }); // Setup the scheduler - var jobSetup = app.ApplicationServices.GetService(); - jobSetup.Setup(); + //var jobSetup = app.ApplicationServices.GetService(); + //jobSetup.Setup(); ctx.Seed(); var settingsctx = serviceProvider.GetService(); var externalctx = serviceProvider.GetService(); @@ -217,7 +216,14 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF app.UseSwagger(); app.UseSwaggerUI(c => { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + if (settings.BaseUrl.HasValue()) + { + c.SwaggerEndpoint($"{settings.BaseUrl}/swagger/v1/swagger.json", "My API V1"); + } + else + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + } }); app.UseMvc(routes => diff --git a/src/Ombi/appsettings.json b/src/Ombi/appsettings.json index 8a9e38008..7d352f0dc 100644 --- a/src/Ombi/appsettings.json +++ b/src/Ombi/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Information", + "Default": "Debug", "System": "Information", "Microsoft": "None", "Hangfire": "None" diff --git a/src/Ombi/package-lock.json b/src/Ombi/package-lock.json index 4ad391ab8..fff834922 100644 --- a/src/Ombi/package-lock.json +++ b/src/Ombi/package-lock.json @@ -1766,9 +1766,9 @@ } }, "bootstrap": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.0.tgz", - "integrity": "sha512-F1yTDO9OHKH0xjl03DsOe8Nu1OWBIeAugGMhy3UTIYDdbbIPesQIhCEbj+HEr6wqlwweGAlP8F3OBC6kEuhFuw==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", + "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==" }, "bootswatch": { "version": "3.4.0", @@ -4190,8 +4190,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -4209,13 +4208,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4228,18 +4225,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -4342,8 +4336,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -4353,7 +4346,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4366,20 +4358,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4396,7 +4385,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -4469,8 +4457,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -4480,7 +4467,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4556,8 +4542,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -4587,7 +4572,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4605,7 +4589,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4644,13 +4627,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, diff --git a/src/Ombi/wwwroot/translations/bg.json b/src/Ombi/wwwroot/translations/bg.json new file mode 100644 index 000000000..7fabf59d9 --- /dev/null +++ b/src/Ombi/wwwroot/translations/bg.json @@ -0,0 +1,186 @@ +{ + "Login": { + "SignInButton": "Sign in", + "UsernamePlaceholder": "Username", + "PasswordPlaceholder": "Password", + "RememberMe": "Remember Me", + "ForgottenPassword": "Forgot your password?", + "Errors": { + "IncorrectCredentials": "Incorrect username or password" + } + }, + "Common": { + "ContinueButton": "Continue", + "Available": "Available", + "PartiallyAvailable": "Partially Available", + "Monitored": "Monitored", + "NotAvailable": "Not Available", + "ProcessingRequest": "Processing Request", + "PendingApproval": "Pending Approval", + "RequestDenied": "Request Denied", + "NotRequested": "Not Requested", + "Requested": "Requested", + "Request": "Request", + "Denied": "Denied", + "Approve": "Approve", + "PartlyAvailable": "Partly Available", + "Errors": { + "Validation": "Please check your entered values" + } + }, + "PasswordReset": { + "EmailAddressPlaceholder": "Email Address", + "ResetPasswordButton": "Reset Password" + }, + "LandingPage": { + "OnlineHeading": "Currently Online", + "OnlineParagraph": "The media server is currently online", + "PartiallyOnlineHeading": "Partially Online", + "PartiallyOnlineParagraph": "The media server is partially online.", + "MultipleServersUnavailable": "There are {{serversUnavailable}} servers offline out of {{totalServers}}.", + "SingleServerUnavailable": "There is {{serversUnavailable}} server offline out of {{totalServers}}.", + "OfflineHeading": "Currently Offline", + "OfflineParagraph": "The media server is currently offline.", + "CheckPageForUpdates": "Check this page for continuous site updates." + }, + "NavigationBar": { + "Search": "Search", + "Requests": "Requests", + "UserManagement": "User Management", + "Issues": "Issues", + "Vote": "Vote", + "Donate": "Donate!", + "DonateLibraryMaintainer": "Donate to Library Maintainer", + "DonateTooltip": "This is how I convince my wife to let me spend my spare time developing Ombi ;)", + "UpdateAvailableTooltip": "Update Available!", + "Settings": "Settings", + "Welcome": "Welcome {{username}}", + "UpdateDetails": "Update Details", + "Logout": "Logout", + "OpenMobileApp": "Open Mobile App", + "RecentlyAdded": "Recently Added" + }, + "Search": { + "Title": "Search", + "Paragraph": "Want to watch something that is not currently available? No problem, just search for it below and request it!", + "MoviesTab": "Movies", + "TvTab": "TV Shows", + "MusicTab": "Music", + "Suggestions": "Suggestions", + "NoResults": "Sorry, we didn't find any results!", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease": "Theatrical Release: {{date}}", + "ViewOnPlex": "View On Plex", + "ViewOnEmby": "View On Emby", + "RequestAdded": "Request for {{title}} has been added successfully", + "Similar": "Similar", + "Refine": "Refine", + "SearchBarPlaceholder": "Type Here to Search", + "Movies": { + "PopularMovies": "Popular Movies", + "UpcomingMovies": "Upcoming Movies", + "TopRatedMovies": "Top Rated Movies", + "NowPlayingMovies": "Now Playing Movies", + "HomePage": "Home Page", + "Trailer": "Trailer" + }, + "TvShows": { + "Popular": "Popular", + "Trending": "Trending", + "MostWatched": "Most Watched", + "MostAnticipated": "Most Anticipated", + "Results": "Results", + "AirDate": "Air Date:", + "AllSeasons": "All Seasons", + "FirstSeason": "First Season", + "LatestSeason": "Latest Season", + "Select": "Select ...", + "SubmitRequest": "Submit Request", + "Season": "Season: {{seasonNumber}}", + "SelectAllInSeason": "Select All in Season {{seasonNumber}}" + } + }, + "Requests": { + "Title": "Requests", + "Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.", + "MoviesTab": "Movies", + "TvTab": "TV Shows", + "MusicTab": "Music", + "RequestedBy": "Requested By:", + "Status": "Status:", + "RequestStatus": "Request status:", + "Denied": " Denied:", + "TheatricalRelease": "Theatrical Release: {{date}}", + "ReleaseDate": "Released: {{date}}", + "TheatricalReleaseSort": "Theatrical Release", + "DigitalRelease": "Digital Release: {{date}}", + "RequestDate": "Request Date:", + "QualityOverride": "Quality Override:", + "RootFolderOverride": "Root Folder Override:", + "ChangeRootFolder": "Root Folder", + "ChangeQualityProfile": "Quality Profile", + "MarkUnavailable": "Mark Unavailable", + "MarkAvailable": "Mark Available", + "Remove": "Remove", + "Deny": "Deny", + "Season": "Season:", + "GridTitle": "Title", + "AirDate": "AirDate", + "GridStatus": "Status", + "ReportIssue": "Report Issue", + "Filter": "Filter", + "Sort": "Sort", + "SeasonNumberHeading": "Season: {seasonNumber}", + "SortTitleAsc": "Title ▲", + "SortTitleDesc": "Title ▼", + "SortRequestDateAsc": "Request Date ▲", + "SortRequestDateDesc": "Request Date ▼", + "SortStatusAsc": "Status ▲", + "SortStatusDesc": "Status ▼", + "Remaining": { + "Quota": "{{remaining}}/{{total}} requests remaining", + "NextDays": "Another request will be added in {{time}} days", + "NextHours": "Another request will be added in {{time}} hours", + "NextMinutes": "Another request will be added in {{time}} minutes", + "NextMinute": "Another request will be added in {{time}} minute" + } + }, + "Issues": { + "Title": "Issues", + "PendingTitle": "Pending Issues", + "InProgressTitle": "In Progress Issues", + "ResolvedTitle": "Resolved Issues", + "ColumnTitle": "Title", + "Category": "Category", + "Status": "Status", + "Details": "Details", + "Description": "Description", + "NoComments": "No Comments!", + "MarkInProgress": "Mark In Progress", + "MarkResolved": "Mark Resolved", + "SendMessageButton": "Send", + "Subject": "Subject", + "Comments": "Comments", + "WriteMessagePlaceholder": "Write your message here...", + "ReportedBy": "Reported By" + }, + "Filter": { + "ClearFilter": "Clear Filter", + "FilterHeaderAvailability": "Availability", + "FilterHeaderRequestStatus": "Status", + "Approved": "Approved", + "PendingApproval": "Pending Approval" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "MusicRemaining": "Music: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}", + "MusicDue": "Music: {{date}}" + }, + "Votes": { + "CompletedVotesTab": "Voted", + "VotesTab": "Votes Needed" + } +} diff --git a/src/Ombi/wwwroot/translations/da.json b/src/Ombi/wwwroot/translations/da.json index af63d607e..a37e925fa 100644 --- a/src/Ombi/wwwroot/translations/da.json +++ b/src/Ombi/wwwroot/translations/da.json @@ -75,6 +75,7 @@ "RequestAdded": "{{title}} er anmodet med succes", "Similar": "Lignende", "Refine": "Refine", + "SearchBarPlaceholder": "Type Here to Search", "Movies": { "PopularMovies": "Populære film", "UpcomingMovies": "Kommende film", diff --git a/src/Ombi/wwwroot/translations/de.json b/src/Ombi/wwwroot/translations/de.json index 592d69f9c..55356306f 100644 --- a/src/Ombi/wwwroot/translations/de.json +++ b/src/Ombi/wwwroot/translations/de.json @@ -13,7 +13,7 @@ "ContinueButton": "Weiter", "Available": "Verfügbar", "PartiallyAvailable": "Teilweise verfügbar", - "Monitored": "Monitored", + "Monitored": "Überwacht", "NotAvailable": "Nicht verfügbar", "ProcessingRequest": "Anfrage wird bearbeitet", "PendingApproval": "Genehmigung ausstehend", @@ -68,13 +68,14 @@ "MusicTab": "Musik", "Suggestions": "Vorschläge", "NoResults": "Es tut uns leid, wir haben keine Ergebnisse gefunden!", - "DigitalDate": "Digital Release: {{date}}", + "DigitalDate": "Veröffentlichung der digitalen Version: {{date}}", "TheatricalRelease": "Kinostart: {{date}}", "ViewOnPlex": "In Plex anschauen", "ViewOnEmby": "In Emby anschauen", "RequestAdded": "Anfrage für {{title}} wurde erfolgreich hinzugefügt", "Similar": "Ähnliche", - "Refine": "Refine", + "Refine": "Auswahl verfeinern", + "SearchBarPlaceholder": "Suchwort eingeben", "Movies": { "PopularMovies": "Beliebte Filme", "UpcomingMovies": "Kommende Filme", @@ -112,7 +113,7 @@ "TheatricalRelease": "Kinostart: {{date}}", "ReleaseDate": "Veröffentlicht: {{date}}", "TheatricalReleaseSort": "Kinostart", - "DigitalRelease": "Digital Release: {{date}}", + "DigitalRelease": "Veröffentlichung der digitalen Version: {{date}}", "RequestDate": "Datum der Anfrage:", "QualityOverride": "Qualitäts Überschreiben:", "RootFolderOverride": "Stammverzeichnis Überschreiben:", @@ -132,16 +133,16 @@ "SeasonNumberHeading": "Staffel: {seasonNumber}", "SortTitleAsc": "Titel ▲", "SortTitleDesc": "Titel ▼", - "SortRequestDateAsc": "Request Date ▲", - "SortRequestDateDesc": "Request Date ▼", + "SortRequestDateAsc": "Datum der Anfrage ▲", + "SortRequestDateDesc": "Datum der Anfrage ▼", "SortStatusAsc": "Status ▲", "SortStatusDesc": "Status ▼", "Remaining": { "Quota": "{{remaining}}/{{total}} Anfragen verbleiben", - "NextDays": "Another request will be added in {{time}} days", - "NextHours": "Another request will be added in {{time}} hours", - "NextMinutes": "Another request will be added in {{time}} minutes", - "NextMinute": "Another request will be added in {{time}} minute" + "NextDays": "Eine weitere Anfrage wird in {{time}} Tagen hinzugefügt", + "NextHours": "Eine weitere Anfrage wird in {{time}} Stunden hinzugefügt", + "NextMinutes": "Eine weitere Anfrage wird in {{time}} Minuten hinzugefügt", + "NextMinute": "Eine weitere Anfrage wird in {{time}} Minute hinzugefügt" } }, "Issues": { diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 83c587e7b..eabce171d 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -79,6 +79,7 @@ "RequestAdded": "Request for {{title}} has been added successfully", "Similar":"Similar", "Refine":"Refine", + "SearchBarPlaceholder":"Type Here to Search", "Movies": { "PopularMovies": "Popular Movies", "UpcomingMovies": "Upcoming Movies", diff --git a/src/Ombi/wwwroot/translations/es.json b/src/Ombi/wwwroot/translations/es.json index d510600ad..61087033d 100644 --- a/src/Ombi/wwwroot/translations/es.json +++ b/src/Ombi/wwwroot/translations/es.json @@ -12,69 +12,70 @@ "Common": { "ContinueButton": "Continuar", "Available": "Disponible", - "PartiallyAvailable": "Disponible en parte", + "PartiallyAvailable": "Disponible parcialmente", "Monitored": "Monitoreado", - "NotAvailable": "No está disponible", + "NotAvailable": "No disponible", "ProcessingRequest": "Procesando solicitud", "PendingApproval": "Pendiente de aprobación", "RequestDenied": "Solicitud denegada", - "NotRequested": "No solicitados", + "NotRequested": "No solicitado", "Requested": "Solicitado", "Request": "Solicitar", "Denied": "Denegado", "Approve": "Aprobar", - "PartlyAvailable": "Disponible en parte", + "PartlyAvailable": "Disponible parcialmente", "Errors": { "Validation": "Por favor, comprueba los datos introducidos" } }, "PasswordReset": { "EmailAddressPlaceholder": "Correo electrónico", - "ResetPasswordButton": "Restablecer Contraseña" + "ResetPasswordButton": "Restablecer contraseña" }, "LandingPage": { "OnlineHeading": "En línea", - "OnlineParagraph": "El servidor de medios esta en línea", + "OnlineParagraph": "El servidor de medios está en línea", "PartiallyOnlineHeading": "Parcialmente en línea", "PartiallyOnlineParagraph": "El servidor de medios está parcialmente en línea.", "MultipleServersUnavailable": "Hay {{serversUnavailable}} de {{totalServers}} servidores fuera de línea.", "SingleServerUnavailable": "Hay {{serversUnavailable}} de {{totalServers}} servidores fuera de línea.", "OfflineHeading": "Fuera de línea", "OfflineParagraph": "El servidor de medios está fuera de línea.", - "CheckPageForUpdates": "Consulte esta página para ver las últimas novedades." + "CheckPageForUpdates": "Consulta esta página para ver las últimas novedades." }, "NavigationBar": { "Search": "Buscar", "Requests": "Solicitudes", "UserManagement": "Gestión de usuarios", - "Issues": "Incidencias", + "Issues": "Problemas", "Vote": "Votar", "Donate": "¡Donar!", - "DonateLibraryMaintainer": "Donar al mantenedor de la biblioteca", - "DonateTooltip": "Así es como convenzo a mi esposa para que me deje pasar mi tiempo libre desarrollando ombi;)", + "DonateLibraryMaintainer": "Donar al desarrollador de la biblioteca", + "DonateTooltip": "Así es como convenzo a mi esposa para que me deje pasar mi tiempo libre desarrollando Ombi ;)", "UpdateAvailableTooltip": "¡Actualización disponible!", "Settings": "Ajustes", "Welcome": "Bienvenido {{username}}", "UpdateDetails": "Editar cuenta usuario", "Logout": "Cerrar sesión", "OpenMobileApp": "Abrir aplicación móvil", - "RecentlyAdded": "Agregado recientemente" + "RecentlyAdded": "Añadido recientemente" }, "Search": { "Title": "Buscar", - "Paragraph": "¿Quieres ver algo que no está disponible? No hay problema, búscalo y solicítalo!", + "Paragraph": "¿Quieres ver algo que no está disponible? ¡No hay problema, búscalo y solicítalo!", "MoviesTab": "Películas", "TvTab": "Series", "MusicTab": "Música", "Suggestions": "Sugerencias", "NoResults": "¡Lo sentimos, no encontramos ningún resultado!", - "DigitalDate": "Versión Digital: {{date}}", - "TheatricalRelease": "Estreno teatral: {{date}}", + "DigitalDate": "Versión digital: {{date}}", + "TheatricalRelease": "En cines: {{date}}", "ViewOnPlex": "Ver en Plex", "ViewOnEmby": "Ver en Emby", - "RequestAdded": "La solicitud de {{title}} se ha agregado con éxito", + "RequestAdded": "La solicitud de {{title}} se ha añadido correctamente", "Similar": "Similar", "Refine": "Filtros", + "SearchBarPlaceholder": "Escribe aquí para buscar", "Movies": { "PopularMovies": "Películas populares", "UpcomingMovies": "Próximas películas", @@ -87,14 +88,14 @@ "Popular": "Popular", "Trending": "Tendencias", "MostWatched": "Más visto", - "MostAnticipated": "Más Anticipado", + "MostAnticipated": "Lo más esperado", "Results": "Resultados", "AirDate": "Fecha de emisión:", "AllSeasons": "Todas las temporadas", "FirstSeason": "Primera temporada", "LatestSeason": "Última temporada", - "Select": "Seleccione...", - "SubmitRequest": "Enviar Solicitud", + "Select": "Selecciona...", + "SubmitRequest": "Enviar solicitud", "Season": "Temporada: {{seasonNumber}}", "SelectAllInSeason": "Seleccionar todo en temporada {{seasonNumber}}" } @@ -109,15 +110,15 @@ "Status": "Estado:", "RequestStatus": "Estado de la solicitud:", "Denied": " Denegado:", - "TheatricalRelease": "Estreno teatral: {{date}}", - "ReleaseDate": "Lanzado el {{date}}", - "TheatricalReleaseSort": "Estreno teatral", - "DigitalRelease": "Presentación Digital: {{date}}", + "TheatricalRelease": "En cines: {{date}}", + "ReleaseDate": "Publicado: {{date}}", + "TheatricalReleaseSort": "En cines", + "DigitalRelease": "Versión digital: {{date}}", "RequestDate": "Fecha de solicitud:", "QualityOverride": "Sobreescribir calidad:", "RootFolderOverride": "Sobreescribir carpeta raíz:", - "ChangeRootFolder": "Cambiar Carpeta Raíz", - "ChangeQualityProfile": "Cambiar Perfil de Calidad", + "ChangeRootFolder": "Carpeta raíz", + "ChangeQualityProfile": "Perfil de calidad", "MarkUnavailable": "Marcar como no disponible", "MarkAvailable": "Marcar como disponible", "Remove": "Eliminar", @@ -126,7 +127,7 @@ "GridTitle": "Título", "AirDate": "Fecha de estreno", "GridStatus": "Estado", - "ReportIssue": "Informar de Problema", + "ReportIssue": "Reportar problema", "Filter": "Filtrar", "Sort": "Ordenar", "SeasonNumberHeading": "Temporada: {seasonNumber}", @@ -138,48 +139,48 @@ "SortStatusDesc": "Estado ▼", "Remaining": { "Quota": "{{remaining}}/{{total}} solicitudes restantes", - "NextDays": "Se añadirá otra solicitud en {{time}} Días", - "NextHours": "Se añadirá otra solicitud en {{time}} Horas", - "NextMinutes": "Se añadirá otra solicitud en {{time}} Minutos", - "NextMinute": "Se añadirá otra solicitud en {{time}} Minuto" + "NextDays": "Se añadirá otra solicitud en {{time}} días", + "NextHours": "Se añadirá otra solicitud en {{time}} horas", + "NextMinutes": "Se añadirá otra solicitud en {{time}} minutos", + "NextMinute": "Se añadirá otra solicitud en {{time}} minuto" } }, "Issues": { - "Title": "Incidencias", - "PendingTitle": "Incidencias pendientes", - "InProgressTitle": "Incidencias en progreso", - "ResolvedTitle": "Incidencias resueltas", + "Title": "Problemas", + "PendingTitle": "Problemas pendientes", + "InProgressTitle": "Problemas en curso", + "ResolvedTitle": "Problemas resueltos", "ColumnTitle": "Título", "Category": "Categoría", "Status": "Estado", "Details": "Detalles", "Description": "Descripción", "NoComments": "¡Sin comentarios!", - "MarkInProgress": "Marcar En Proceso", - "MarkResolved": "Marcar resuelto", + "MarkInProgress": "Marcar en progreso", + "MarkResolved": "Marcar como resuelto", "SendMessageButton": "Enviar", "Subject": "Asunto", "Comments": "Comentarios", "WriteMessagePlaceholder": "Escribe tu mensaje aquí...", - "ReportedBy": "Informado por" + "ReportedBy": "Reportado por" }, "Filter": { - "ClearFilter": "Eliminar Filtro", + "ClearFilter": "Reiniciar filtro", "FilterHeaderAvailability": "Disponibilidad", "FilterHeaderRequestStatus": "Estado", - "Approved": "¡Aprobado!", - "PendingApproval": "Aprobación Pendiente" + "Approved": "Aprobado", + "PendingApproval": "Pendiente de aprobación" }, "UserManagment": { - "TvRemaining": "Música: {{remaining}}/{{total}} Restante", - "MovieRemaining": "Música: {{remaining}}/{{total}} Restante", - "MusicRemaining": "Música: {{remaining}}/{{total}} Restante", + "TvRemaining": "TV: {{remaining}}/{{total}} restantes", + "MovieRemaining": "Películas: {{remaining}}/{{total}} restantes", + "MusicRemaining": "Música: {{remaining}}/{{total}} restantes", "TvDue": "TV: {{date}}", "MovieDue": "Película: {{date}}", "MusicDue": "Música: {{date}}" }, "Votes": { - "CompletedVotesTab": "¡Votado!", + "CompletedVotesTab": "Votado", "VotesTab": "Votos necesarios" } } diff --git a/src/Ombi/wwwroot/translations/fr.json b/src/Ombi/wwwroot/translations/fr.json index c2df9306b..2f98728e5 100644 --- a/src/Ombi/wwwroot/translations/fr.json +++ b/src/Ombi/wwwroot/translations/fr.json @@ -74,7 +74,8 @@ "ViewOnEmby": "Regarder sur Emby", "RequestAdded": "La demande pour {{title}} a été ajoutée avec succès", "Similar": "Similaires", - "Refine": "Refine", + "Refine": "Affiner", + "SearchBarPlaceholder": "Tapez ici pour rechercher", "Movies": { "PopularMovies": "Films populaires", "UpcomingMovies": "Films à venir", diff --git a/src/Ombi/wwwroot/translations/hu.json b/src/Ombi/wwwroot/translations/hu.json new file mode 100644 index 000000000..1414f0ea0 --- /dev/null +++ b/src/Ombi/wwwroot/translations/hu.json @@ -0,0 +1,186 @@ +{ + "Login": { + "SignInButton": "Bejelentkezés", + "UsernamePlaceholder": "Felhasználónév", + "PasswordPlaceholder": "Jelszó", + "RememberMe": "Emlékezz rám", + "ForgottenPassword": "Elfelejtetted a jelszavad?", + "Errors": { + "IncorrectCredentials": "Helytelen felhasználónév vagy jelszó" + } + }, + "Common": { + "ContinueButton": "Tovább", + "Available": "Elérhető", + "PartiallyAvailable": "Részlegesen elérhető", + "Monitored": "Figyelve", + "NotAvailable": "Nem elérhető", + "ProcessingRequest": "Kérés feldolgozása", + "PendingApproval": "Jóváhagyásra vár", + "RequestDenied": "Kérés megtagadva", + "NotRequested": "Nincs kérve", + "Requested": "Kérve", + "Request": "Kérés", + "Denied": "Megtagadva", + "Approve": "Jóváhagyva", + "PartlyAvailable": "Részlegesen elérhető", + "Errors": { + "Validation": "Kérjük, ellenőrizze a beírt értékeket" + } + }, + "PasswordReset": { + "EmailAddressPlaceholder": "E-mail cím", + "ResetPasswordButton": "Jelszó visszaállítása" + }, + "LandingPage": { + "OnlineHeading": "Jelenleg elérhető", + "OnlineParagraph": "A médiaszerver jelenleg elérhető", + "PartiallyOnlineHeading": "Részben elérhető", + "PartiallyOnlineParagraph": "A médiaszerver részben elérhető.", + "MultipleServersUnavailable": "{{serversUnavailable}} szerver nem érhető el ennyiből: {{totalServers}}.", + "SingleServerUnavailable": "{{serversUnavailable}} szerver nem érhető el ennyiből: {{totalServers}}.", + "OfflineHeading": "Jelenleg nem elérhető", + "OfflineParagraph": "A médiaszerver jelenleg nem elérhető.", + "CheckPageForUpdates": "Látogasd meg ezt az oldalt a frissítésekhez." + }, + "NavigationBar": { + "Search": "Keresés", + "Requests": "Kérések", + "UserManagement": "Felhasználók kezelése", + "Issues": "Problémák", + "Vote": "Szavazás", + "Donate": "Adakozás!", + "DonateLibraryMaintainer": "Adakozz a könyvtár fenntartónak", + "DonateTooltip": "Ezzel győzöm meg a feleségem, hogy a szabadidőmben fejleszthessem az Ombi-t ;)", + "UpdateAvailableTooltip": "Frissítés elérhető!", + "Settings": "Beállítások", + "Welcome": "Üdv {{username}}", + "UpdateDetails": "Fiók beállításai", + "Logout": "Kilépés", + "OpenMobileApp": "Mobil app megnyitása", + "RecentlyAdded": "Nemrég hozzáadott" + }, + "Search": { + "Title": "Keresés", + "Paragraph": "Szeretnél nézni valamit ami jelenleg nem elérhető? Semmi gond, csak keress rá lentebb és kérd!", + "MoviesTab": "Filmek", + "TvTab": "Sorozatok", + "MusicTab": "Zene", + "Suggestions": "Javaslatok", + "NoResults": "Sajnáljuk, nem találtunk semmit!", + "DigitalDate": "Digitális kiadás: {{date}}", + "TheatricalRelease": "Mozis kiadás: {{date}}", + "ViewOnPlex": "Megnézés Plexen", + "ViewOnEmby": "Megnézés Emby-n", + "RequestAdded": "Kérés sikeresen leadva erre: {{title}}", + "Similar": "Hasonló", + "Refine": "Finomítás", + "SearchBarPlaceholder": "Type Here to Search", + "Movies": { + "PopularMovies": "Népszerű filmek", + "UpcomingMovies": "Közelgő filmek", + "TopRatedMovies": "Legjobbra értékelt filmek", + "NowPlayingMovies": "Most játszott filmek", + "HomePage": "Főoldal", + "Trailer": "Előzetes" + }, + "TvShows": { + "Popular": "Népszerű", + "Trending": "Felkapott", + "MostWatched": "Legnézettebb", + "MostAnticipated": "Leginkább várt", + "Results": "Eredmények", + "AirDate": "Bemutató:", + "AllSeasons": "Összes Évad", + "FirstSeason": "Első évad", + "LatestSeason": "Utolsó évad", + "Select": "Kiválasztás...", + "SubmitRequest": "Kérés küldése", + "Season": "Évad: {{seasonNumber}}", + "SelectAllInSeason": "Egész {{seasonNumber}}. évad kiválasztása" + } + }, + "Requests": { + "Title": "Kérések", + "Paragraph": "Lentebb láthatod a saját és egyéb kéréseket, valamint a letöltési és jóváhagyási állapotukat.", + "MoviesTab": "Filmek", + "TvTab": "Sorozatok", + "MusicTab": "Zene", + "RequestedBy": "Kérte:", + "Status": "Állapot:", + "RequestStatus": "Kérés állapota:", + "Denied": " Megtagadta:", + "TheatricalRelease": "Mozis kiadás: {{date}}", + "ReleaseDate": "Kiadva: {{date}}", + "TheatricalReleaseSort": "Mozis kiadás", + "DigitalRelease": "Digitális kiadás: {{date}}", + "RequestDate": "Kérés ideje:", + "QualityOverride": "Minőség felülírása:", + "RootFolderOverride": "Gyökér mappa felülírása:", + "ChangeRootFolder": "Gyökér mappa", + "ChangeQualityProfile": "Minőség profil", + "MarkUnavailable": "Megjelölés nem elérhetőnek", + "MarkAvailable": "Megjelölés elérhetőnek", + "Remove": "Törlés", + "Deny": "Elutasítás", + "Season": "Évad:", + "GridTitle": "Cím", + "AirDate": "Bemutató", + "GridStatus": "Állapot", + "ReportIssue": "Probléma jelentése", + "Filter": "Szűrő", + "Sort": "Rendezés", + "SeasonNumberHeading": "Évad: {seasonNumber}", + "SortTitleAsc": "Cím ▲", + "SortTitleDesc": "Cím ▼", + "SortRequestDateAsc": "Kérés ideje ▲", + "SortRequestDateDesc": "Kérés ideje ▼", + "SortStatusAsc": "Állapot ▲", + "SortStatusDesc": "Állapot ▼", + "Remaining": { + "Quota": "{{remaining}}/{{total}} kérés van még", + "NextDays": "Újabb kérés lesz hozzáadva {{time}} nap múlva", + "NextHours": "Újabb kérés lesz hozzáadva {{time}} óra múlva", + "NextMinutes": "Újabb kérés lesz hozzáadva {{time}} perc múlva", + "NextMinute": "Újabb kérés lesz hozzáadva {{time}} perc múlva" + } + }, + "Issues": { + "Title": "Problémák", + "PendingTitle": "Várakozó problémák", + "InProgressTitle": "Folyamatban lévő problémák", + "ResolvedTitle": "Megoldott problémák", + "ColumnTitle": "Cím", + "Category": "Kategória", + "Status": "Állapot", + "Details": "Részletek", + "Description": "Leírás", + "NoComments": "Nincs megjegyzés!", + "MarkInProgress": "Folyamatban lévőre jelölés", + "MarkResolved": "Megjelölés megoldottként", + "SendMessageButton": "Küldés", + "Subject": "Tárgy", + "Comments": "Hozzászólások", + "WriteMessagePlaceholder": "Írd ide az üzeneted...", + "ReportedBy": "Jelentette" + }, + "Filter": { + "ClearFilter": "Szűrő törlése", + "FilterHeaderAvailability": "Elérhetőség", + "FilterHeaderRequestStatus": "Állapot", + "Approved": "Jóváhagyva", + "PendingApproval": "Jóváhagyásra vár" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} maradt", + "MovieRemaining": "Filmek: {{remaining}}/{{total}} maradt", + "MusicRemaining": "Zene: {{remaining}}/{{total}} maradt", + "TvDue": "TV: {{date}}", + "MovieDue": "Film: {{date}}", + "MusicDue": "Zene: {{date}}" + }, + "Votes": { + "CompletedVotesTab": "Szavazott", + "VotesTab": "Szavazat szükséges" + } +} diff --git a/src/Ombi/wwwroot/translations/it.json b/src/Ombi/wwwroot/translations/it.json index f106b1a6b..38fbf4b99 100644 --- a/src/Ombi/wwwroot/translations/it.json +++ b/src/Ombi/wwwroot/translations/it.json @@ -75,6 +75,7 @@ "RequestAdded": "La richiesta per {{title}} è stata aggiunta correttamente", "Similar": "Similar", "Refine": "Refine", + "SearchBarPlaceholder": "Type Here to Search", "Movies": { "PopularMovies": "Film popolari", "UpcomingMovies": "Film in arrivo", diff --git a/src/Ombi/wwwroot/translations/nl.json b/src/Ombi/wwwroot/translations/nl.json index fdf70aad1..7bacfd142 100644 --- a/src/Ombi/wwwroot/translations/nl.json +++ b/src/Ombi/wwwroot/translations/nl.json @@ -13,15 +13,15 @@ "ContinueButton": "Doorgaan", "Available": "Beschikbaar", "PartiallyAvailable": "Deels Beschikbaar", - "Monitored": "Onder toezicht", + "Monitored": "Gemonitord", "NotAvailable": "Niet Beschikbaar", "ProcessingRequest": "Verzoek wordt verwerkt", "PendingApproval": "Wacht op goedkeuring", "RequestDenied": "Verzoek geweigerd", "NotRequested": "Niet verzocht", - "Requested": "Verzocht", - "Request": "Verzoek", - "Denied": "Geweigerd", + "Requested": "Aangevraagd", + "Request": "Aanvragen", + "Denied": "Afgewezen", "Approve": "Accepteer", "PartlyAvailable": "Deels Beschikbaar", "Errors": { @@ -37,27 +37,27 @@ "OnlineParagraph": "De mediaserver is momenteel online", "PartiallyOnlineHeading": "Gedeeltelijk Online", "PartiallyOnlineParagraph": "De mediaserver is gedeeltelijk online.", - "MultipleServersUnavailable": "Er zijn {{serversUnavailable}} servers offline van de in totaal {{totalServers}}.", - "SingleServerUnavailable": "Er is {{serversUnavailable}} server offline uit {{totalServers}}.", - "OfflineHeading": "Op dit moment Offline", + "MultipleServersUnavailable": "Er zijn {{serversUnavailable}} van de {{totalServers}} servers offline.", + "SingleServerUnavailable": "Er is {{serversUnavailable}} van de {{totalServers}} server offline.", + "OfflineHeading": "Momenteel Offline", "OfflineParagraph": "De mediaserver is momenteel offline.", "CheckPageForUpdates": "Controleer deze pagina voor updates." }, "NavigationBar": { "Search": "Zoeken", - "Requests": "Verzoeklijst", - "UserManagement": "Gebruikersbeheer", + "Requests": "Verzoeken", + "UserManagement": "Gebruikersmanagement", "Issues": "Problemen", "Vote": "Stem", "Donate": "Doneer!", - "DonateLibraryMaintainer": "Doneren aan bibliotheek beheerder", + "DonateLibraryMaintainer": "Doneren aan Ombi beheerder", "DonateTooltip": "Zo heb ik mijn vrouw overtuigd dat ik Ombi mag ontwikkelen ;)", "UpdateAvailableTooltip": "Update beschikbaar!", "Settings": "Instellingen", "Welcome": "Welkom {{username}}", - "UpdateDetails": "Update gegevens", + "UpdateDetails": "Gegevens updaten", "Logout": "Afmelden", - "OpenMobileApp": "Open Mobiele App", + "OpenMobileApp": "Mobiele App Openen", "RecentlyAdded": "Onlangs Toegevoegd" }, "Search": { @@ -67,7 +67,7 @@ "TvTab": "TV Series", "MusicTab": "Muziek", "Suggestions": "Suggesties", - "NoResults": "Sorry, we hebben geen resultaten gevonden!", + "NoResults": "Sorry, er zijn geen resultaten gevonden!", "DigitalDate": "Digitale Uitgave: {{date}}", "TheatricalRelease": "Bioscoop Uitgave: {{date}}", "ViewOnPlex": "Bekijk op Plex", @@ -75,11 +75,12 @@ "RequestAdded": "Aanvraag voor {{title}} is succesvol toegevoegd", "Similar": "Vergelijkbaar", "Refine": "Verfijn", + "SearchBarPlaceholder": "Voer zoekterm in", "Movies": { "PopularMovies": "Populaire films", "UpcomingMovies": "Aankomende Films", "TopRatedMovies": "Best Beoordeelde Films", - "NowPlayingMovies": "In Cinema Films", + "NowPlayingMovies": "Nu in de bioscoop", "HomePage": "Startpagina", "Trailer": "Trailer" }, diff --git a/src/Ombi/wwwroot/translations/no.json b/src/Ombi/wwwroot/translations/no.json index 93ed1342e..51d55b2c5 100644 --- a/src/Ombi/wwwroot/translations/no.json +++ b/src/Ombi/wwwroot/translations/no.json @@ -13,7 +13,7 @@ "ContinueButton": "Gå videre", "Available": "Tilgjengelig", "PartiallyAvailable": "Partially Available", - "Monitored": "Monitored", + "Monitored": "Overvåket", "NotAvailable": "Ikke tilgjengelig", "ProcessingRequest": "Behandler forespørsel", "PendingApproval": "Venter på godkjenning", @@ -48,7 +48,7 @@ "Requests": "Forespørsler", "UserManagement": "Brukeradministrasjon", "Issues": "Mangler", - "Vote": "Vote", + "Vote": "Stem", "Donate": "Doner!", "DonateLibraryMaintainer": "Doner til vedlikeholderen av biblioteket", "DonateTooltip": "Dette er hvordan jeg overbevise min kone til å la meg bruke min fritid til å utvikle Ombi ;)", @@ -58,14 +58,14 @@ "UpdateDetails": "Oppdater detaljer", "Logout": "Logg av", "OpenMobileApp": "Åpne mobilapp", - "RecentlyAdded": "Recently Added" + "RecentlyAdded": "Nylig lagt til" }, "Search": { "Title": "Søk", "Paragraph": "Vil du se noe som foreløpig ikke er tilgjengelig? Ikke noe problem, bare søk etter det nedenfor og be om det!", "MoviesTab": "Filmer", "TvTab": "TV serier", - "MusicTab": "Music", + "MusicTab": "Musikk", "Suggestions": "Forslag", "NoResults": "Beklager, vi fant ingen resultater!", "DigitalDate": "Digital utgivelse: {{date}}", @@ -75,6 +75,7 @@ "RequestAdded": "Forespørsel om {{title}} er lagt til", "Similar": "Lignende", "Refine": "Refine", + "SearchBarPlaceholder": "Type Here to Search", "Movies": { "PopularMovies": "Populære filmer", "UpcomingMovies": "Kommende filmer", @@ -104,7 +105,7 @@ "Paragraph": "Nedenfor kan du se dine og alle andres forespørsler, du ser også status for nedlasting og godkjenning.", "MoviesTab": "Filmer", "TvTab": "TV serier", - "MusicTab": "Music", + "MusicTab": "Musikk", "RequestedBy": "Etterspurt av:", "Status": "Status:", "RequestStatus": "Status for forespørsel:", @@ -128,10 +129,10 @@ "GridStatus": "Status", "ReportIssue": "Rapportér en feil", "Filter": "Filter", - "Sort": "Sort", + "Sort": "Sorter", "SeasonNumberHeading": "Sesong: {seasonNumber}", - "SortTitleAsc": "Title ▲", - "SortTitleDesc": "Title ▼", + "SortTitleAsc": "Tittel ▲", + "SortTitleDesc": "Tittel ▼", "SortRequestDateAsc": "Request Date ▲", "SortRequestDateDesc": "Request Date ▼", "SortStatusAsc": "Status ▲", @@ -168,18 +169,18 @@ "FilterHeaderAvailability": "Tilgjengelighet", "FilterHeaderRequestStatus": "Status", "Approved": "Godkjent", - "PendingApproval": "Pending Approval" + "PendingApproval": "Venter på godkjenning" }, "UserManagment": { - "TvRemaining": "TV: {{remaining}}/{{total}} remaining", - "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", - "MusicRemaining": "Music: {{remaining}}/{{total}} remaining", + "TvRemaining": "TV: {{remaining}}/{{total}} gjenstående", + "MovieRemaining": "Filmer: {{remaining}}/{{total}} gjenstående", + "MusicRemaining": "Musikk: {{remaining}}/{{total}} gjenstående", "TvDue": "TV: {{date}}", - "MovieDue": "Movie: {{date}}", - "MusicDue": "Music: {{date}}" + "MovieDue": "Film:{{date}}", + "MusicDue": "Musikk:{{date}}" }, "Votes": { - "CompletedVotesTab": "Voted", + "CompletedVotesTab": "Stemt", "VotesTab": "Votes Needed" } } diff --git a/src/Ombi/wwwroot/translations/pl.json b/src/Ombi/wwwroot/translations/pl.json index ddd3ed24d..f309e6a86 100644 --- a/src/Ombi/wwwroot/translations/pl.json +++ b/src/Ombi/wwwroot/translations/pl.json @@ -10,7 +10,7 @@ } }, "Common": { - "ContinueButton": "Dalej", + "ContinueButton": "Kontynuuj", "Available": "Dostępne", "PartiallyAvailable": "Częściowo dostępne", "Monitored": "Monitorowane", @@ -18,14 +18,14 @@ "ProcessingRequest": "Przetwarzanie zgłoszenia", "PendingApproval": "Oczekujące na zatwierdzenie", "RequestDenied": "Zgłoszenie odrzucone", - "NotRequested": "Nie zgłoszone", + "NotRequested": "Niezgłoszone", "Requested": "Zgłoszone", "Request": "Zgłoszenie", "Denied": "Odrzucone", "Approve": "Zatwierdź", "PartlyAvailable": "Częściowo dostępne", "Errors": { - "Validation": "Sprawdź wprowadzone dane" + "Validation": "Proszę sprawdzić wprowadzone wartości" } }, "PasswordReset": { @@ -33,11 +33,11 @@ "ResetPasswordButton": "Zresetuj hasło" }, "LandingPage": { - "OnlineHeading": "Dostępny", + "OnlineHeading": "Aktualnie online", "OnlineParagraph": "Serwer multimediów jest aktualnie online", "PartiallyOnlineHeading": "Częściowo online", "PartiallyOnlineParagraph": "Serwer multimediów jest częściowo online.", - "MultipleServersUnavailable": "{{serversUnavailable}} serwerów spośród {{totalServers}} jest offline.", + "MultipleServersUnavailable": "{{serversUnavailable}} serwery(ów) spośród {{totalServers}} jest offline.", "SingleServerUnavailable": "{{serversUnavailable}} serwer z {{totalServers}} jest w offline.", "OfflineHeading": "Aktualnie offline", "OfflineParagraph": "Serwer multimediów jest aktualnie offline.", @@ -51,7 +51,7 @@ "Vote": "Głosowania", "Donate": "Wesprzyj!", "DonateLibraryMaintainer": "Wesprzyj właściciela biblioteki", - "DonateTooltip": "W ten sposób przekonuję moją żonę by spędzać mój wolny czas rozwijając Ombi ;)", + "DonateTooltip": "Tak przekonuję moją żonę, aby pozwalała mi w wolnym czasie rozwijać Ombi ;)", "UpdateAvailableTooltip": "Dostępna aktualizacja!", "Settings": "Ustawienia", "Welcome": "Witaj {{username}}", @@ -71,13 +71,14 @@ "DigitalDate": "Wydanie cyfrowe: {{date}}", "TheatricalRelease": "Premiera kinowa: {{date}}", "ViewOnPlex": "Obejrzyj w Plex", - "ViewOnEmby": "Obejrzyj na Emby", - "RequestAdded": "Zgłoszenie dla {{title}} zostało dodane", + "ViewOnEmby": "Obejrzyj w Emby", + "RequestAdded": "Zgłoszenie dla {{title}} zostało pomyślnie dodane", "Similar": "Podobne", "Refine": "Zawęź", + "SearchBarPlaceholder": "Wpisz tutaj, aby wyszukać", "Movies": { "PopularMovies": "Popularne filmy", - "UpcomingMovies": "Wkrótce w kinach", + "UpcomingMovies": "Nadchodzące filmy", "TopRatedMovies": "Najwyżej oceniane filmy", "NowPlayingMovies": "W kinach", "HomePage": "Strona główna", @@ -101,9 +102,9 @@ }, "Requests": { "Title": "Zgłoszenia", - "Paragraph": "Poniżej są twoje i wszystkie inne zgłoszenia, a także ich status akceptacji i pobierania.", + "Paragraph": "Poniżej znajdują się Twoje i wszystkie inne zgłoszenia, a także ich status akceptacji i pobierania.", "MoviesTab": "Filmy", - "TvTab": "Programy TV", + "TvTab": "Seriale", "MusicTab": "Muzyka", "RequestedBy": "Zgłoszone przez:", "Status": "Status:", @@ -117,7 +118,7 @@ "QualityOverride": "Wymuszenie jakości:", "RootFolderOverride": "Wymuszenie folderu głównego:", "ChangeRootFolder": "Folder główny", - "ChangeQualityProfile": "Wybór jakości", + "ChangeQualityProfile": "Profil jakości", "MarkUnavailable": "Oznacz jako niedostępne", "MarkAvailable": "Oznacz jako dostępne", "Remove": "Usuń", @@ -128,7 +129,7 @@ "GridStatus": "Status", "ReportIssue": "Zgłoś problem", "Filter": "Filtr", - "Sort": "Sortowanie", + "Sort": "Sortuj", "SeasonNumberHeading": "Sezon: {seasonNumber}", "SortTitleAsc": "Tytuł ▲", "SortTitleDesc": "Tytuł ▼", @@ -151,7 +152,7 @@ "ResolvedTitle": "Problemy rozwiązane", "ColumnTitle": "Tytuł", "Category": "Kategoria", - "Status": "Stan", + "Status": "Status", "Details": "Szczegóły", "Description": "Opis", "NoComments": "Brak komentarzy!", @@ -164,17 +165,17 @@ "ReportedBy": "Zgłoszone przez" }, "Filter": { - "ClearFilter": "Wyczyść fltr", + "ClearFilter": "Wyczyść filtr", "FilterHeaderAvailability": "Dostępność", "FilterHeaderRequestStatus": "Status", "Approved": "Zatwierdzone", "PendingApproval": "Oczekujące na zatwierdzenie" }, "UserManagment": { - "TvRemaining": "TV: pozostało {{remaining}}/{{total}}", + "TvRemaining": "Seriale: pozostało {{remaining}}/{{total}}", "MovieRemaining": "Filmy: pozostało {{remaining}}/{{total}}", "MusicRemaining": "Muzyka: pozostało {{remaining}}/{{total}}", - "TvDue": "TV: {{date}}", + "TvDue": "Serial: {{date}}", "MovieDue": "Film: {{date}}", "MusicDue": "Muzyka: {{date}}" }, diff --git a/src/Ombi/wwwroot/translations/pt.json b/src/Ombi/wwwroot/translations/pt.json index aecd9903f..b0b3783ef 100644 --- a/src/Ombi/wwwroot/translations/pt.json +++ b/src/Ombi/wwwroot/translations/pt.json @@ -1,12 +1,12 @@ { "Login": { - "SignInButton": "Registrar", + "SignInButton": "Iniciar sessão", "UsernamePlaceholder": "Nome de usuário", - "PasswordPlaceholder": "Senha", - "RememberMe": "Lembre-se de mim", - "ForgottenPassword": "Esqueceu sua senha?", + "PasswordPlaceholder": "Palavra-passe", + "RememberMe": "Guardar a minha autênticação", + "ForgottenPassword": "Esqueceu-se da sua palavra-passe?", "Errors": { - "IncorrectCredentials": "Nome de usuário ou senha incorretos" + "IncorrectCredentials": "Nome de usuário ou palavra-passe incorretos" } }, "Common": { @@ -14,13 +14,13 @@ "Available": "Disponível", "PartiallyAvailable": "Parcialmente Disponível", "Monitored": "Monitorado", - "NotAvailable": "Inisponível", - "ProcessingRequest": "Processando Solicitação", + "NotAvailable": "Não Disponível", + "ProcessingRequest": "A processar o pedido", "PendingApproval": "Aprovação Pendente", - "RequestDenied": "Solicitação Negada", - "NotRequested": "Não Solicitado", - "Requested": "Solicitado", - "Request": "Solicitar", + "RequestDenied": "Pedido negado", + "NotRequested": "Não pedido", + "Requested": "Pedido", + "Request": "Pedir", "Denied": "Negado", "Approve": "Aprovar", "PartlyAvailable": "Parcialmente Disponível", @@ -30,34 +30,34 @@ }, "PasswordReset": { "EmailAddressPlaceholder": "Endereço de e-mail", - "ResetPasswordButton": "Redefinir Senha" + "ResetPasswordButton": "Redefinir palavra-passe" }, "LandingPage": { "OnlineHeading": "Online Agora", - "OnlineParagraph": "O servidor de mídia está atualmente online", + "OnlineParagraph": "O servidor de media está actualmente online", "PartiallyOnlineHeading": "Parcialmente Online", - "PartiallyOnlineParagraph": "O servidor de mídia está parcialmente online.", - "MultipleServersUnavailable": "Existem {{serversUnavailable}} servidores offline em um total de {{totalServers}}.", - "SingleServerUnavailable": "Existe {{serversUnavailable}} servidor offline em um total de {{totalServers}}.", - "OfflineHeading": "Offline Agora", - "OfflineParagraph": "O servidor de mídia está atualmente offline.", - "CheckPageForUpdates": "Verifique esta página para atualizações contínuas do site." + "PartiallyOnlineParagraph": "O servidor de media está parcialmente online.", + "MultipleServersUnavailable": "Existem {{serversUnavailable}} servidores offline de um total de {{totalServers}}.", + "SingleServerUnavailable": "Existe {{serversUnavailable}} servidor offline de um total de {{totalServers}}.", + "OfflineHeading": "Actualmente Offline", + "OfflineParagraph": "O servidor de media está actualmente offline.", + "CheckPageForUpdates": "Verifique esta página para acompanhar as atualizações do site." }, "NavigationBar": { "Search": "Pesquisar", - "Requests": "Solicitações", - "UserManagement": "Gerenciador de Usuário", - "Issues": "Problemas", + "Requests": "Pedidos", + "UserManagement": "Gestor de utilizadores", + "Issues": "Incidentes", "Vote": "Votar", "Donate": "Fazer uma doação!", "DonateLibraryMaintainer": "Doar para o Dono da Biblioteca", - "DonateTooltip": "É assim que eu convenço a minha mulher a deixar-me passar o meu tempo livre desenvolvendo Ombi;)", + "DonateTooltip": "É assim que eu convenço a minha mulher a deixar-me passar o meu tempo livre a desenvolver o Ombi ;)", "UpdateAvailableTooltip": "Atualização Disponível!", "Settings": "Configurações", "Welcome": "Bem-vindo, {{username}}", - "UpdateDetails": "Detalhes da Atualização", + "UpdateDetails": "Detalhes da Actualização", "Logout": "Sair", - "OpenMobileApp": "Abrir aplicativo do celular", + "OpenMobileApp": "Abrir app móvel", "RecentlyAdded": "Recentemente adicionado" }, "Search": { @@ -75,6 +75,7 @@ "RequestAdded": "Pedido de {{title}} foi adicionado com sucesso", "Similar": "Semelhante", "Refine": "Filtro", + "SearchBarPlaceholder": "Digite aqui para pesquisar", "Movies": { "PopularMovies": "Filmes populares", "UpcomingMovies": "Próximos filmes", diff --git a/src/Ombi/wwwroot/translations/ru.json b/src/Ombi/wwwroot/translations/ru.json new file mode 100644 index 000000000..7cf9f1702 --- /dev/null +++ b/src/Ombi/wwwroot/translations/ru.json @@ -0,0 +1,186 @@ +{ + "Login": { + "SignInButton": "Войти", + "UsernamePlaceholder": "Имя пользователя", + "PasswordPlaceholder": "Пароль", + "RememberMe": "Запомнить Меня", + "ForgottenPassword": "Забыли пароль?", + "Errors": { + "IncorrectCredentials": "Неверное имя пользователя или пароль" + } + }, + "Common": { + "ContinueButton": "Продолжить", + "Available": "Доступно", + "PartiallyAvailable": "Частично доступно", + "Monitored": "Мониторинг", + "NotAvailable": "Недоступно", + "ProcessingRequest": "Обработка запроса", + "PendingApproval": "Ожидание утверждения", + "RequestDenied": "Запрос отклонен", + "NotRequested": "Не запрошено", + "Requested": "Запрос отправлен", + "Request": "Запрос", + "Denied": "Запрещено", + "Approve": "Утвердить", + "PartlyAvailable": "Partly Available", + "Errors": { + "Validation": "Please check your entered values" + } + }, + "PasswordReset": { + "EmailAddressPlaceholder": "Email Address", + "ResetPasswordButton": "Reset Password" + }, + "LandingPage": { + "OnlineHeading": "Currently Online", + "OnlineParagraph": "The media server is currently online", + "PartiallyOnlineHeading": "Partially Online", + "PartiallyOnlineParagraph": "The media server is partially online.", + "MultipleServersUnavailable": "There are {{serversUnavailable}} servers offline out of {{totalServers}}.", + "SingleServerUnavailable": "There is {{serversUnavailable}} server offline out of {{totalServers}}.", + "OfflineHeading": "Currently Offline", + "OfflineParagraph": "The media server is currently offline.", + "CheckPageForUpdates": "Check this page for continuous site updates." + }, + "NavigationBar": { + "Search": "Search", + "Requests": "Requests", + "UserManagement": "User Management", + "Issues": "Issues", + "Vote": "Vote", + "Donate": "Donate!", + "DonateLibraryMaintainer": "Donate to Library Maintainer", + "DonateTooltip": "This is how I convince my wife to let me spend my spare time developing Ombi ;)", + "UpdateAvailableTooltip": "Update Available!", + "Settings": "Settings", + "Welcome": "Welcome {{username}}", + "UpdateDetails": "Update Details", + "Logout": "Logout", + "OpenMobileApp": "Open Mobile App", + "RecentlyAdded": "Recently Added" + }, + "Search": { + "Title": "Search", + "Paragraph": "Want to watch something that is not currently available? No problem, just search for it below and request it!", + "MoviesTab": "Movies", + "TvTab": "TV Shows", + "MusicTab": "Music", + "Suggestions": "Suggestions", + "NoResults": "Sorry, we didn't find any results!", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease": "Theatrical Release: {{date}}", + "ViewOnPlex": "View On Plex", + "ViewOnEmby": "View On Emby", + "RequestAdded": "Request for {{title}} has been added successfully", + "Similar": "Similar", + "Refine": "Refine", + "SearchBarPlaceholder": "Type Here to Search", + "Movies": { + "PopularMovies": "Popular Movies", + "UpcomingMovies": "Upcoming Movies", + "TopRatedMovies": "Top Rated Movies", + "NowPlayingMovies": "Now Playing Movies", + "HomePage": "Home Page", + "Trailer": "Trailer" + }, + "TvShows": { + "Popular": "Popular", + "Trending": "Trending", + "MostWatched": "Most Watched", + "MostAnticipated": "Most Anticipated", + "Results": "Results", + "AirDate": "Air Date:", + "AllSeasons": "All Seasons", + "FirstSeason": "First Season", + "LatestSeason": "Latest Season", + "Select": "Select ...", + "SubmitRequest": "Submit Request", + "Season": "Season: {{seasonNumber}}", + "SelectAllInSeason": "Select All in Season {{seasonNumber}}" + } + }, + "Requests": { + "Title": "Requests", + "Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.", + "MoviesTab": "Movies", + "TvTab": "TV Shows", + "MusicTab": "Music", + "RequestedBy": "Requested By:", + "Status": "Status:", + "RequestStatus": "Request status:", + "Denied": " Denied:", + "TheatricalRelease": "Theatrical Release: {{date}}", + "ReleaseDate": "Released: {{date}}", + "TheatricalReleaseSort": "Theatrical Release", + "DigitalRelease": "Digital Release: {{date}}", + "RequestDate": "Request Date:", + "QualityOverride": "Quality Override:", + "RootFolderOverride": "Root Folder Override:", + "ChangeRootFolder": "Root Folder", + "ChangeQualityProfile": "Quality Profile", + "MarkUnavailable": "Mark Unavailable", + "MarkAvailable": "Mark Available", + "Remove": "Remove", + "Deny": "Deny", + "Season": "Season:", + "GridTitle": "Title", + "AirDate": "AirDate", + "GridStatus": "Status", + "ReportIssue": "Report Issue", + "Filter": "Filter", + "Sort": "Sort", + "SeasonNumberHeading": "Season: {seasonNumber}", + "SortTitleAsc": "Title ▲", + "SortTitleDesc": "Title ▼", + "SortRequestDateAsc": "Request Date ▲", + "SortRequestDateDesc": "Request Date ▼", + "SortStatusAsc": "Status ▲", + "SortStatusDesc": "Status ▼", + "Remaining": { + "Quota": "{{remaining}}/{{total}} requests remaining", + "NextDays": "Another request will be added in {{time}} days", + "NextHours": "Another request will be added in {{time}} hours", + "NextMinutes": "Another request will be added in {{time}} minutes", + "NextMinute": "Another request will be added in {{time}} minute" + } + }, + "Issues": { + "Title": "Issues", + "PendingTitle": "Pending Issues", + "InProgressTitle": "In Progress Issues", + "ResolvedTitle": "Resolved Issues", + "ColumnTitle": "Title", + "Category": "Category", + "Status": "Status", + "Details": "Details", + "Description": "Description", + "NoComments": "No Comments!", + "MarkInProgress": "Mark In Progress", + "MarkResolved": "Mark Resolved", + "SendMessageButton": "Send", + "Subject": "Subject", + "Comments": "Comments", + "WriteMessagePlaceholder": "Write your message here...", + "ReportedBy": "Reported By" + }, + "Filter": { + "ClearFilter": "Clear Filter", + "FilterHeaderAvailability": "Availability", + "FilterHeaderRequestStatus": "Status", + "Approved": "Approved", + "PendingApproval": "Pending Approval" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "MusicRemaining": "Music: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}", + "MusicDue": "Music: {{date}}" + }, + "Votes": { + "CompletedVotesTab": "Voted", + "VotesTab": "Votes Needed" + } +} diff --git a/src/Ombi/wwwroot/translations/sv.json b/src/Ombi/wwwroot/translations/sv.json index 78ea25051..766804ba9 100644 --- a/src/Ombi/wwwroot/translations/sv.json +++ b/src/Ombi/wwwroot/translations/sv.json @@ -75,6 +75,7 @@ "RequestAdded": "Begäran av {{title}} har lagts till", "Similar": "Liknande", "Refine": "Förfina", + "SearchBarPlaceholder": "Skriv här för att söka", "Movies": { "PopularMovies": "Populära filmer", "UpcomingMovies": "Kommande filmer", diff --git a/src/Ombi/yarn.lock b/src/Ombi/yarn.lock index 95d3e136b..3f35f5f50 100644 --- a/src/Ombi/yarn.lock +++ b/src/Ombi/yarn.lock @@ -855,9 +855,10 @@ body-parser@1.18.3: raw-body "2.3.3" type-is "~1.6.16" -bootstrap@3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.0.tgz#f8d77540dd3062283d2ae7687e21c1e691961640" +bootstrap@3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72" + integrity sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA== bootswatch@3.4.0: version "3.4.0"