diff --git a/CHANGELOG.md b/CHANGELOG.md index b17a436e7..316404100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,68 @@ ### **New Features** +- Added a similar button to the movie searches. Makes movie discoverablility easier. [tidusjar] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update ISSUE_TEMPLATE.md. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update ISSUE_TEMPLATE.md. [PotatoQuality] + +- Update ISSUE_TEMPLATE.md. [PotatoQuality] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [PotatoQuality] + +- Change the default templates to use {IssueUser} [Jamie] + +- Changed the base url validation. [tidusjar] + +- Added bulk editing (#1941) [Jamie] + +- Change the poster size to w300 #1932. [Jamie] + +- Added a default user agent on all API calls. [tidusjar] + +- Update request.service.ts. [Jamie] + +- Added a filter onto the movies requests page for some inital feedback. [Jamie] + +- Added ordering to the User Management screen. [Jamie] + +- Update README.md. [Jamie] + +- Added custom donation url (#1902) [m4tta] + +- Changed the url scheme to make it easier to parse. [Jamie] + +- Added Norwegian to the translation code, forgot to check this in. [Jamie] + +- Added Norwegian to the language dropdown. [Jamie] + +- Added the stuff needed for omBlur. [tidusjar] + +- Update README.md (#1872) [xnaas] + +- Update README.md. [Jamie] + +- Update plex.component.html. [Jamie] + +- Change plus to list in menu (#1855) [Louis Laureys] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + - Added user request limits, We can now set the limit for a user. [tidusjar] - Updated the UI JWT framework. [Jamie] @@ -284,6 +346,268 @@ ### **Fixes** +- Publish 32bit build of windows. [tidusjar] + +- Fixing incorrect filter translation targets (#1987) [Jono Cairns] + +- New Crowdin translations (#2017) [Jamie] + +- Fixed #1997. [tidusjar] + +- We now show the digital release date in the search if available #1962. [tidusjar] + +- Css fixes (#2014) [Louis Laureys] + +- API improvements. [Jamie] + +- Fix #1599 (#2008) [Louis Laureys] + +- Issue button fix (#2006) [Louis Laureys] + +- Fixed #1886 #1865. [Jamie] + +- Fixed the outstanding issue on #1995. [Jamie] + +- Fixed an issue for #1951. [tidusjar] + +- Try and fuzzy match the title and release if we cannot get the tvdb id or imdbid (depends on the media agents in Plex) #1951. [tidusjar] + +- Fixed #1989 #1719. [Jamie] + +- Small changes that might fix #1985 but doubt it. [Jamie] + +- Should fix #1975. [tidusjar] + +- Fixed #1789. [tidusjar] + +- Fixed #1968. [tidusjar] + +- Fixed #1978. [tidusjar] + +- Fixed #1954. [tidusjar] + +- Small changes to the auto updater, let's see how this works. [Jamie] + +- Fixed build. [Jamie] + +- Fixed the update check for the master build. [Jamie] + +- Removed accidently merged files. [Jamie] + +- Create CODE_OF_CONDUCT.md. [Jamie] + +- Windows installation guide link update. [PotatoQuality] + +- Fixed the issue comment issue #1914 also added another variable for issues {IssueUser} which is the user that reported the issue. [Jamie] + +- Fix #1914. [tidusjar] + +- Fixed #1914. [tidusjar] + +- Fixed build and added logging. [TidusJar] + +- New Crowdin translations (#1934) [Jamie] + +- Potential fix for #1942. [Jamie] + +- Quick change to the Emby Availability rule to make it in line slightly with the Plex one. #1950. [Jamie] + +- Turn off mobile notifications. [tidusjar] + +- FIXED PLEX!!!!! [tidusjar] + +- Batch the PlexContentSync and increase the plex episode batch size. [tidusjar] + +- Fixed the migration issue, it's too difficult to migrate the tables. [tidusjar] + +- Fixed #1942. [tidusjar] + +- Fixed checkboxes style. [Jamie] + +- These are not the droids you are looking for. [Jamie] + +- Fixed the wrong translation and see if we can VACUUM the db. [tidusjar] + +- More translations and added a check on the baseurl to ensure it starts with a '/' [Jamie] + +- More translations. [Jamie] + +- Fixed #1878 and added a Request all button when selecting episodes. [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- Working on the movie matching. Stop dupes #1869. [tidusjar] + +- Delete plex episodes on every run due to a bug, need to spend quite a bit of time on this. [tidusjar] + +- Fixed the issue where we were always adding emby episodes. Also fixed #1933. [tidusjar] + +- New Crowdin translations (#1906) [Jamie] + +- Add plain password for emby login (#1925) [dorian ALKOUM] + +- Fixed #1924. [Jamie] + +- Fixed the issue where I knocked out the ordering of notifications, oops. [tidusjar] + +- #1914 for the issue resolved notification. [Jamie] + +- #1916. [Jamie] + +- Remove the placeholder. [Jamie] + +- Feature arm (#1909) [Jamie] + +- New Crowdin translations (#1897) [Jamie] + +- Fix logo cut off on login screen (#1896) [Louis Laureys] + +- E-Mails: Only add poster table row if img is set (#1899) [Louis Laureys] + +- New Crowdin translations (#1884) [Jamie] + +- Fix mobile layout (#1888) [Louis Laureys] + +- Smal changes to the api. [tidusjar] + +- OmBlur. [tidusjar] + +- Hide the password field if it's not needed #1815. [Jamie] + +- Should fix #1885. [Jamie] + +- Make user management table responsive (#1882) [Louis Laureys] + +- Fixed some stuff for omBlur. [Jamie] + +- Some work... No one take a look at this, it's a suprise. [Jamie] + +- New Crowdin translations (#1858) [Jamie] + +- When requesting Anime, we now mark it correctly as Anime in Sonarr. [tidusjar] + +- Fixed #1879 and added the spans. [tidusjar] + +- Some work on the auto updater #1460. [tidusjar] + +- Removed the potential locking. [tidusjar] + +- Fixed #1863. [tidusjar] + +- Moved the update check code from the External azure service into Ombi at /api/v1/update/BRANCH. [Jamie] + +- Fixed the UI erroring out, also dont show tv with no externals. [tidusjar] + +- More memory management and improvements. [tidusjar] + +- These are not needed, added accidentally (#1860) [Louis Laureys] + +- Some memory management improvements. [tidusjar] + +- Fixed #1857. [tidusjar] + +- Delete old v2 ombi from v3 branch. [tidusjar] + +- New Crowdin translations (#1840) [Jamie] + +- Better login backgrounds! (#1852) [Louis Laureys] + +- Fixed #1851. [tidusjar] + +- Fixed #1826. [tidusjar] + +- Redo change #1848. [tidusjar] + +- Fix the issue for welcome emails not sending. [tidusjar] + +- Fix typo (#1845) [Kyle Lucy] + +- Fix user mentions in Slack notifications (#1846) [Aljosa Asanovic] + +- If Radarr/Sonarr has noticed that the media is available, then mark it as available in the UI. [Jamie] + +- Fixed #1835. [Jamie] + +- Enable Multi MIME and add alt tags to images (#1838) [Louis Laureys] + +- New Crowdin translations (#1816) [Jamie] + +- Fixed #1832. [tidusjar] + +- Switch to use a single HTTPClient rather than a new one every request !dev. [tidusjar] + +- Fix non-admin rights (#1820) [Rob Gökemeijer] + +- Fix duplicated "Requests" element ID on new Issues link (#1817) [Shoghi Cervantes] + +- Add the Issue Reporting functionality (#1811) [Jamie] + +- Removed the forum. [tidusjar] + - #1659 Made the option to ignore notifcations for auto approve. [Jamie] - New Crowdin translations (#1806) [Jamie] diff --git a/appveyor.yml b/appveyor.yml index 51a0f0859..c513c650e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,7 +23,14 @@ after_build: appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux.tar.gz" + appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm.tar.gz" + + + appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\windows-32bit.zip" + + +# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz" diff --git a/build.cake b/build.cake index ad3e7d93d..0fc440500 100644 --- a/build.cake +++ b/build.cake @@ -26,26 +26,29 @@ var csProj = "./src/Ombi/Ombi.csproj"; // Path to the project.csproj var solutionFile = "Ombi.sln"; // Solution file if needed GitVersion versionInfo = null; +var frameworkVer = "netcoreapp2.0"; + var buildSettings = new DotNetCoreBuildSettings { - Framework = "netcoreapp2.0", + Framework = frameworkVer, Configuration = "Release", OutputDirectory = Directory(buildDir), }; var publishSettings = new DotNetCorePublishSettings { - Framework = "netcoreapp2.0", + Framework = frameworkVer, Configuration = "Release", OutputDirectory = Directory(buildDir), }; -var artifactsFolder = buildDir + "/netcoreapp2.0/"; +var artifactsFolder = buildDir + "/"+frameworkVer+"/"; var windowsArtifactsFolder = artifactsFolder + "win10-x64/published"; -var windows32BitArtifactsFolder = artifactsFolder + "win10-x32/published"; +var windows32BitArtifactsFolder = artifactsFolder + "win10-x86/published"; var osxArtifactsFolder = artifactsFolder + "osx-x64/published"; var linuxArtifactsFolder = artifactsFolder + "linux-x64/published"; var linuxArmArtifactsFolder = artifactsFolder + "linux-arm/published"; +var linuxArm64BitArtifactsFolder = artifactsFolder + "linux-arm64/published"; @@ -104,6 +107,10 @@ Task("SetVersionInfo") { fullVer = fullVer.Replace("_",""); } + if(fullVer.Contains("/")) + { + fullVer = fullVer.Replace("/",""); + } buildSettings.ArgumentCustomization = args => args.Append("/p:SemVer=" + versionInfo.AssemblySemVer); buildSettings.ArgumentCustomization = args => args.Append("/p:FullVer=" + fullVer); @@ -161,35 +168,38 @@ Task("Package") GZipCompress(osxArtifactsFolder, artifactsFolder + "osx.tar.gz"); GZipCompress(linuxArtifactsFolder, artifactsFolder + "linux.tar.gz"); GZipCompress(linuxArmArtifactsFolder, artifactsFolder + "linux-arm.tar.gz"); + //GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz"); }); Task("Publish") .IsDependentOn("PrePublish") .IsDependentOn("Publish-Windows") + .IsDependentOn("Publish-Windows-32bit") .IsDependentOn("Publish-OSX") .IsDependentOn("Publish-Linux") .IsDependentOn("Publish-Linux-ARM") + //.IsDependentOn("Publish-Linux-ARM-64Bit") .IsDependentOn("Package"); Task("Publish-Windows") .Does(() => { publishSettings.Runtime = "win10-x64"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/win10-x64/published"); + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x64/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); - CopyFile(buildDir + "/netcoreapp2.0/win10-x64/Swagger.xml", buildDir + "/netcoreapp2.0/win10-x64/published/Swagger.xml"); + CopyFile(buildDir + "/"+frameworkVer+"/win10-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x64/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); Task("Publish-Windows-32bit") .Does(() => { - publishSettings.Runtime = "win10-x32"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/win10-x32/published"); + publishSettings.Runtime = "win10-x86"; + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/win10-x86/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); - CopyFile(buildDir + "/netcoreapp2.0/win10-x32/Swagger.xml", buildDir + "/netcoreapp2.0/win10-x32/published/Swagger.xml"); + CopyFile(buildDir + "/"+frameworkVer+"/win10-x86/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x86/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -197,10 +207,10 @@ Task("Publish-OSX") .Does(() => { publishSettings.Runtime = "osx-x64"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/osx-x64/published"); + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/osx-x64/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); - CopyFile(buildDir + "/netcoreapp2.0/osx-x64/Swagger.xml", buildDir + "/netcoreapp2.0/osx-x64/published/Swagger.xml"); + CopyFile(buildDir + "/"+frameworkVer+"/osx-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/osx-x64/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -208,10 +218,10 @@ Task("Publish-Linux") .Does(() => { publishSettings.Runtime = "linux-x64"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/linux-x64/published"); + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/linux-x64/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); - CopyFile(buildDir + "/netcoreapp2.0/linux-x64/Swagger.xml", buildDir + "/netcoreapp2.0/linux-x64/published/Swagger.xml"); + CopyFile(buildDir + "/"+frameworkVer+"/linux-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-x64/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -219,12 +229,25 @@ Task("Publish-Linux-ARM") .Does(() => { publishSettings.Runtime = "linux-arm"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/linux-arm/published"); + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/linux-arm/published"); + + DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); + CopyFile( + buildDir + "/"+frameworkVer+"/linux-arm/Swagger.xml", + buildDir + "/"+frameworkVer+"/linux-arm/published/Swagger.xml"); + DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); +}); + +Task("Publish-Linux-ARM-64Bit") + .Does(() => +{ + publishSettings.Runtime = "linux-arm64"; + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/linux-arm64/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); CopyFile( - buildDir + "/netcoreapp2.0/linux-arm/Swagger.xml", - buildDir + "/netcoreapp2.0/linux-arm/published/Swagger.xml"); + buildDir + "/"+frameworkVer+"/linux-arm64/Swagger.xml", + buildDir + "/"+frameworkVer+"/linux-arm64/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); diff --git a/src/Ombi.Api.Radarr/CommandResult.cs b/src/Ombi.Api.Radarr/CommandResult.cs new file mode 100644 index 000000000..e658a1ede --- /dev/null +++ b/src/Ombi.Api.Radarr/CommandResult.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ombi.Api.Radarr +{ + public class CommandResult + { + public string name { get; set; } + } +} diff --git a/src/Ombi.Api.Radarr/IRadarrApi.cs b/src/Ombi.Api.Radarr/IRadarrApi.cs index 8df55b748..a23f6d3f5 100644 --- a/src/Ombi.Api.Radarr/IRadarrApi.cs +++ b/src/Ombi.Api.Radarr/IRadarrApi.cs @@ -10,6 +10,9 @@ public interface IRadarrApi Task> GetProfiles(string apiKey, string baseUrl); Task> GetRootFolders(string apiKey, string baseUrl); Task SystemStatus(string apiKey, string baseUrl); + Task GetMovie(int id, string apiKey, string baseUrl); + Task UpdateMovie(MovieResponse movie, string apiKey, string baseUrl); + Task MovieSearch(int[] movieIds, string apiKey, string baseUrl); Task AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath,string apiKey, string baseUrl, bool searchNow, string minimumAvailability); } } \ No newline at end of file diff --git a/src/Ombi.Api.Radarr/Models/RadarrAddMovie.cs b/src/Ombi.Api.Radarr/Models/RadarrAddMovie.cs index eaf7ee6da..9288058f9 100644 --- a/src/Ombi.Api.Radarr/Models/RadarrAddMovie.cs +++ b/src/Ombi.Api.Radarr/Models/RadarrAddMovie.cs @@ -17,10 +17,7 @@ public RadarrAddMovieResponse() public bool monitored { get; set; } public int tmdbId { get; set; } public List images { get; set; } - public string cleanTitle { get; set; } - public string imdbId { get; set; } public string titleSlug { get; set; } - public int id { get; set; } public int year { get; set; } public string minimumAvailability { get; set; } } diff --git a/src/Ombi.Api.Radarr/Models/RadarrError.cs b/src/Ombi.Api.Radarr/Models/RadarrError.cs index a42c2ca52..654f83772 100644 --- a/src/Ombi.Api.Radarr/Models/RadarrError.cs +++ b/src/Ombi.Api.Radarr/Models/RadarrError.cs @@ -3,19 +3,10 @@ public class RadarrError { public string message { get; set; } - public string description { get; set; } } public class RadarrErrorResponse { - public string propertyName { get; set; } public string errorMessage { get; set; } - public object attemptedValue { get; set; } - public FormattedMessagePlaceholderValues formattedMessagePlaceholderValues { get; set; } - } - public class FormattedMessagePlaceholderValues - { - public string propertyName { get; set; } - public object propertyValue { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Api.Radarr/RadarrApi.cs b/src/Ombi.Api.Radarr/RadarrApi.cs index 9ec66eb70..1f897b60b 100644 --- a/src/Ombi.Api.Radarr/RadarrApi.cs +++ b/src/Ombi.Api.Radarr/RadarrApi.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; @@ -53,6 +52,23 @@ public async Task> GetMovies(string apiKey, string baseUrl) return await Api.Request>(request); } + public async Task GetMovie(int id, string apiKey, string baseUrl) + { + var request = new Request($"/api/movie/{id}", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + + return await Api.Request(request); + } + + public async Task UpdateMovie(MovieResponse movie, string apiKey, string baseUrl) + { + var request = new Request($"/api/movie/", baseUrl, HttpMethod.Put); + AddHeaders(request, apiKey); + request.AddJsonBody(movie); + + return await Api.Request(request); + } + public async Task AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow, string minimumAvailability) { var request = new Request("/api/movie", baseUrl, HttpMethod.Post); @@ -66,7 +82,7 @@ public async Task AddMovie(int tmdbId, string title, int titleSlug = title, monitored = true, year = year, - minimumAvailability = minimumAvailability, + minimumAvailability = minimumAvailability }; if (searchNow) @@ -81,9 +97,9 @@ public async Task AddMovie(int tmdbId, string title, int request.AddHeader("X-Api-Key", apiKey); request.AddJsonBody(options); + var response = await Api.RequestContent(request); try { - var response = await Api.RequestContent(request); if (response.Contains("\"message\":")) { var error = JsonConvert.DeserializeObject(response); @@ -98,11 +114,24 @@ public async Task AddMovie(int tmdbId, string title, int } catch (JsonSerializationException jse) { - Logger.LogError(LoggingEvents.RadarrApi, jse, "Error When adding movie to Radarr"); + Logger.LogError(LoggingEvents.RadarrApi, jse, "Error When adding movie to Radarr, Reponse: {0}", response); } return null; } + public async Task MovieSearch(int[] movieIds, string apiKey, string baseUrl) + { + var result = await Command(apiKey, baseUrl, new { name = "MoviesSearch", movieIds }); + return result != null; + } + + private async Task Command(string apiKey, string baseUrl, object body) + { + var request = new Request($"/api/Command/", baseUrl, HttpMethod.Post); + request.AddHeader("X-Api-Key", apiKey); + request.AddJsonBody(body); + return await Api.Request(request); + } /// /// Adds the required headers and also the authorization header diff --git a/src/Ombi.Api.Sonarr/Models/CommandResult.cs b/src/Ombi.Api.Sonarr/Models/CommandResult.cs index 0e9565eb8..8680bd1e3 100644 --- a/src/Ombi.Api.Sonarr/Models/CommandResult.cs +++ b/src/Ombi.Api.Sonarr/Models/CommandResult.cs @@ -8,11 +8,6 @@ namespace Ombi.Api.Sonarr.Models public class CommandResult { public string name { get; set; } - public DateTime startedOn { get; set; } - public DateTime stateChangeTime { get; set; } - public bool sendUpdatesToClient { get; set; } - public string state { get; set; } - public int id { get; set; } } } diff --git a/src/Ombi.Api.Sonarr/SonarrApi.cs b/src/Ombi.Api.Sonarr/SonarrApi.cs index 8753203a2..7fd74d2a3 100644 --- a/src/Ombi.Api.Sonarr/SonarrApi.cs +++ b/src/Ombi.Api.Sonarr/SonarrApi.cs @@ -66,8 +66,10 @@ public async Task GetSeriesById(int id, string apiKey, string base var request = new Request($"/api/series/{id}", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); var result = await Api.Request(request); - result.seasons.ToList().RemoveAt(0); - + if (result?.seasons?.Length > 0) + { + result?.seasons?.ToList().RemoveAt(0); + } return result; } diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs index 1f65b5a15..91b6404db 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs @@ -17,5 +17,7 @@ public interface IMovieEngine Task> UpcomingMovies(); Task LookupImdbInformation(int theMovieDbId); + + Task> SimilarMovies(int theMovieDbId); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs index 06a419214..53721f792 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs @@ -10,9 +10,13 @@ public interface ITvSearchEngine Task>> SearchTreeNode(string searchTerm); Task> GetShowInformationTreeNode(int tvdbid); Task GetShowInformation(int tvdbid); - Task>> Popular(); - Task>> Anticipated(); - Task>> MostWatches(); - Task>> Trending(); + Task>> PopularTree(); + Task> Popular(); + Task>> AnticipatedTree(); + Task> Anticipated(); + Task>> MostWatchesTree(); + Task> MostWatches(); + Task>> TrendingTree(); + Task> Trending(); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 2b678b74c..451820ebb 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Ombi.Api.TheMovieDb.Models; using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Rule.Interfaces; @@ -45,7 +46,7 @@ public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestServi /// public async Task RequestMovie(MovieRequestViewModel model) { - var movieInfo = await MovieApi.GetMovieInformation(model.TheMovieDbId); + var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId); if (movieInfo == null || movieInfo.Id == 0) { return new RequestEngineResult @@ -78,6 +79,9 @@ public async Task RequestMovie(MovieRequestViewModel model) Background = movieInfo.BackdropPath }; + var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); + requestModel.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate; + var ruleResults = (await RunRequestRules(requestModel)).ToList(); if (ruleResults.Any(x => !x.Success)) { diff --git a/src/Ombi.Core/Engine/MovieSearchEngine.cs b/src/Ombi.Core/Engine/MovieSearchEngine.cs index 4e105d38d..33107e5af 100644 --- a/src/Ombi.Core/Engine/MovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/MovieSearchEngine.cs @@ -40,7 +40,7 @@ public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovi /// public async Task LookupImdbInformation(int theMovieDbId) { - var movieInfo = await MovieApi.GetMovieInformationWithVideo(theMovieDbId); + var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId); var viewMovie = Mapper.Map(movieInfo); return await ProcessSingleMovie(viewMovie, true); @@ -63,6 +63,22 @@ public async Task> Search(string search) return null; } + /// + /// Get similar movies to the id passed in + /// + /// + /// + public async Task> SimilarMovies(int theMovieDbId) + { + var result = await MovieApi.SimilarMovies(theMovieDbId); + if (result != null) + { + Logger.LogDebug("Search Result: {result}", result); + return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API + } + return null; + } + /// /// Gets popular movies. /// @@ -141,6 +157,8 @@ private async Task ProcessSingleMovie(SearchMovieViewModel var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id); viewMovie.Id = showInfo.Id; // TheMovieDbId viewMovie.ImdbId = showInfo.ImdbId; + var usDates = viewMovie.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); + viewMovie.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate; } viewMovie.TheMovieDbId = viewMovie.Id.ToString(); diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index 2fc3e6c12..56000d00a 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -122,33 +122,60 @@ public async Task> GetShowInformationTreeNode(in return ParseIntoTreeNode(result); } - public async Task>> Popular() + public async Task>> PopularTree() { var result = await MemCache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); var processed = await ProcessResults(result); return processed.Select(ParseIntoTreeNode).ToList(); } - public async Task>> Anticipated() + public async Task> Popular() + { + var result = await MemCache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); + var processed = await ProcessResults(result); + return processed; + } + + public async Task>> AnticipatedTree() { var result = await MemCache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12)); - var processed= await ProcessResults(result); + var processed = await ProcessResults(result); return processed.Select(ParseIntoTreeNode).ToList(); } + public async Task> Anticipated() + { + var result = await MemCache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12)); + var processed = await ProcessResults(result); + return processed; + } - public async Task>> MostWatches() + public async Task>> MostWatchesTree() { var result = await MemCache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12)); var processed = await ProcessResults(result); return processed.Select(ParseIntoTreeNode).ToList(); } + public async Task> MostWatches() + { + var result = await MemCache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12)); + var processed = await ProcessResults(result); + return processed; + } - public async Task>> Trending() + public async Task>> TrendingTree() { var result = await MemCache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12)); var processed = await ProcessResults(result); return processed.Select(ParseIntoTreeNode).ToList(); } + + public async Task> Trending() + { + var result = await MemCache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12)); + var processed = await ProcessResults(result); + return processed; + } + private static TreeNode ParseIntoTreeNode(SearchTvShowViewModel result) { return new TreeNode diff --git a/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs b/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs index 869a2c4b7..4d0d05a49 100644 --- a/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs +++ b/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Ombi.Api.TheMovieDb.Models; using Ombi.Store.Entities; namespace Ombi.Core.Models.Search @@ -25,5 +26,7 @@ public class SearchMovieViewModel : SearchViewModel public int RootPathOverride { get; set; } public int QualityOverride { get; set; } public override RequestType Type => RequestType.Movie; + public ReleaseDatesDto ReleaseDates { get; set; } + public DateTime? DigitalReleaseDate { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MovieSender.cs b/src/Ombi.Core/Senders/MovieSender.cs index 5d19b5b06..e57a5bf2a 100644 --- a/src/Ombi.Core/Senders/MovieSender.cs +++ b/src/Ombi.Core/Senders/MovieSender.cs @@ -48,7 +48,7 @@ public async Task Send(MovieRequests model) var dogSettings = await DogNzbSettings.GetSettingsAsync(); if (dogSettings.Enabled) { - await SendToDogNzb(model,dogSettings); + await SendToDogNzb(model, dogSettings); return new SenderResult { Success = true, @@ -95,18 +95,40 @@ private async Task SendToRadarr(MovieRequests model, RadarrSetting } var rootFolderPath = model.RootPathOverride <= 0 ? settings.DefaultRootPath : await RadarrRootPath(model.RootPathOverride, settings); - var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, settings.MinimumAvailability); - if (!string.IsNullOrEmpty(result.Error?.message)) + // Check if the movie already exists? Since it could be unmonitored + var movies = await RadarrApi.GetMovies(settings.ApiKey, settings.FullUri); + var existingMovie = movies.FirstOrDefault(x => x.tmdbId == model.TheMovieDbId); + if (existingMovie == null) { - Log.LogError(LoggingEvents.RadarrCacher,result.Error.message); - return new SenderResult { Success = false, Message = result.Error.message, Sent = false }; + var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, + qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, + settings.MinimumAvailability); + + if (!string.IsNullOrEmpty(result.Error?.message)) + { + Log.LogError(LoggingEvents.RadarrCacher, result.Error.message); + return new SenderResult { Success = false, Message = result.Error.message, Sent = false }; + } + if (!string.IsNullOrEmpty(result.title)) + { + return new SenderResult { Success = true, Sent = false }; + } + return new SenderResult { Success = true, Sent = false }; } - if (!string.IsNullOrEmpty(result.title)) + // We have the movie, check if we can request it or change the status + if (!existingMovie.monitored) { - return new SenderResult { Success = true, Sent = false }; + // let's set it to monitored and search for it + existingMovie.monitored = true; + await RadarrApi.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri); + // Search for it + await RadarrApi.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri); + + return new SenderResult { Success = true, Sent = true }; } - return new SenderResult { Success = true, Sent = false }; + + return new SenderResult { Success = false, Sent = false, Message = "Movie is already monitored" }; } private async Task RadarrRootPath(int overrideId, RadarrSettings settings) diff --git a/src/Ombi.Mapping/Profiles/MovieProfile.cs b/src/Ombi.Mapping/Profiles/MovieProfile.cs index 29b2f3da2..d7e43fc50 100644 --- a/src/Ombi.Mapping/Profiles/MovieProfile.cs +++ b/src/Ombi.Mapping/Profiles/MovieProfile.cs @@ -42,7 +42,19 @@ public MovieProfile() .ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.runtime)) .ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status)) .ForMember(dest => dest.Tagline, opts => opts.MapFrom(src => src.tagline)) - .ForMember(dest => dest.VoteCount, opts => opts.MapFrom(src => src.vote_count)); + .ForMember(dest => dest.VoteCount, opts => opts.MapFrom(src => src.vote_count)) + .ForMember(dest => dest.ReleaseDates, opts => opts.MapFrom(src => src.release_dates)); + + CreateMap() + .ForMember(x => x.Results, o => o.MapFrom(src => src.results)); + + CreateMap() + .ForMember(x => x.ReleaseDate, o => o.MapFrom(s => s.release_dates)) + .ForMember(x => x.IsoCode, o => o.MapFrom(s => s.iso_3166_1)); + CreateMap() + .ForMember(x => x.ReleaseDate, o => o.MapFrom(s => s.release_date)) + .ForMember(x => x.Type, o => o.MapFrom(s => s.Type)); + CreateMap(); CreateMap().ReverseMap(); diff --git a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs index 38b669d76..da3b3305c 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs @@ -106,14 +106,23 @@ public async Task Update(PerformContext c) { // Let's download the correct zip var desc = RuntimeInformation.OSDescription; - var proce = RuntimeInformation.ProcessArchitecture; + var process = RuntimeInformation.ProcessArchitecture; - Logger.LogDebug(LoggingEvents.Updater, "OS Information: {0} {1}", desc, proce); + Logger.LogDebug(LoggingEvents.Updater, "OS Information: {0} {1}", desc, process); Downloads download; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { Logger.LogDebug(LoggingEvents.Updater, "We are Windows"); - download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("windows.zip", CompareOptions.IgnoreCase)); + if (process == Architecture.X64) + { + download = updates.Downloads.FirstOrDefault(x => + x.Name.Contains("windows.", CompareOptions.IgnoreCase)); + } + else + { + download = updates.Downloads.FirstOrDefault(x => + x.Name.Contains("windows-32bit", CompareOptions.IgnoreCase)); + } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { @@ -123,13 +132,16 @@ public async Task Update(PerformContext c) else { Logger.LogDebug(LoggingEvents.Updater, "We are linux"); - if (RuntimeInformation.OSDescription.Contains("arm", CompareOptions.IgnoreCase)) + if (process == Architecture.Arm) + { + download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm.", CompareOptions.IgnoreCase)); + } else if (process == Architecture.Arm64) { - download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm", CompareOptions.IgnoreCase)); + download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm64.", CompareOptions.IgnoreCase)); } else { - download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("linux", CompareOptions.IgnoreCase)); + download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("linux.", CompareOptions.IgnoreCase)); } } if (download == null) @@ -196,11 +208,14 @@ public async Task Update(PerformContext c) var updaterFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "TempUpdate", $"Ombi.Updater{updaterExtension}"); + // Make sure the file is an executable + ExecLinuxCommand($"chmod +x {updaterFile}"); + // There must be an update var start = new ProcessStartInfo { - UseShellExecute = false, - CreateNoWindow = false, + UseShellExecute = true, + CreateNoWindow = false, // Ignored if UseShellExecute is set to true FileName = updaterFile, Arguments = GetArgs(settings), WorkingDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "TempUpdate"), @@ -244,24 +259,16 @@ private string GetArgs(UpdateSettings settings) sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" "); } var sb2 = new StringBuilder(); - var hasStartupArgs = false; if (url?.Value.HasValue() ?? false) { - hasStartupArgs = true; - sb2.Append(url.Value); + sb2.Append($" --host {url.Value}"); } if (storage?.Value.HasValue() ?? false) { - hasStartupArgs = true; - sb2.Append(storage.Value); - } - if (hasStartupArgs) - { - sb.Append($"--startupArgs {sb2.ToString()}"); + sb2.Append($" --storage {storage.Value}"); } return sb.ToString(); - //return string.Join(" ", currentLocation, processName, url?.Value ?? string.Empty, storage?.Value ?? string.Empty); } private void RunScript(UpdateSettings settings, string downloadUrl) @@ -367,5 +374,30 @@ public static void DeleteDirectory(string path) Directory.Delete(path, true); } } + + public static void ExecLinuxCommand(string cmd) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + var escapedArgs = cmd.Replace("\"", "\\\""); + + var process = new Process + { + StartInfo = new ProcessStartInfo + { + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + FileName = "/bin/bash", + Arguments = $"-c \"{escapedArgs}\"" + } + }; + + process.Start(); + process.WaitForExit(); + } } } \ 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 c6b876c21..343c4256e 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs @@ -67,6 +67,16 @@ private async Task ProcessTv() { seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString()); } + + if (!seriesEpisodes.Any()) + { + // Let's try and match the series by name + seriesEpisodes = plexEpisodes.Where(x => + x.Series.Title.Equals(child.Title, StringComparison.CurrentCultureIgnoreCase) && + x.Series.ReleaseYear == child.ParentRequest.ReleaseDate.Year.ToString()); + + } + foreach (var season in child.SeasonRequests) { foreach (var episode in season.Episodes) diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index 3eda1cdad..92111b1cf 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -30,6 +30,7 @@ using System.Linq; using System.Threading.Tasks; using Hangfire; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Ombi.Api.Plex; using Ombi.Api.Plex.Models; @@ -81,7 +82,7 @@ public async Task CacheContent() } catch (Exception e) { - Logger.LogWarning(LoggingEvents.Cacher, e, "Exception thrown when attempting to cache the Plex Content"); + Logger.LogWarning(LoggingEvents.PlexContentCacher, e, "Exception thrown when attempting to cache the Plex Content"); } Logger.LogInformation("Starting EP Cacher"); @@ -120,20 +121,39 @@ private async Task StartTheCache(PlexSettings plexSettings) PlexContentId = show.ratingKey }); } - + // Do we already have this item? // Let's try and match - var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title + var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title && x.ReleaseYear == show.year.ToString() && x.Type == PlexMediaTypeEntity.Show); - if (existingContent == null) + if (existingContent != null) { // Just check the key - var hasSameKey = await Repo.GetByKey(show.ratingKey); - if (hasSameKey != null) + var existingKey = await Repo.GetByKey(show.ratingKey); + if (existingKey != null) + { + // The rating key is all good! + } + else { - existingContent = hasSameKey; + // This means the rating key has changed somehow. + // Should probably delete this and get the new one + var oldKey = existingContent.Key; + Repo.DeleteWithoutSave(existingContent); + + // Because we have changed the rating key, we need to change all children too + var episodeToChange = Repo.GetAllEpisodes().Where(x => x.GrandparentKey == oldKey); + if (episodeToChange.Any()) + { + foreach (var e in episodeToChange) + { + Repo.DeleteWithoutSave(e); + } + } + await Repo.SaveChangesAsync(); + existingContent = null; } } @@ -164,46 +184,70 @@ private async Task StartTheCache(PlexSettings plexSettings) } catch (Exception e) { - Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding new seasons to title {0}", existingContent.Title); + Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding new seasons to title {0}", existingContent.Title); } } else { + try + { - Logger.LogInformation("New show {0}, so add it", show.title); + Logger.LogInformation("New show {0}, so add it", show.title); - // Get the show metadata... This sucks since the `metadata` var contains all information about the show - // But it does not contain the `guid` property that we need to pull out thetvdb id... - var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, - show.ratingKey); - var providerIds = PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault().guid); + // Get the show metadata... This sucks since the `metadata` var contains all information about the show + // But it does not contain the `guid` property that we need to pull out thetvdb id... + var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, + show.ratingKey); + var providerIds = PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault().guid); + + var item = new PlexServerContent + { + AddedAt = DateTime.Now, + Key = show.ratingKey, + ReleaseYear = show.year.ToString(), + Type = PlexMediaTypeEntity.Show, + Title = show.title, + Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey), + Seasons = new List() + }; + if (providerIds.Type == ProviderType.ImdbId) + { + item.ImdbId = providerIds.ImdbId; + } + if (providerIds.Type == ProviderType.TheMovieDbId) + { + item.TheMovieDbId = providerIds.TheMovieDb; + } + if (providerIds.Type == ProviderType.TvDbId) + { + item.TvDbId = providerIds.TheTvDb; + } + + // Let's just double check to make sure we do not have it now we have some id's + var existingImdb = false; + var existingMovieDbId = false; + var existingTvDbId = false; + + existingImdb = await Repo.GetAll().AnyAsync(x => x.ImdbId == item.ImdbId && x.Type == PlexMediaTypeEntity.Show); + existingMovieDbId = await Repo.GetAll().AnyAsync(x => x.TheMovieDbId == item.TheMovieDbId && x.Type == PlexMediaTypeEntity.Show); + existingTvDbId = await Repo.GetAll().AnyAsync(x => x.TvDbId == item.TvDbId && x.Type == PlexMediaTypeEntity.Show); + + if (existingImdb || existingTvDbId || existingMovieDbId) + { + // We already have it! + continue; + } + + item.Seasons.ToList().AddRange(seasonsContent); + + contentToAdd.Add(item); - var item = new PlexServerContent - { - AddedAt = DateTime.Now, - Key = show.ratingKey, - ReleaseYear = show.year.ToString(), - Type = PlexMediaTypeEntity.Show, - Title = show.title, - Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey), - Seasons = new List() - }; - if (providerIds.Type == ProviderType.ImdbId) - { - item.ImdbId = providerIds.ImdbId; - } - if (providerIds.Type == ProviderType.TheMovieDbId) - { - item.TheMovieDbId = providerIds.TheMovieDb; } - if (providerIds.Type == ProviderType.TvDbId) + catch (Exception e) { - item.TvDbId = providerIds.TheTvDb; + Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding tv show {0}", show.title); } - item.Seasons.ToList().AddRange(seasonsContent); - - contentToAdd.Add(item); } } } @@ -214,54 +258,61 @@ private async Task StartTheCache(PlexSettings plexSettings) { // Let's check if we have this movie - var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title - && x.ReleaseYear == movie.year.ToString() - && x.Type == PlexMediaTypeEntity.Movie); - // The rating key keeps changing - //var existing = await Repo.GetByKey(movie.ratingKey); - if (existing != null) + try { - Logger.LogInformation("We already have movie {0}", movie.title); - continue; - } + var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title + && x.ReleaseYear == movie.year.ToString() + && x.Type == PlexMediaTypeEntity.Movie); + // The rating key keeps changing + //var existing = await Repo.GetByKey(movie.ratingKey); + if (existing != null) + { + Logger.LogInformation("We already have movie {0}", movie.title); + continue; + } - var hasSameKey = await Repo.GetByKey(movie.ratingKey); - if (hasSameKey != null) - { - await Repo.Delete(hasSameKey); - } + var hasSameKey = await Repo.GetByKey(movie.ratingKey); + if (hasSameKey != null) + { + await Repo.Delete(hasSameKey); + } - Logger.LogInformation("Adding movie {0}", movie.title); - var metaData = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, - movie.ratingKey); - var providerIds = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer.Metadata - .FirstOrDefault() - .guid); + Logger.LogInformation("Adding movie {0}", movie.title); + var metaData = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, + movie.ratingKey); + var providerIds = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer.Metadata + .FirstOrDefault() + .guid); - var item = new PlexServerContent - { - AddedAt = DateTime.Now, - Key = movie.ratingKey, - ReleaseYear = movie.year.ToString(), - Type = PlexMediaTypeEntity.Movie, - Title = movie.title, - Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey), - Seasons = new List(), - Quality = movie.Media?.FirstOrDefault()?.videoResolution ?? string.Empty - }; - if (providerIds.Type == ProviderType.ImdbId) - { - item.ImdbId = providerIds.ImdbId; - } - if (providerIds.Type == ProviderType.TheMovieDbId) - { - item.TheMovieDbId = providerIds.TheMovieDb; + var item = new PlexServerContent + { + AddedAt = DateTime.Now, + Key = movie.ratingKey, + ReleaseYear = movie.year.ToString(), + Type = PlexMediaTypeEntity.Movie, + Title = movie.title, + Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey), + Seasons = new List(), + Quality = movie.Media?.FirstOrDefault()?.videoResolution ?? string.Empty + }; + if (providerIds.Type == ProviderType.ImdbId) + { + item.ImdbId = providerIds.ImdbId; + } + if (providerIds.Type == ProviderType.TheMovieDbId) + { + item.TheMovieDbId = providerIds.TheMovieDb; + } + if (providerIds.Type == ProviderType.TvDbId) + { + item.TvDbId = providerIds.TheTvDb; + } + contentToAdd.Add(item); } - if (providerIds.Type == ProviderType.TvDbId) + catch (Exception e) { - item.TvDbId = providerIds.TheTvDb; + Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding new Movie {0}", movie.title); } - contentToAdd.Add(item); } } if (contentToAdd.Count > 500) diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs index 308c74489..f9fc3fbf8 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs @@ -48,8 +48,9 @@ public async Task Start() foreach (var server in s.Servers) { await Cache(server); - BackgroundJob.Enqueue(() => _availabilityChecker.Start()); } + + BackgroundJob.Enqueue(() => _availabilityChecker.Start()); } catch (Exception e) { @@ -127,7 +128,10 @@ private async Task GetEpisodes(PlexServers settings, Directory section) private async Task ProcessEpsiodes(PlexContainer episodes) { var ep = new HashSet(); + try + { + foreach (var episode in episodes?.MediaContainer?.Metadata ?? new Metadata[]{}) { // I don't think we need to get the metadata, we only need to get the metadata if we need the provider id (TheTvDbid). Why do we need it for episodes? @@ -142,6 +146,25 @@ private async Task ProcessEpsiodes(PlexContainer episodes) // continue; //} + // Let's check if we have the parent + var seriesExists = await _repo.GetByKey(episode.grandparentRatingKey); + if (seriesExists == null) + { + // Ok let's try and match it to a title. TODO (This is experimental) + var seriesMatch = await _repo.GetAll().FirstOrDefaultAsync(x => + x.Title.Equals(episode.grandparentTitle, StringComparison.CurrentCultureIgnoreCase)); + if (seriesMatch == null) + { + _log.LogWarning( + "The episode title {0} we cannot find the parent series. The episode grandparentKey = {1}, grandparentTitle = {2}", + episode.title, episode.grandparentRatingKey, episode.grandparentTitle); + continue; + } + + // Set the rating key to the correct one + episode.grandparentRatingKey = seriesMatch.Key; + } + ep.Add(new PlexEpisode { EpisodeNumber = episode.index, @@ -154,6 +177,12 @@ private async Task ProcessEpsiodes(PlexContainer episodes) } await _repo.AddRange(ep); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } } private bool Validate(PlexServers settings) diff --git a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs index 83d55aec3..f1c0925aa 100644 --- a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs +++ b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net.Http; using System.Threading.Tasks; @@ -9,6 +10,7 @@ using Ombi.Api; using Ombi.Api.Service; using Ombi.Core.Processor; +using Ombi.Helpers; namespace Ombi.Schedule.Processor { @@ -44,7 +46,8 @@ public async Task Process(string branch) if (masterBranch) { latestRelease = doc.DocumentNode.Descendants("h2") - .FirstOrDefault(x => x.InnerText != "(unreleased)"); + .FirstOrDefault(x => x.InnerText == "(unreleased)"); + // TODO: Change this to InnterText != "(unreleased)" once we go live and it's not a prerelease } else { @@ -173,10 +176,13 @@ private async Task GetGitubRelease(Release release, string releaseTag) var releases = await client.Repository.Release.GetAll("tidusjar", "ombi"); var latest = releases.FirstOrDefault(x => x.TagName == releaseTag); - + if (latest.Name.Contains("V2", CompareOptions.IgnoreCase)) + { + latest = null; + } if (latest == null) { - latest = releases.OrderBy(x => x.CreatedAt).FirstOrDefault(); + latest = releases.OrderByDescending(x => x.CreatedAt).FirstOrDefault(); } foreach (var item in latest.Assets) { diff --git a/src/Ombi.Store/Entities/Requests/FullBaseRequest.cs b/src/Ombi.Store/Entities/Requests/FullBaseRequest.cs index 95f771c63..eb6755f98 100644 --- a/src/Ombi.Store/Entities/Requests/FullBaseRequest.cs +++ b/src/Ombi.Store/Entities/Requests/FullBaseRequest.cs @@ -10,11 +10,14 @@ public class FullBaseRequest : BaseRequest public string Overview { get; set; } public string PosterPath { get; set; } public DateTime ReleaseDate { get; set; } + public DateTime? DigitalReleaseDate { get; set; } public string Status { get; set; } public string Background { get; set; } [NotMapped] public bool Released => DateTime.UtcNow > ReleaseDate; + [NotMapped] + public bool DigitalRelease => DigitalReleaseDate.HasValue && DigitalReleaseDate > DateTime.UtcNow; } } \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.Designer.cs b/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.Designer.cs new file mode 100644 index 000000000..a7ca036f2 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.Designer.cs @@ -0,0 +1,916 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180228114507_DigitalRelease")] + partial class DigitalRelease + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ProviderId"); + + b.Property("Title"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.cs b/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.cs new file mode 100644 index 000000000..196bfc28a --- /dev/null +++ b/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class DigitalRelease : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DigitalReleaseDate", + table: "MovieRequests", + type: "TEXT", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DigitalReleaseDate", + table: "MovieRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 6d2501703..bbd671d11 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -553,6 +553,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DeniedReason"); + b.Property("DigitalReleaseDate"); + b.Property("ImdbId"); b.Property("IssueId"); diff --git a/src/Ombi.Store/Repository/IPlexContentRepository.cs b/src/Ombi.Store/Repository/IPlexContentRepository.cs index cb244dcd2..2fef89be2 100644 --- a/src/Ombi.Store/Repository/IPlexContentRepository.cs +++ b/src/Ombi.Store/Repository/IPlexContentRepository.cs @@ -19,5 +19,8 @@ public interface IPlexContentRepository : IRepository Task AddRange(IEnumerable content); IEnumerable GetWhereContentByCustom(Expression> predicate); Task GetFirstContentByCustom(Expression> predicate); + Task DeleteEpisode(PlexEpisode content); + void DeleteWithoutSave(PlexServerContent content); + void DeleteWithoutSave(PlexEpisode content); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index 35bf56057..56fec441a 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -103,12 +103,29 @@ public IQueryable GetAllEpisodes() return Db.PlexEpisode.Include(x => x.Series).AsQueryable(); } + public void DeleteWithoutSave(PlexServerContent content) + { + Db.PlexServerContent.Remove(content); + } + + public void DeleteWithoutSave(PlexEpisode content) + { + Db.PlexEpisode.Remove(content); + } + public async Task Add(PlexEpisode content) { await Db.PlexEpisode.AddAsync(content); await Db.SaveChangesAsync(); return content; } + + public async Task DeleteEpisode(PlexEpisode content) + { + Db.PlexEpisode.Remove(content); + await Db.SaveChangesAsync(); + } + public async Task GetEpisodeByKey(int key) { return await Db.PlexEpisode.FirstOrDefaultAsync(x => x.Key == key); diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index d39e2e6a7..dd0d0e92c 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -8,11 +8,12 @@ namespace Ombi.Api.TheMovieDb public interface IMovieDbApi { Task GetMovieInformation(int movieId); - Task GetMovieInformationWithVideo(int movieId); + Task GetMovieInformationWithExtraInfo(int movieId); Task> NowPlaying(); Task> PopularMovies(); Task> SearchMovie(string searchTerm); Task> TopRated(); Task> Upcoming(); + Task> SimilarMovies(int movieId); } } \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/Models/MovieResponse.cs b/src/Ombi.TheMovieDbApi/Models/MovieResponse.cs index 17aceca5d..c707f92ed 100644 --- a/src/Ombi.TheMovieDbApi/Models/MovieResponse.cs +++ b/src/Ombi.TheMovieDbApi/Models/MovieResponse.cs @@ -24,6 +24,10 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using System; +using System.Collections.Generic; + namespace Ombi.TheMovieDbApi.Models { @@ -54,5 +58,26 @@ public class MovieResponse public bool video { get; set; } public float vote_average { get; set; } public int vote_count { get; set; } + public ReleaseDates release_dates { get; set; } + } + + public class ReleaseDates + { + public List results { get; set; } + } + + public class ReleaseResults + { + public string iso_3166_1 { get; set; } + public List release_dates { get; set; } + } + + public class ReleaseDate + { + public string Certification { get; set; } + public string iso_639_1 { get; set; } + public string note { get; set; } + public DateTime release_date { get; set; } + public int Type { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/Models/MovieResponseDTO.cs b/src/Ombi.TheMovieDbApi/Models/MovieResponseDTO.cs index 0fd27a23f..4df8083ba 100644 --- a/src/Ombi.TheMovieDbApi/Models/MovieResponseDTO.cs +++ b/src/Ombi.TheMovieDbApi/Models/MovieResponseDTO.cs @@ -1,4 +1,7 @@ -namespace Ombi.Api.TheMovieDb.Models +using System; +using System.Collections.Generic; + +namespace Ombi.Api.TheMovieDb.Models { public class MovieResponseDto { @@ -23,5 +26,33 @@ public class MovieResponseDto public bool Video { get; set; } public float VoteAverage { get; set; } public int VoteCount { get; set; } + public ReleaseDatesDto ReleaseDates { get; set; } + } + + public class ReleaseDatesDto + { + public List Results { get; set; } + } + + public class ReleaseResultsDto + { + public string IsoCode { get; set; } + public List ReleaseDate { get; set; } + } + + public class ReleaseDateDto + { + public DateTime ReleaseDate { get; set; } + public ReleaseDateType Type { get; set; } + } + + public enum ReleaseDateType + { + Premiere = 1, + TheatricalLimited = 2, + Theatrical = 3, + Digital = 4, + Physical = 5, + Tv = 6 } } \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index df3de6d4e..1fbfe9aaf 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -30,11 +30,20 @@ public async Task GetMovieInformation(int movieId) return Mapper.Map(result); } - public async Task GetMovieInformationWithVideo(int movieId) + public async Task> SimilarMovies(int movieId) + { + var request = new Request($"movie/{movieId}/similar", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + + var result = await Api.Request>(request); + return Mapper.Map>(result.results); + } + + public async Task GetMovieInformationWithExtraInfo(int movieId) { var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); - request.FullUri = request.FullUri.AddQueryParameter("append_to_response", "videos"); + request.FullUri = request.FullUri.AddQueryParameter("append_to_response", "videos,release_dates"); var result = await Api.Request(request); return Mapper.Map(result); } diff --git a/src/Ombi.Updater/Installer.cs b/src/Ombi.Updater/Installer.cs index 5a84ecde7..4827d45f9 100644 --- a/src/Ombi.Updater/Installer.cs +++ b/src/Ombi.Updater/Installer.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using Microsoft.Extensions.Logging; @@ -33,27 +34,27 @@ public void Start(StartupOptions opt) } // Make sure the process has been killed - while (p.FindProcessByName(opt.ProcessName).Any()) + while (p.FindProcessByName(opt.ProcessName).Any()) + { + Thread.Sleep(500); + _log.LogDebug("Found another process called {0}, KILLING!", opt.ProcessName); + var proc = p.FindProcessByName(opt.ProcessName).FirstOrDefault(); + if (proc != null) { - Thread.Sleep(500); - _log.LogDebug("Found another process called {0}, KILLING!", opt.ProcessName); - var proc = p.FindProcessByName(opt.ProcessName).FirstOrDefault(); - if (proc != null) - { - _log.LogDebug($"[{proc.Id}] - {proc.Name} - Path: {proc.StartPath}"); - opt.OmbiProcessId = proc.Id; - p.Kill(opt); - } + _log.LogDebug($"[{proc.Id}] - {proc.Name} - Path: {proc.StartPath}"); + opt.OmbiProcessId = proc.Id; + p.Kill(opt); } - - _log.LogDebug("Starting to move the files"); - MoveFiles(opt); - _log.LogDebug("Files replaced"); - // Start Ombi - StartOmbi(opt); } - private void StartOmbi(StartupOptions options) + _log.LogDebug("Starting to move the files"); + MoveFiles(opt); + _log.LogDebug("Files replaced"); + // Start Ombi + StartOmbi(opt); + } + + private void StartOmbi(StartupOptions options) { _log.LogDebug("Starting ombi"); var fileName = "Ombi.exe"; @@ -71,19 +72,29 @@ private void StartOmbi(StartupOptions options) Arguments = $"/C net start \"{options.WindowsServiceName}\"" }; - using (var process = new Process{StartInfo = startInfo}) + using (var process = new Process { StartInfo = startInfo }) { process.Start(); } } else { + var startupArgsBuilder = new StringBuilder(); + if (!string.IsNullOrEmpty(options.Host)) + { + startupArgsBuilder.Append($"--host {options.Host} "); + } + if (!string.IsNullOrEmpty(options.Storage)) + { + startupArgsBuilder.Append($"--storage {options.Storage}"); + } + var start = new ProcessStartInfo { UseShellExecute = false, FileName = Path.Combine(options.ApplicationPath, fileName), WorkingDirectory = options.ApplicationPath, - Arguments = options.StartupArgs + Arguments = startupArgsBuilder.ToString() }; using (var proc = new Process { StartInfo = start }) { diff --git a/src/Ombi.Updater/Ombi.Updater.csproj b/src/Ombi.Updater/Ombi.Updater.csproj index af866b8fe..9f5612367 100644 --- a/src/Ombi.Updater/Ombi.Updater.csproj +++ b/src/Ombi.Updater/Ombi.Updater.csproj @@ -2,7 +2,7 @@ Exe - win10-x64;win10-x32;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64; + win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64; netcoreapp2.0 3.0.0.0 3.0.0.0 diff --git a/src/Ombi.Updater/Program.cs b/src/Ombi.Updater/Program.cs index 26a1d180f..612d9d3fb 100644 --- a/src/Ombi.Updater/Program.cs +++ b/src/Ombi.Updater/Program.cs @@ -1,14 +1,10 @@ using System; -using System.Diagnostics; using System.IO; -using System.Linq; using CommandLine; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; -using Serilog.Events; -using ILogger = Serilog.ILogger; namespace Ombi.Updater { @@ -78,8 +74,10 @@ public class StartupOptions public string ApplicationPath { get; set; } [Option("processId", Required = false)] public int OmbiProcessId { get; set; } - [Option("startupArgs", Required = false)] - public string StartupArgs { get; set; } + [Option("host", Required = false)] + public string Host { get; set; } + [Option("storage", Required = false)] + public string Storage { get; set; } [Option("windowsServiceName", Required = false)] public string WindowsServiceName { get; set; } diff --git a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts index 9f822540c..d4345b185 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts @@ -34,6 +34,7 @@ export interface IMovieRequests extends IFullBaseRequest { theMovieDbId: number; rootPathOverride: number; qualityOverride: number; + digitalReleaseDate: Date; rootPathOverrideTitle: string; qualityOverrideTitle: string; diff --git a/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts b/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts index 77ec87ed5..c5301d2b0 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts @@ -22,6 +22,7 @@ available: boolean; plexUrl: string; quality: string; + digitalReleaseDate: Date; // for the UI requestProcessing: boolean; diff --git a/src/Ombi/ClientApp/app/landingpage/landingpage.component.scss b/src/Ombi/ClientApp/app/landingpage/landingpage.component.scss index b444ef92f..8cb255d73 100644 --- a/src/Ombi/ClientApp/app/landingpage/landingpage.component.scss +++ b/src/Ombi/ClientApp/app/landingpage/landingpage.component.scss @@ -1,4 +1,13 @@ -div.centered { +@media only screen and (max-width: 992px) { + div.centered { + max-height: 100%; + overflow-y: auto; + width: 100%; + padding: 50% 12.5%; + } +} + +div.centered { position: fixed; top: 50%; left: 50%; diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.html b/src/Ombi/ClientApp/app/requests/movierequests.component.html index 7b5f0b56b..82c87df5a 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.html +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.html @@ -66,7 +66,8 @@

{{request.title}} ({{request.releaseDate | date: 'yyyy -
{{ 'Requests.ReleaseDate' | translate }} {{request.releaseDate | date}}
+
{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}
+
{{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}
{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}

@@ -168,7 +169,7 @@

{{request.title}} ({{request.releaseDate | date: 'yyyy -

{{ 'Filter.Filter' | translate }}

+

{{ 'Requests.Filter' | translate }}


{{ 'Filter.FilterHeaderAvailability' | translate }}

@@ -200,5 +201,5 @@

{{ 'Filter.FilterHeaderRequestStatus' | translate }}

+ {{ 'Filter.ClearFilter' | translate }} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index d43622c44..58d8415fb 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -41,8 +41,9 @@

{{result.title}} ({{result.releaseDate | date: 'yyyy'}})

- - {{ 'Search.ReleaseDate' | translate }} {{result.releaseDate | date: 'dd/MM/yyyy'}} + + {{ 'Search.TheatricalRelease' | translate: {date: result.releaseDate | date: 'mediumDate'} }} + {{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | date: 'mediumDate'} }} @@ -79,7 +80,8 @@

{{result.title}} ({{result.releaseDate | date: 'yyyy'}})

{{ 'Common.Request' | translate }} - + +
View On Plex diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index 6be32becc..819463c4f 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -146,6 +146,15 @@ export class MovieSearchComponent implements OnInit { this.issueProviderId = req.id.toString(); } + public similarMovies(theMovieDbId: number) { + this.clearResults(); + this.searchService.similarMovies(theMovieDbId) + .subscribe(x => { + this.movieResults = x; + this.getExtraInfo(); + }); + } + private getExtraInfo() { this.movieResults.forEach((val, index) => { diff --git a/src/Ombi/ClientApp/app/services/search.service.ts b/src/Ombi/ClientApp/app/services/search.service.ts index 859f01240..522b7dddf 100644 --- a/src/Ombi/ClientApp/app/services/search.service.ts +++ b/src/Ombi/ClientApp/app/services/search.service.ts @@ -19,6 +19,9 @@ export class SearchService extends ServiceHelpers { public searchMovie(searchTerm: string): Observable { return this.http.get(`${this.url}/Movie/` + searchTerm); } + public similarMovies(theMovieDbId: number): Observable { + return this.http.get(`${this.url}/Movie/${theMovieDbId}/similar`); + } public popularMovies(): Observable { return this.http.get(`${this.url}/Movie/Popular`); @@ -54,15 +57,15 @@ export class SearchService extends ServiceHelpers { } public popularTv(): Observable { - return this.http.get(`${this.url}/Tv/popular`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/popular/tree`, {headers: this.headers}); } public mostWatchedTv(): Observable { - return this.http.get(`${this.url}/Tv/mostwatched`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/mostwatched/tree`, {headers: this.headers}); } public anticipatedTv(): Observable { - return this.http.get(`${this.url}/Tv/anticipated`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/anticipated/tree`, {headers: this.headers}); } public trendingTv(): Observable { - return this.http.get(`${this.url}/Tv/trending`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/trending/tree`, {headers: this.headers}); } } diff --git a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts index 1adfb97e2..e8a3f9e80 100644 --- a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts +++ b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts @@ -41,7 +41,7 @@ export class OmbiComponent implements OnInit { } const result = form.value; - if(result.baseUrl.length > 0) { + if(result.baseUrl && result.baseUrl.length > 0) { if(!result.baseUrl.startsWith("/")) { this.notificationService.error("Please ensure your base url starts with a '/'"); return; diff --git a/src/Ombi/ClientApp/styles/Themes/plex.scss b/src/Ombi/ClientApp/styles/Themes/plex.scss index d32f81000..ef3e31c45 100644 --- a/src/Ombi/ClientApp/styles/Themes/plex.scss +++ b/src/Ombi/ClientApp/styles/Themes/plex.scss @@ -1,5 +1,4 @@ -@import '../base.scss'; -$primary-colour: #df691a; +$primary-colour: #df691a; $primary-colour-outline: #ff761b; $bg-colour: #333333; $bg-colour-disabled: #252424; diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index cf15cb561..3e47df13c 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -22,6 +22,49 @@ $i: !important; } } +@media only screen and (max-width: 768px) { + + .table-usermanagement { + /* Force table to not be like tables anymore */ + display: block; + + thead, tbody, th, td, tr { + display: block; + } + + /* Hide table headers (but not display: none;, for accessibility) */ + thead tr { + position: absolute; + top: -9999px; + left: -9999px; + } + + td { + /* Behave like a "row" */ + border: none; + border-bottom: 1px solid #eee; + position: relative; + padding-left: 50% $i; + min-height: 25px; + } + + td:before { + /* Now like a table header */ + position: absolute; + /* Top/left values mimic padding */ + top: 6px; + left: 6px; + width: 45%; + padding-right: 10px; + white-space: nowrap; + } + /* Label the data */ + .td-labelled:before { + content: attr(data-label) + } + } +} + @media (max-width: 48em) { .home { padding-top: 1rem; @@ -841,8 +884,13 @@ textarea { border: 1px solid $form-color-lighter; } +.ui-treetable tfoot td, .ui-treetable th { + text-align: left; +} + .ui-treetable tbody td { white-space: inherit; + overflow: visible; } table a:not(.btn) { @@ -897,49 +945,6 @@ a > h4:hover { padding-top:15px; } -@media only screen and (max-width: 768px) { - - .table-usermanagement { - /* Force table to not be like tables anymore */ - display: block; - - thead, tbody, th, td, tr { - display: block; - } - - /* Hide table headers (but not display: none;, for accessibility) */ - thead tr { - position: absolute; - top: -9999px; - left: -9999px; - } - - td { - /* Behave like a "row" */ - border: none; - border-bottom: 1px solid #eee; - position: relative; - padding-left: 50% $i; - min-height: 25px; - } - - td:before { - /* Now like a table header */ - position: absolute; - /* Top/left values mimic padding */ - top: 6px; - left: 6px; - width: 45%; - padding-right: 10px; - white-space: nowrap; - } - /* Label the data */ - .td-labelled:before { - content: attr(data-label) - } - } -} - .searchWidth { width: 94%; } \ No newline at end of file diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index 58ba1b7c1..6a8aee52c 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; - +using System.Web; using AutoMapper; using Hangfire; using Microsoft.AspNetCore.Authorization; @@ -472,8 +472,18 @@ public async Task UpdateUser([FromBody] UserViewModel ui) // Get the roles var userRoles = await UserManager.GetRolesAsync(user); + // Am I modifying myself? + var modifyingSelf = user.UserName.Equals(User.Identity.Name, StringComparison.CurrentCultureIgnoreCase); + foreach (var role in userRoles) { + if (modifyingSelf && role.Equals(OmbiRoles.Admin)) + { + // We do not want to remove the admin role from yourself, this must be an accident + var claim = ui.Claims.FirstOrDefault(x => x.Value == OmbiRoles.Admin && x.Enabled); + ui.Claims.Remove(claim); + continue; + } await UserManager.RemoveFromRoleAsync(user, role); } @@ -613,25 +623,54 @@ public async Task SubmitResetPassword([FromBody]SubmitPasswo return defaultMessage; } - // We have the user - var token = await UserManager.GeneratePasswordResetTokenAsync(user); - // We now need to email the user with this token - var emailSettings = await EmailSettings.GetSettingsAsync(); + var customizationSettings = await CustomizationSettings.GetSettingsAsync(); var appName = (string.IsNullOrEmpty(customizationSettings.ApplicationName) ? "Ombi" : customizationSettings.ApplicationName); + var emailSettings = await EmailSettings.GetSettingsAsync(); + customizationSettings.AddToUrl("/token?token="); var url = customizationSettings.ApplicationUrl; - await EmailProvider.SendAdHoc(new NotificationMessage + if (user.UserType == UserType.PlexUser) + { + await EmailProvider.SendAdHoc(new NotificationMessage + { + To = user.Email, + Subject = $"{appName} Password Reset", + Message = + $"You recently made a request to reset your {appName} account. Please click the link below to complete the process.

" + + $" Reset " + }, emailSettings); + } + else if (user.UserType == UserType.EmbyUser && user.IsEmbyConnect) { - To = user.Email, - Subject = $"{appName} Password Reset", - Message = $"You recently made a request to reset your {appName} account. Please click the link below to complete the process.

" + - $" Reset " - }, emailSettings); + await EmailProvider.SendAdHoc(new NotificationMessage + { + To = user.Email, + Subject = $"{appName} Password Reset", + Message = + $"You recently made a request to reset your {appName} account.

" + + $"To reset your password you need to go to Emby.Media and then click on your Username > Edit Profile > Email and Password" + }, emailSettings); + } + else + { + // We have the user + var token = await UserManager.GeneratePasswordResetTokenAsync(user); + var encodedToken = WebUtility.UrlEncode(token); + + await EmailProvider.SendAdHoc(new NotificationMessage + { + To = user.Email, + Subject = $"{appName} Password Reset", + Message = + $"You recently made a request to reset your {appName} account. Please click the link below to complete the process.

" + + $" Reset " + }, emailSettings); + } return defaultMessage; } diff --git a/src/Ombi/Controllers/LoggingController.cs b/src/Ombi/Controllers/LoggingController.cs index a61b6c144..c6879da39 100644 --- a/src/Ombi/Controllers/LoggingController.cs +++ b/src/Ombi/Controllers/LoggingController.cs @@ -17,7 +17,7 @@ public LoggingController(ILogger logger) } private ILogger Logger { get; } - private const string Message = "Exception: {0} at {1}. Stacktrade {2}"; + private const string Message = "Exception: {0} at {1}. Stacktrace {2}"; [HttpPost] public IActionResult Log([FromBody]UiLoggingModel l) diff --git a/src/Ombi/Controllers/SearchController.cs b/src/Ombi/Controllers/SearchController.cs index 1a5d36b50..7060a973e 100644 --- a/src/Ombi/Controllers/SearchController.cs +++ b/src/Ombi/Controllers/SearchController.cs @@ -61,6 +61,19 @@ public async Task GetExtraMovieInfo(int theMovieDbId) return await MovieEngine.LookupImdbInformation(theMovieDbId); } + /// + /// Returns similar movies to the movie id passed in + /// + /// ID of the movie + /// + /// We use TheMovieDb as the Movie Provider + /// + [HttpGet("movie/{theMovieDbId}/similar")] + public async Task> SimilarMovies(int theMovieDbId) + { + return await MovieEngine.SimilarMovies(theMovieDbId); + } + /// /// Returns Popular Movies /// @@ -151,43 +164,91 @@ public async Task GetShowInfo(int tvdbId) return await TvEngine.GetShowInformation(tvdbId); } + /// + /// Returns Popular Tv Shows + /// + /// We use Trakt.tv as the Provider + /// + [HttpGet("tv/popular/tree")] + public async Task>> PopularTvTree() + { + return await TvEngine.PopularTree(); + } + /// /// Returns Popular Tv Shows /// /// We use Trakt.tv as the Provider /// [HttpGet("tv/popular")] - public async Task>> PopularTv() + public async Task> PopularTv() { return await TvEngine.Popular(); } + + /// + /// Returns most Anticiplateds tv shows. + /// + /// We use Trakt.tv as the Provider + /// + [HttpGet("tv/anticipated/tree")] + public async Task>> AnticipatedTvTree() + { + return await TvEngine.AnticipatedTree(); + } + + /// /// Returns most Anticiplateds tv shows. /// /// We use Trakt.tv as the Provider /// [HttpGet("tv/anticipated")] - public async Task>> AnticiplatedTv() + public async Task> AnticipatedTv() { return await TvEngine.Anticipated(); } + + /// + /// Returns Most watched shows. + /// + /// We use Trakt.tv as the Provider + /// + [HttpGet("tv/mostwatched/tree")] + public async Task>> MostWatchedTree() + { + return await TvEngine.MostWatchesTree(); + } + /// /// Returns Most watched shows. /// /// We use Trakt.tv as the Provider /// [HttpGet("tv/mostwatched")] - public async Task>> MostWatched() + public async Task> MostWatched() { return await TvEngine.MostWatches(); } + + /// + /// Returns trending shows + /// + /// We use Trakt.tv as the Provider + /// + [HttpGet("tv/trending/tree")] + public async Task>> TrendingTree() + { + return await TvEngine.TrendingTree(); + } + /// /// Returns trending shows /// /// We use Trakt.tv as the Provider /// [HttpGet("tv/trending")] - public async Task>> Trending() + public async Task> Trending() { return await TvEngine.Trending(); } diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 0f0dfb8c6..0bb111bc6 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -2,7 +2,7 @@ netcoreapp2.0 - win10-x64;win10-x32;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64; + win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64; false Latest $(SemVer) diff --git a/src/Ombi/Program.cs b/src/Ombi/Program.cs index f3f9c8b7e..943301a4d 100644 --- a/src/Ombi/Program.cs +++ b/src/Ombi/Program.cs @@ -28,6 +28,12 @@ public static void Main(string[] args) { host = o.Host; storagePath = o.StoragePath; + }).WithNotParsed(err => + { + foreach (var e in err) + { + Console.WriteLine(e); + } }); Console.WriteLine(HelpOutput(result)); @@ -46,7 +52,7 @@ public static void Main(string[] args) url = new ApplicationConfiguration { Type = ConfigurationTypes.Url, - Value = "http://*" + Value = "http://*:5000" }; ctx.ApplicationConfigurations.Add(url); @@ -87,13 +93,13 @@ private static string HelpOutput(ParserResult args) public class Options { - [Option('h', "host", Required = false, HelpText = + [Option("host", Required = false, HelpText = "Set to a semicolon-separated (;) list of URL prefixes to which the server should respond. For example, http://localhost:123." + " Use \"*\" to indicate that the server should listen for requests on any IP address or hostname using the specified port and protocol (for example, http://*:5000). " + "The protocol (http:// or https://) must be included with each URL. Supported formats vary between servers.", Default = "http://*:5000")] public string Host { get; set; } - - [Option('s', "storage", Required = false, HelpText = "Storage path, where we save the logs and database")] + + [Option("storage", Required = false, HelpText = "Storage path, where we save the logs and database")] public string StoragePath { get; set; } } diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index f66754a0e..d92650d85 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -57,7 +57,6 @@ public Startup(IHostingEnvironment env) config = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Logs", "log-{Date}.txt")) - .WriteTo.SQLite("Ombi.db", "Logs", LogEventLevel.Debug) .CreateLogger(); } else @@ -65,7 +64,6 @@ public Startup(IHostingEnvironment env) config = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.RollingFile(Path.Combine(StoragePath.StoragePath, "Logs", "log-{Date}.txt")) - .WriteTo.SQLite(Path.Combine(StoragePath.StoragePath, "Ombi.db"), "Logs", LogEventLevel.Debug) .CreateLogger(); } Log.Logger = config; diff --git a/src/Ombi/appsettings.Development.json b/src/Ombi/appsettings.Development.json index fa8ce71a9..6fa76219e 100644 --- a/src/Ombi/appsettings.Development.json +++ b/src/Ombi/appsettings.Development.json @@ -2,9 +2,9 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Default": "Trace", + "System": "Trace", + "Microsoft": "Warning" } } } diff --git a/src/Ombi/appsettings.json b/src/Ombi/appsettings.json index e2534496b..ed9a1b88a 100644 --- a/src/Ombi/appsettings.json +++ b/src/Ombi/appsettings.json @@ -4,7 +4,8 @@ "LogLevel": { "Default": "Debug", "System": "Debug", - "Microsoft": "None" + "Microsoft": "None", + "Hangfire": "None" } }, "ApplicationSettings": { diff --git a/src/Ombi/package-lock.json b/src/Ombi/package-lock.json index 43ed2887b..71c8c5489 100644 --- a/src/Ombi/package-lock.json +++ b/src/Ombi/package-lock.json @@ -5140,7 +5140,6 @@ "resolved": "https://registry.npmjs.org/npm/-/npm-5.6.0.tgz", "integrity": "sha512-mt839mCsI5hzdBJLf1iRBwt610P35iUfvqLVuL7VFdanUwRBAmGtbsjdGIuzegplR95xx+fTHE0vBMuMJp1sLQ==", "requires": { - "JSONStream": "1.3.1", "abbrev": "1.1.1", "ansi-regex": "3.0.0", "ansicolors": "0.3.2", @@ -5175,6 +5174,7 @@ "ini": "1.3.4", "init-package-json": "1.10.1", "is-cidr": "1.0.0", + "JSONStream": "1.3.1", "lazy-property": "1.0.0", "libnpx": "9.7.1", "lockfile": "1.0.3", @@ -5248,24 +5248,6 @@ "write-file-atomic": "2.1.0" }, "dependencies": { - "JSONStream": { - "version": "1.3.1", - "bundled": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - }, - "dependencies": { - "jsonparse": { - "version": "1.3.1", - "bundled": true - }, - "through": { - "version": "2.3.8", - "bundled": true - } - } - }, "abbrev": { "version": "1.1.1", "bundled": true @@ -5668,6 +5650,24 @@ } } }, + "JSONStream": { + "version": "1.3.1", + "bundled": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + }, + "dependencies": { + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + } + } + }, "lazy-property": { "version": "1.0.0", "bundled": true @@ -10843,6 +10843,14 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -10853,14 +10861,6 @@ "strip-ansi": "3.0.1" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", diff --git a/src/Ombi/wwwroot/translations/da.json b/src/Ombi/wwwroot/translations/da.json index e5185df1d..ec4b0bce5 100644 --- a/src/Ombi/wwwroot/translations/da.json +++ b/src/Ombi/wwwroot/translations/da.json @@ -73,9 +73,11 @@ "TvTab": "Tv-serier", "Suggestions": "Forslag", "NoResults": "Beklager, vi fandt ingen resultater!", - "ReleaseDate": "Udgivelsesdato", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Se på Plex", "RequestAdded": "{{title}} er anmodet med succes", + "Similar": "Similar", "Movies": { "PopularMovies": "Populære film", "UpcomingMovies": "Kommende film", @@ -109,7 +111,8 @@ "Status": "Status:", "RequestStatus": "Status for anmodning:", "Denied": " Afvist:", - "ReleaseDate": "Udgivelsesdato:", + "TheatricalRelease": "Theatrical Release: {{date}}", + "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Dato for anmodning:", "QualityOverride": "Tilsidesæt kvalitet:", "RootFolderOverride": "Tilsidesæt rodmappe:", diff --git a/src/Ombi/wwwroot/translations/de.json b/src/Ombi/wwwroot/translations/de.json index 786be8f00..df8b6a724 100644 --- a/src/Ombi/wwwroot/translations/de.json +++ b/src/Ombi/wwwroot/translations/de.json @@ -21,7 +21,7 @@ "Request": "Anfrage", "Denied": "Abgelehnt", "Approve": "Genehmigen", - "PartlyAvailable": "Partly Available", + "PartlyAvailable": "Teilweise verfügbar", "Errors": { "Validation": "Bitte überprüfen Sie die eingegebenen Werte" } @@ -73,9 +73,11 @@ "TvTab": "Serien", "Suggestions": "Vorschläge", "NoResults": "Es tut uns leid, wir haben keine Ergebnisse gefunden!", - "ReleaseDate": "Veröffentlichungsdatum", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "In Plex anschauen", "RequestAdded": "Anfrage für {{title}} wurde erfolgreich hinzugefügt", + "Similar": "Similar", "Movies": { "PopularMovies": "Beliebte Filme", "UpcomingMovies": "Kommende Filme", @@ -85,19 +87,19 @@ "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}}" + "Popular": "Beliebt", + "Trending": "im Trend", + "MostWatched": "Meist gesehen", + "MostAnticipated": "Meist erwartet", + "Results": "Ergebnisse", + "AirDate": "Veröffentlicht am:", + "AllSeasons": "Alle Staffeln", + "FirstSeason": "erste Staffel", + "LatestSeason": "aktuellste Staffel", + "Select": "Wähle...", + "SubmitRequest": "Anfrage einreichen", + "Season": "Staffel: {{seasonNumber}}", + "SelectAllInSeason": "Markiere alles in Staffel {{seasonNumber}}" } }, "Requests": { @@ -109,7 +111,8 @@ "Status": "Status:", "RequestStatus": "Anfrage Status:", "Denied": " Abgelehnt:", - "ReleaseDate": "Veröffentlichungsdatum:", + "TheatricalRelease": "Theatrical Release: {{date}}", + "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Datum der Anfrage:", "QualityOverride": "Qualitäts Überschreiben:", "RootFolderOverride": "Stammverzeichnis Überschreiben:", @@ -125,7 +128,7 @@ "GridStatus": "Status", "ReportIssue": "Problem melden", "Filter": "Filter", - "SeasonNumberHeading": "Season: {seasonNumber}" + "SeasonNumberHeading": "Staffel: {seasonNumber}" }, "Issues": { "Title": "Probleme", diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 160916bfa..c6660b9c0 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -77,9 +77,11 @@ "TvTab": "TV Shows", "Suggestions": "Suggestions", "NoResults": "Sorry, we didn't find any results!", - "ReleaseDate": "Release Date", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease":"Theatrical Release: {{date}}", "ViewOnPlex": "View On Plex", "RequestAdded": "Request for {{title}} has been added successfully", + "Similar":"Similar", "Movies": { "PopularMovies": "Popular Movies", "UpcomingMovies": "Upcoming Movies", @@ -114,7 +116,8 @@ "Status": "Status:", "RequestStatus": "Request status:", "Denied": " Denied:", - "ReleaseDate": "Release Date:", + "TheatricalRelease": "Theatrical Release: {{date}}", + "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Request Date:", "QualityOverride": "Quality Override:", "RootFolderOverride": "Root Folder Override:", diff --git a/src/Ombi/wwwroot/translations/es.json b/src/Ombi/wwwroot/translations/es.json index a3c4ebd39..6b2f87874 100644 --- a/src/Ombi/wwwroot/translations/es.json +++ b/src/Ombi/wwwroot/translations/es.json @@ -73,9 +73,11 @@ "TvTab": "Series", "Suggestions": "Sugerencias", "NoResults": "¡Lo sentimos, no encontramos ningún resultado!", - "ReleaseDate": "Fecha de estreno", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Ver en Plex", "RequestAdded": "La solicitud de {{title}} se ha agregado con éxito", + "Similar": "Similar", "Movies": { "PopularMovies": "Películas populares", "UpcomingMovies": "Próximas películas", @@ -109,7 +111,8 @@ "Status": "Estado:", "RequestStatus": "Estado de la solicitud:", "Denied": " Denegado:", - "ReleaseDate": "Fecha de estreno:", + "TheatricalRelease": "Theatrical Release: {{date}}", + "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Fecha de solicitud:", "QualityOverride": "Sobreescribir calidad:", "RootFolderOverride": "Sobreescribir carpeta raíz:", diff --git a/src/Ombi/wwwroot/translations/fr.json b/src/Ombi/wwwroot/translations/fr.json index bf1126ea3..983763a08 100644 --- a/src/Ombi/wwwroot/translations/fr.json +++ b/src/Ombi/wwwroot/translations/fr.json @@ -73,9 +73,11 @@ "TvTab": "TV", "Suggestions": "Suggestions", "NoResults": "Désolé, nous n'avons trouvé aucun résultat !", - "ReleaseDate": "Date de sortie", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Regarder sur Plex", "RequestAdded": "La demande pour {{title}} a été ajoutée avec succès", + "Similar": "Similar", "Movies": { "PopularMovies": "Films populaires", "UpcomingMovies": "Films à venir", @@ -109,7 +111,8 @@ "Status": "Statut :", "RequestStatus": "Statut de la demande :", "Denied": " Refusé :", - "ReleaseDate": "Date de sortie :", + "TheatricalRelease": "Theatrical Release: {{date}}", + "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Date de la demande :", "QualityOverride": "Remplacement de la qualité :", "RootFolderOverride": "Remplacement du répertoire racine :", diff --git a/src/Ombi/wwwroot/translations/it.json b/src/Ombi/wwwroot/translations/it.json index 327eddf63..2081ce2a1 100644 --- a/src/Ombi/wwwroot/translations/it.json +++ b/src/Ombi/wwwroot/translations/it.json @@ -73,9 +73,11 @@ "TvTab": "Serie TV", "Suggestions": "Suggerimenti", "NoResults": "Ci dispiace, non abbiamo trovato alcun risultato!", - "ReleaseDate": "Data di rilascio", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Guarda su Plex", "RequestAdded": "La richiesta per {{title}} è stata aggiunta correttamente", + "Similar": "Similar", "Movies": { "PopularMovies": "Film popolari", "UpcomingMovies": "Film in arrivo", @@ -109,7 +111,8 @@ "Status": "Stato:", "RequestStatus": "Stato della richiesta:", "Denied": " Rifiutato:", - "ReleaseDate": "Data di rilascio:", + "TheatricalRelease": "Theatrical Release: {{date}}", + "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Data della richiesta:", "QualityOverride": "Sovrascrivi qualità:", "RootFolderOverride": "Sovrascrivi cartella principale:", diff --git a/src/Ombi/wwwroot/translations/nl.json b/src/Ombi/wwwroot/translations/nl.json index d70396d1e..a9ba6b683 100644 --- a/src/Ombi/wwwroot/translations/nl.json +++ b/src/Ombi/wwwroot/translations/nl.json @@ -12,7 +12,7 @@ "Common": { "ContinueButton": "Doorgaan", "Available": "Beschikbaar", - "NotAvailable": "Not Available", + "NotAvailable": "Niet Beschikbaar", "ProcessingRequest": "Verzoek wordt verwerkt", "PendingApproval": "Wacht op goedkeuring", "RequestDenied": "Verzoek geweigerd", @@ -21,7 +21,7 @@ "Request": "Verzoek", "Denied": "Geweigerd", "Approve": "Accepteer", - "PartlyAvailable": "Partly Available", + "PartlyAvailable": "Deels Beschikbaar", "Errors": { "Validation": "Fout: Controleer de ingevulde waardes" } @@ -47,7 +47,7 @@ "UserManagement": "Gebruikersbeheer", "Issues": "Problemen", "Donate": "Doneer!", - "DonateLibraryMaintainer": "Donate to Library Maintainer", + "DonateLibraryMaintainer": "Doneren aan bibliotheek beheerder", "DonateTooltip": "Zo heb ik mijn vrouw overtuigd dat ik Ombi mag ontwikkelen ;)", "UpdateAvailableTooltip": "Update beschikbaar!", "Settings": "Instellingen", @@ -62,9 +62,9 @@ "Italian": "Italiaans", "Danish": "Deens", "Dutch": "Nederlands", - "Norwegian": "Norwegian" + "Norwegian": "Noors" }, - "OpenMobileApp": "Open Mobile App" + "OpenMobileApp": "Open Mobiele App" }, "Search": { "Title": "Zoeken", @@ -73,9 +73,11 @@ "TvTab": "TV Series", "Suggestions": "Suggesties", "NoResults": "Sorry, we hebben geen resultaten gevonden!", - "ReleaseDate": "Releasedatum", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Bekijk op Plex", "RequestAdded": "Aanvraag voor {{title}} is succesvol toegevoegd", + "Similar": "Similar", "Movies": { "PopularMovies": "Populaire films", "UpcomingMovies": "Aankomende Films", @@ -85,19 +87,19 @@ "Trailer": "Trailer" }, "TvShows": { - "Popular": "Popular", + "Popular": "Populair", "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}}" + "MostWatched": "Meest Bekeken", + "MostAnticipated": "Meest Verwacht", + "Results": "Resultaten", + "AirDate": "Uitzenddatum:", + "AllSeasons": "Alle Seizoenen", + "FirstSeason": "Eerste Seizoen", + "LatestSeason": "Laatste Seizoen", + "Select": "Selecteer...", + "SubmitRequest": "Verzoek Indienen", + "Season": "Seizoen: {{seasonNumber}}", + "SelectAllInSeason": "Selecteer Alles in het Seizoen {{seasonNumber}}" } }, "Requests": { @@ -109,7 +111,8 @@ "Status": "Status:", "RequestStatus": "Aanvraagstatus:", "Denied": " Geweigerd:", - "ReleaseDate": "Releasedatum:", + "TheatricalRelease": "Theatrical Release: {{date}}", + "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Aanvraag Datum:", "QualityOverride": "Kwaliteit overschrijven:", "RootFolderOverride": "Hoofdmap overschrijven:", @@ -123,9 +126,9 @@ "GridTitle": "Titel", "AirDate": "Uitzenddatum", "GridStatus": "Status", - "ReportIssue": "Report Issue", + "ReportIssue": "Probleem Melden", "Filter": "Filter", - "SeasonNumberHeading": "Season: {seasonNumber}" + "SeasonNumberHeading": "Seizoen: {seasonNumber}" }, "Issues": { "Title": "Problemen", @@ -147,9 +150,9 @@ "ReportedBy": "Gerapporteerd door" }, "Filter": { - "ClearFilter": "Clear Filter", - "FilterHeaderAvailability": "Availability", + "ClearFilter": "Verwijder Filter", + "FilterHeaderAvailability": "Beschikbaarheid", "FilterHeaderRequestStatus": "Status", - "Approved": "Approved" + "Approved": "Goedgekeurd" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/no.json b/src/Ombi/wwwroot/translations/no.json index 7ed5ab363..cbaf1379a 100644 --- a/src/Ombi/wwwroot/translations/no.json +++ b/src/Ombi/wwwroot/translations/no.json @@ -73,9 +73,11 @@ "TvTab": "TV serier", "Suggestions": "Forslag", "NoResults": "Beklager, vi fant ingen resultater!", - "ReleaseDate": "Utgivelsesdato", + "DigitalDate": "Digital utgivelse: {{date}}", + "TheatricalRelease": "Kinopremiere: {{date}}", "ViewOnPlex": "Spill av på Plex", "RequestAdded": "Forespørsel om {{title}} er lagt til", + "Similar": "Lignende", "Movies": { "PopularMovies": "Populære filmer", "UpcomingMovies": "Kommende filmer", @@ -109,7 +111,8 @@ "Status": "Status:", "RequestStatus": "Status for forespørsel:", "Denied": " Avslått:", - "ReleaseDate": "Utgivelsesdato:", + "TheatricalRelease": "Kinopremiere: {{date}}", + "DigitalRelease": "Digital utgivelse: {{date}}", "RequestDate": "Dato for forespørsel:", "QualityOverride": "Overstyr kvalitet:", "RootFolderOverride": "Overstyring av rotmappe:", diff --git a/src/Ombi/wwwroot/translations/sv.json b/src/Ombi/wwwroot/translations/sv.json index e3cb0b5fc..b750b5e0c 100644 --- a/src/Ombi/wwwroot/translations/sv.json +++ b/src/Ombi/wwwroot/translations/sv.json @@ -73,9 +73,11 @@ "TvTab": "TV-serier", "Suggestions": "Förslag", "NoResults": "Tyvärr, hittade vi inte några resultat!", - "ReleaseDate": "Publiceringsdatum", + "DigitalDate": "Digital Release: {{date}}", + "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Visa på Plex", "RequestAdded": "Efterfrågan om {{title}} har lagts till", + "Similar": "Similar", "Movies": { "PopularMovies": "Populära filmer", "UpcomingMovies": "Kommande filmer", @@ -109,7 +111,8 @@ "Status": "Status:", "RequestStatus": "Status för efterfrågan:", "Denied": " Nekad:", - "ReleaseDate": "Releasedatum:", + "TheatricalRelease": "Theatrical Release: {{date}}", + "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Datum för efterfrågan:", "QualityOverride": "Kvalité överskridande:", "RootFolderOverride": "Root mapp överskridande:",