Merge pull request #2022 from tidusjar/DotNetCore

Merge recent changes into master
pull/2023/head Ombi-v3.0.2948
Jamie 6 years ago committed by GitHub
commit 13d3198a62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,68 @@
### **New Features** ### **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] - Added user request limits, We can now set the limit for a user. [tidusjar]
- Updated the UI JWT framework. [Jamie] - Updated the UI JWT framework. [Jamie]
@ -284,6 +346,268 @@
### **Fixes** ### **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] - #1659 Made the option to ignore notifcations for auto approve. [Jamie]
- New Crowdin translations (#1806) [Jamie] - New Crowdin translations (#1806) [Jamie]

@ -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.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\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"

@ -26,26 +26,29 @@ var csProj = "./src/Ombi/Ombi.csproj"; // Path to the project.csproj
var solutionFile = "Ombi.sln"; // Solution file if needed var solutionFile = "Ombi.sln"; // Solution file if needed
GitVersion versionInfo = null; GitVersion versionInfo = null;
var frameworkVer = "netcoreapp2.0";
var buildSettings = new DotNetCoreBuildSettings var buildSettings = new DotNetCoreBuildSettings
{ {
Framework = "netcoreapp2.0", Framework = frameworkVer,
Configuration = "Release", Configuration = "Release",
OutputDirectory = Directory(buildDir), OutputDirectory = Directory(buildDir),
}; };
var publishSettings = new DotNetCorePublishSettings var publishSettings = new DotNetCorePublishSettings
{ {
Framework = "netcoreapp2.0", Framework = frameworkVer,
Configuration = "Release", Configuration = "Release",
OutputDirectory = Directory(buildDir), OutputDirectory = Directory(buildDir),
}; };
var artifactsFolder = buildDir + "/netcoreapp2.0/"; var artifactsFolder = buildDir + "/"+frameworkVer+"/";
var windowsArtifactsFolder = artifactsFolder + "win10-x64/published"; 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 osxArtifactsFolder = artifactsFolder + "osx-x64/published";
var linuxArtifactsFolder = artifactsFolder + "linux-x64/published"; var linuxArtifactsFolder = artifactsFolder + "linux-x64/published";
var linuxArmArtifactsFolder = artifactsFolder + "linux-arm/published"; var linuxArmArtifactsFolder = artifactsFolder + "linux-arm/published";
var linuxArm64BitArtifactsFolder = artifactsFolder + "linux-arm64/published";
@ -104,6 +107,10 @@ Task("SetVersionInfo")
{ {
fullVer = fullVer.Replace("_",""); fullVer = fullVer.Replace("_","");
} }
if(fullVer.Contains("/"))
{
fullVer = fullVer.Replace("/","");
}
buildSettings.ArgumentCustomization = args => args.Append("/p:SemVer=" + versionInfo.AssemblySemVer); buildSettings.ArgumentCustomization = args => args.Append("/p:SemVer=" + versionInfo.AssemblySemVer);
buildSettings.ArgumentCustomization = args => args.Append("/p:FullVer=" + fullVer); buildSettings.ArgumentCustomization = args => args.Append("/p:FullVer=" + fullVer);
@ -161,35 +168,38 @@ Task("Package")
GZipCompress(osxArtifactsFolder, artifactsFolder + "osx.tar.gz"); GZipCompress(osxArtifactsFolder, artifactsFolder + "osx.tar.gz");
GZipCompress(linuxArtifactsFolder, artifactsFolder + "linux.tar.gz"); GZipCompress(linuxArtifactsFolder, artifactsFolder + "linux.tar.gz");
GZipCompress(linuxArmArtifactsFolder, artifactsFolder + "linux-arm.tar.gz"); GZipCompress(linuxArmArtifactsFolder, artifactsFolder + "linux-arm.tar.gz");
//GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz");
}); });
Task("Publish") Task("Publish")
.IsDependentOn("PrePublish") .IsDependentOn("PrePublish")
.IsDependentOn("Publish-Windows") .IsDependentOn("Publish-Windows")
.IsDependentOn("Publish-Windows-32bit")
.IsDependentOn("Publish-OSX") .IsDependentOn("Publish-OSX")
.IsDependentOn("Publish-Linux") .IsDependentOn("Publish-Linux")
.IsDependentOn("Publish-Linux-ARM") .IsDependentOn("Publish-Linux-ARM")
//.IsDependentOn("Publish-Linux-ARM-64Bit")
.IsDependentOn("Package"); .IsDependentOn("Package");
Task("Publish-Windows") Task("Publish-Windows")
.Does(() => .Does(() =>
{ {
publishSettings.Runtime = "win10-x64"; 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); 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); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });
Task("Publish-Windows-32bit") Task("Publish-Windows-32bit")
.Does(() => .Does(() =>
{ {
publishSettings.Runtime = "win10-x32"; publishSettings.Runtime = "win10-x86";
publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/win10-x32/published"); publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/win10-x86/published");
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); 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); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });
@ -197,10 +207,10 @@ Task("Publish-OSX")
.Does(() => .Does(() =>
{ {
publishSettings.Runtime = "osx-x64"; 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); 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); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });
@ -208,10 +218,10 @@ Task("Publish-Linux")
.Does(() => .Does(() =>
{ {
publishSettings.Runtime = "linux-x64"; 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); 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); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });
@ -219,12 +229,25 @@ Task("Publish-Linux-ARM")
.Does(() => .Does(() =>
{ {
publishSettings.Runtime = "linux-arm"; 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); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
CopyFile( CopyFile(
buildDir + "/netcoreapp2.0/linux-arm/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-arm64/Swagger.xml",
buildDir + "/netcoreapp2.0/linux-arm/published/Swagger.xml"); buildDir + "/"+frameworkVer+"/linux-arm64/published/Swagger.xml");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });

@ -0,0 +1,9 @@
using System;
namespace Ombi.Api.Radarr
{
public class CommandResult
{
public string name { get; set; }
}
}

@ -10,6 +10,9 @@ namespace Ombi.Api.Radarr
Task<List<RadarrProfile>> GetProfiles(string apiKey, string baseUrl); Task<List<RadarrProfile>> GetProfiles(string apiKey, string baseUrl);
Task<List<RadarrRootFolder>> GetRootFolders(string apiKey, string baseUrl); Task<List<RadarrRootFolder>> GetRootFolders(string apiKey, string baseUrl);
Task<SystemStatus> SystemStatus(string apiKey, string baseUrl); Task<SystemStatus> SystemStatus(string apiKey, string baseUrl);
Task<MovieResponse> GetMovie(int id, string apiKey, string baseUrl);
Task<MovieResponse> UpdateMovie(MovieResponse movie, string apiKey, string baseUrl);
Task<bool> MovieSearch(int[] movieIds, string apiKey, string baseUrl);
Task<RadarrAddMovieResponse> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath,string apiKey, string baseUrl, bool searchNow, string minimumAvailability); Task<RadarrAddMovieResponse> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath,string apiKey, string baseUrl, bool searchNow, string minimumAvailability);
} }
} }

@ -17,10 +17,7 @@ namespace Ombi.Api.Radarr.Models
public bool monitored { get; set; } public bool monitored { get; set; }
public int tmdbId { get; set; } public int tmdbId { get; set; }
public List<string> images { get; set; } public List<string> images { get; set; }
public string cleanTitle { get; set; }
public string imdbId { get; set; }
public string titleSlug { get; set; } public string titleSlug { get; set; }
public int id { get; set; }
public int year { get; set; } public int year { get; set; }
public string minimumAvailability { get; set; } public string minimumAvailability { get; set; }
} }

@ -3,19 +3,10 @@
public class RadarrError public class RadarrError
{ {
public string message { get; set; } public string message { get; set; }
public string description { get; set; }
} }
public class RadarrErrorResponse public class RadarrErrorResponse
{ {
public string propertyName { get; set; }
public string errorMessage { 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; }
} }
} }

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -53,6 +52,23 @@ namespace Ombi.Api.Radarr
return await Api.Request<List<MovieResponse>>(request); return await Api.Request<List<MovieResponse>>(request);
} }
public async Task<MovieResponse> GetMovie(int id, string apiKey, string baseUrl)
{
var request = new Request($"/api/movie/{id}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<MovieResponse>(request);
}
public async Task<MovieResponse> 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<MovieResponse>(request);
}
public async Task<RadarrAddMovieResponse> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow, string minimumAvailability) public async Task<RadarrAddMovieResponse> 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); var request = new Request("/api/movie", baseUrl, HttpMethod.Post);
@ -66,7 +82,7 @@ namespace Ombi.Api.Radarr
titleSlug = title, titleSlug = title,
monitored = true, monitored = true,
year = year, year = year,
minimumAvailability = minimumAvailability, minimumAvailability = minimumAvailability
}; };
if (searchNow) if (searchNow)
@ -81,9 +97,9 @@ namespace Ombi.Api.Radarr
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(options); request.AddJsonBody(options);
var response = await Api.RequestContent(request);
try try
{ {
var response = await Api.RequestContent(request);
if (response.Contains("\"message\":")) if (response.Contains("\"message\":"))
{ {
var error = JsonConvert.DeserializeObject<RadarrError>(response); var error = JsonConvert.DeserializeObject<RadarrError>(response);
@ -98,11 +114,24 @@ namespace Ombi.Api.Radarr
} }
catch (JsonSerializationException jse) 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; return null;
} }
public async Task<bool> MovieSearch(int[] movieIds, string apiKey, string baseUrl)
{
var result = await Command(apiKey, baseUrl, new { name = "MoviesSearch", movieIds });
return result != null;
}
private async Task<CommandResult> 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<CommandResult>(request);
}
/// <summary> /// <summary>
/// Adds the required headers and also the authorization header /// Adds the required headers and also the authorization header

@ -8,11 +8,6 @@ namespace Ombi.Api.Sonarr.Models
public class CommandResult public class CommandResult
{ {
public string name { get; set; } 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; }
} }
} }

@ -66,8 +66,10 @@ namespace Ombi.Api.Sonarr
var request = new Request($"/api/series/{id}", baseUrl, HttpMethod.Get); var request = new Request($"/api/series/{id}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
var result = await Api.Request<SonarrSeries>(request); var result = await Api.Request<SonarrSeries>(request);
result.seasons.ToList().RemoveAt(0); if (result?.seasons?.Length > 0)
{
result?.seasons?.ToList().RemoveAt(0);
}
return result; return result;
} }

@ -17,5 +17,7 @@ namespace Ombi.Core
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies(); Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId); Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId);
Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId);
} }
} }

@ -10,9 +10,13 @@ namespace Ombi.Core.Engine.Interfaces
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm); Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm);
Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid); Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid); Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Popular(); Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Anticipated(); Task<IEnumerable<SearchTvShowViewModel>> Popular();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatches(); Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Trending(); Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree();
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree();
Task<IEnumerable<SearchTvShowViewModel>> Trending();
} }
} }

@ -11,6 +11,7 @@ using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces; using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
@ -45,7 +46,7 @@ namespace Ombi.Core.Engine
/// <returns></returns> /// <returns></returns>
public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model) public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model)
{ {
var movieInfo = await MovieApi.GetMovieInformation(model.TheMovieDbId); var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId);
if (movieInfo == null || movieInfo.Id == 0) if (movieInfo == null || movieInfo.Id == 0)
{ {
return new RequestEngineResult return new RequestEngineResult
@ -78,6 +79,9 @@ namespace Ombi.Core.Engine
Background = movieInfo.BackdropPath 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(); var ruleResults = (await RunRequestRules(requestModel)).ToList();
if (ruleResults.Any(x => !x.Success)) if (ruleResults.Any(x => !x.Success))
{ {

@ -40,7 +40,7 @@ namespace Ombi.Core.Engine
/// <returns></returns> /// <returns></returns>
public async Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId) public async Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId)
{ {
var movieInfo = await MovieApi.GetMovieInformationWithVideo(theMovieDbId); var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId);
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo); var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo);
return await ProcessSingleMovie(viewMovie, true); return await ProcessSingleMovie(viewMovie, true);
@ -63,6 +63,22 @@ namespace Ombi.Core.Engine
return null; return null;
} }
/// <summary>
/// Get similar movies to the id passed in
/// </summary>
/// <param name="theMovieDbId"></param>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> 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;
}
/// <summary> /// <summary>
/// Gets popular movies. /// Gets popular movies.
/// </summary> /// </summary>
@ -141,6 +157,8 @@ namespace Ombi.Core.Engine
var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id); var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id);
viewMovie.Id = showInfo.Id; // TheMovieDbId viewMovie.Id = showInfo.Id; // TheMovieDbId
viewMovie.ImdbId = showInfo.ImdbId; 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(); viewMovie.TheMovieDbId = viewMovie.Id.ToString();

@ -122,33 +122,60 @@ namespace Ombi.Core.Engine
return ParseIntoTreeNode(result); return ParseIntoTreeNode(result);
} }
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Popular() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree()
{ {
var result = await MemCache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); var result = await MemCache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result); var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList(); return processed.Select(ParseIntoTreeNode).ToList();
} }
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Anticipated() public async Task<IEnumerable<SearchTvShowViewModel>> 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<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree()
{ {
var result = await MemCache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12)); 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(); return processed.Select(ParseIntoTreeNode).ToList();
} }
public async Task<IEnumerable<SearchTvShowViewModel>> 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<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatches() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree()
{ {
var result = await MemCache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12)); var result = await MemCache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result); var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList(); return processed.Select(ParseIntoTreeNode).ToList();
} }
public async Task<IEnumerable<SearchTvShowViewModel>> 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<IEnumerable<TreeNode<SearchTvShowViewModel>>> Trending() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree()
{ {
var result = await MemCache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12)); var result = await MemCache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result); var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList(); return processed.Select(ParseIntoTreeNode).ToList();
} }
public async Task<IEnumerable<SearchTvShowViewModel>> 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<SearchTvShowViewModel> ParseIntoTreeNode(SearchTvShowViewModel result) private static TreeNode<SearchTvShowViewModel> ParseIntoTreeNode(SearchTvShowViewModel result)
{ {
return new TreeNode<SearchTvShowViewModel> return new TreeNode<SearchTvShowViewModel>

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Store.Entities; using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search namespace Ombi.Core.Models.Search
@ -25,5 +26,7 @@ namespace Ombi.Core.Models.Search
public int RootPathOverride { get; set; } public int RootPathOverride { get; set; }
public int QualityOverride { get; set; } public int QualityOverride { get; set; }
public override RequestType Type => RequestType.Movie; public override RequestType Type => RequestType.Movie;
public ReleaseDatesDto ReleaseDates { get; set; }
public DateTime? DigitalReleaseDate { get; set; }
} }
} }

@ -48,7 +48,7 @@ namespace Ombi.Core.Senders
var dogSettings = await DogNzbSettings.GetSettingsAsync(); var dogSettings = await DogNzbSettings.GetSettingsAsync();
if (dogSettings.Enabled) if (dogSettings.Enabled)
{ {
await SendToDogNzb(model,dogSettings); await SendToDogNzb(model, dogSettings);
return new SenderResult return new SenderResult
{ {
Success = true, Success = true,
@ -95,18 +95,40 @@ namespace Ombi.Core.Senders
} }
var rootFolderPath = model.RootPathOverride <= 0 ? settings.DefaultRootPath : await RadarrRootPath(model.RootPathOverride, settings); 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); var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year,
return new SenderResult { Success = false, Message = result.Error.message, Sent = false }; 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<string> RadarrRootPath(int overrideId, RadarrSettings settings) private async Task<string> RadarrRootPath(int overrideId, RadarrSettings settings)

@ -42,7 +42,19 @@ namespace Ombi.Mapping.Profiles
.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.runtime)) .ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.runtime))
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status)) .ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status))
.ForMember(dest => dest.Tagline, opts => opts.MapFrom(src => src.tagline)) .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<ReleaseDates, ReleaseDatesDto>()
.ForMember(x => x.Results, o => o.MapFrom(src => src.results));
CreateMap<ReleaseResults, ReleaseResultsDto>()
.ForMember(x => x.ReleaseDate, o => o.MapFrom(s => s.release_dates))
.ForMember(x => x.IsoCode, o => o.MapFrom(s => s.iso_3166_1));
CreateMap<ReleaseDate, ReleaseDateDto>()
.ForMember(x => x.ReleaseDate, o => o.MapFrom(s => s.release_date))
.ForMember(x => x.Type, o => o.MapFrom(s => s.Type));
CreateMap<Genre, GenreDto>(); CreateMap<Genre, GenreDto>();
CreateMap<MovieSearchResult, SearchMovieViewModel>().ReverseMap(); CreateMap<MovieSearchResult, SearchMovieViewModel>().ReverseMap();

@ -106,14 +106,23 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
// Let's download the correct zip // Let's download the correct zip
var desc = RuntimeInformation.OSDescription; 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; Downloads download;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
Logger.LogDebug(LoggingEvents.Updater, "We are 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)) else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{ {
@ -123,13 +132,16 @@ namespace Ombi.Schedule.Jobs.Ombi
else else
{ {
Logger.LogDebug(LoggingEvents.Updater, "We are linux"); 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 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) if (download == null)
@ -196,11 +208,14 @@ namespace Ombi.Schedule.Jobs.Ombi
var updaterFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), var updaterFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"TempUpdate", $"Ombi.Updater{updaterExtension}"); "TempUpdate", $"Ombi.Updater{updaterExtension}");
// Make sure the file is an executable
ExecLinuxCommand($"chmod +x {updaterFile}");
// There must be an update // There must be an update
var start = new ProcessStartInfo var start = new ProcessStartInfo
{ {
UseShellExecute = false, UseShellExecute = true,
CreateNoWindow = false, CreateNoWindow = false, // Ignored if UseShellExecute is set to true
FileName = updaterFile, FileName = updaterFile,
Arguments = GetArgs(settings), Arguments = GetArgs(settings),
WorkingDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "TempUpdate"), WorkingDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "TempUpdate"),
@ -244,24 +259,16 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" "); sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" ");
} }
var sb2 = new StringBuilder(); var sb2 = new StringBuilder();
var hasStartupArgs = false;
if (url?.Value.HasValue() ?? false) if (url?.Value.HasValue() ?? false)
{ {
hasStartupArgs = true; sb2.Append($" --host {url.Value}");
sb2.Append(url.Value);
} }
if (storage?.Value.HasValue() ?? false) if (storage?.Value.HasValue() ?? false)
{ {
hasStartupArgs = true; sb2.Append($" --storage {storage.Value}");
sb2.Append(storage.Value);
}
if (hasStartupArgs)
{
sb.Append($"--startupArgs {sb2.ToString()}");
} }
return sb.ToString(); return sb.ToString();
//return string.Join(" ", currentLocation, processName, url?.Value ?? string.Empty, storage?.Value ?? string.Empty);
} }
private void RunScript(UpdateSettings settings, string downloadUrl) private void RunScript(UpdateSettings settings, string downloadUrl)
@ -367,5 +374,30 @@ namespace Ombi.Schedule.Jobs.Ombi
Directory.Delete(path, true); 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();
}
} }
} }

@ -67,6 +67,16 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString()); 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 season in child.SeasonRequests)
{ {
foreach (var episode in season.Episodes) foreach (var episode in season.Episodes)

@ -30,6 +30,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Hangfire; using Hangfire;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.Plex; using Ombi.Api.Plex;
using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models;
@ -81,7 +82,7 @@ namespace Ombi.Schedule.Jobs.Plex
} }
catch (Exception e) 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"); Logger.LogInformation("Starting EP Cacher");
@ -120,20 +121,39 @@ namespace Ombi.Schedule.Jobs.Plex
PlexContentId = show.ratingKey PlexContentId = show.ratingKey
}); });
} }
// Do we already have this item? // Do we already have this item?
// Let's try and match // 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.ReleaseYear == show.year.ToString()
&& x.Type == PlexMediaTypeEntity.Show); && x.Type == PlexMediaTypeEntity.Show);
if (existingContent == null) if (existingContent != null)
{ {
// Just check the key // Just check the key
var hasSameKey = await Repo.GetByKey(show.ratingKey); var existingKey = await Repo.GetByKey(show.ratingKey);
if (hasSameKey != null) 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 @@ namespace Ombi.Schedule.Jobs.Plex
} }
catch (Exception e) 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 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 // 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... // 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, var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
show.ratingKey); show.ratingKey);
var providerIds = PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault().guid); 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<PlexSeasonsContent>()
};
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<PlexSeasonsContent>()
};
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 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
// Let's check if we have this movie // Let's check if we have this movie
var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title try
&& 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); var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title
continue; && 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); var hasSameKey = await Repo.GetByKey(movie.ratingKey);
if (hasSameKey != null) if (hasSameKey != null)
{ {
await Repo.Delete(hasSameKey); await Repo.Delete(hasSameKey);
} }
Logger.LogInformation("Adding movie {0}", movie.title); Logger.LogInformation("Adding movie {0}", movie.title);
var metaData = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, var metaData = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
movie.ratingKey); movie.ratingKey);
var providerIds = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer.Metadata var providerIds = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer.Metadata
.FirstOrDefault() .FirstOrDefault()
.guid); .guid);
var item = new PlexServerContent var item = new PlexServerContent
{ {
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Key = movie.ratingKey, Key = movie.ratingKey,
ReleaseYear = movie.year.ToString(), ReleaseYear = movie.year.ToString(),
Type = PlexMediaTypeEntity.Movie, Type = PlexMediaTypeEntity.Movie,
Title = movie.title, Title = movie.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey), Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey),
Seasons = new List<PlexSeasonsContent>(), Seasons = new List<PlexSeasonsContent>(),
Quality = movie.Media?.FirstOrDefault()?.videoResolution ?? string.Empty Quality = movie.Media?.FirstOrDefault()?.videoResolution ?? string.Empty
}; };
if (providerIds.Type == ProviderType.ImdbId) if (providerIds.Type == ProviderType.ImdbId)
{ {
item.ImdbId = providerIds.ImdbId; item.ImdbId = providerIds.ImdbId;
} }
if (providerIds.Type == ProviderType.TheMovieDbId) if (providerIds.Type == ProviderType.TheMovieDbId)
{ {
item.TheMovieDbId = providerIds.TheMovieDb; 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) if (contentToAdd.Count > 500)

@ -48,8 +48,9 @@ namespace Ombi.Schedule.Jobs.Plex
foreach (var server in s.Servers) foreach (var server in s.Servers)
{ {
await Cache(server); await Cache(server);
BackgroundJob.Enqueue(() => _availabilityChecker.Start());
} }
BackgroundJob.Enqueue(() => _availabilityChecker.Start());
} }
catch (Exception e) catch (Exception e)
{ {
@ -127,7 +128,10 @@ namespace Ombi.Schedule.Jobs.Plex
private async Task ProcessEpsiodes(PlexContainer episodes) private async Task ProcessEpsiodes(PlexContainer episodes)
{ {
var ep = new HashSet<PlexEpisode>(); var ep = new HashSet<PlexEpisode>();
try
{
foreach (var episode in episodes?.MediaContainer?.Metadata ?? new Metadata[]{}) 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? // 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 @@ namespace Ombi.Schedule.Jobs.Plex
// continue; // 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 ep.Add(new PlexEpisode
{ {
EpisodeNumber = episode.index, EpisodeNumber = episode.index,
@ -154,6 +177,12 @@ namespace Ombi.Schedule.Jobs.Plex
} }
await _repo.AddRange(ep); await _repo.AddRange(ep);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
} }
private bool Validate(PlexServers settings) private bool Validate(PlexServers settings)

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -9,6 +10,7 @@ using Octokit;
using Ombi.Api; using Ombi.Api;
using Ombi.Api.Service; using Ombi.Api.Service;
using Ombi.Core.Processor; using Ombi.Core.Processor;
using Ombi.Helpers;
namespace Ombi.Schedule.Processor namespace Ombi.Schedule.Processor
{ {
@ -44,7 +46,8 @@ namespace Ombi.Schedule.Processor
if (masterBranch) if (masterBranch)
{ {
latestRelease = doc.DocumentNode.Descendants("h2") 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 else
{ {
@ -173,10 +176,13 @@ namespace Ombi.Schedule.Processor
var releases = await client.Repository.Release.GetAll("tidusjar", "ombi"); var releases = await client.Repository.Release.GetAll("tidusjar", "ombi");
var latest = releases.FirstOrDefault(x => x.TagName == releaseTag); var latest = releases.FirstOrDefault(x => x.TagName == releaseTag);
if (latest.Name.Contains("V2", CompareOptions.IgnoreCase))
{
latest = null;
}
if (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) foreach (var item in latest.Assets)
{ {

@ -10,11 +10,14 @@ namespace Ombi.Store.Entities.Requests
public string Overview { get; set; } public string Overview { get; set; }
public string PosterPath { get; set; } public string PosterPath { get; set; }
public DateTime ReleaseDate { get; set; } public DateTime ReleaseDate { get; set; }
public DateTime? DigitalReleaseDate { get; set; }
public string Status { get; set; } public string Status { get; set; }
public string Background { get; set; } public string Background { get; set; }
[NotMapped] [NotMapped]
public bool Released => DateTime.UtcNow > ReleaseDate; public bool Released => DateTime.UtcNow > ReleaseDate;
[NotMapped]
public bool DigitalRelease => DigitalReleaseDate.HasValue && DigitalReleaseDate > DateTime.UtcNow;
} }
} }

@ -0,0 +1,916 @@
// <auto-generated />
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<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ProviderId");
b.Property<string>("Title");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

@ -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<DateTime>(
name: "DigitalReleaseDate",
table: "MovieRequests",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DigitalReleaseDate",
table: "MovieRequests");
}
}
}

@ -553,6 +553,8 @@ namespace Ombi.Store.Migrations
b.Property<string>("DeniedReason"); b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId"); b.Property<string>("ImdbId");
b.Property<int?>("IssueId"); b.Property<int?>("IssueId");

@ -19,5 +19,8 @@ namespace Ombi.Store.Repository
Task AddRange(IEnumerable<PlexEpisode> content); Task AddRange(IEnumerable<PlexEpisode> content);
IEnumerable<PlexServerContent> GetWhereContentByCustom(Expression<Func<PlexServerContent, bool>> predicate); IEnumerable<PlexServerContent> GetWhereContentByCustom(Expression<Func<PlexServerContent, bool>> predicate);
Task<PlexServerContent> GetFirstContentByCustom(Expression<Func<PlexServerContent, bool>> predicate); Task<PlexServerContent> GetFirstContentByCustom(Expression<Func<PlexServerContent, bool>> predicate);
Task DeleteEpisode(PlexEpisode content);
void DeleteWithoutSave(PlexServerContent content);
void DeleteWithoutSave(PlexEpisode content);
} }
} }

@ -103,12 +103,29 @@ namespace Ombi.Store.Repository
return Db.PlexEpisode.Include(x => x.Series).AsQueryable(); 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<PlexEpisode> Add(PlexEpisode content) public async Task<PlexEpisode> Add(PlexEpisode content)
{ {
await Db.PlexEpisode.AddAsync(content); await Db.PlexEpisode.AddAsync(content);
await Db.SaveChangesAsync(); await Db.SaveChangesAsync();
return content; return content;
} }
public async Task DeleteEpisode(PlexEpisode content)
{
Db.PlexEpisode.Remove(content);
await Db.SaveChangesAsync();
}
public async Task<PlexEpisode> GetEpisodeByKey(int key) public async Task<PlexEpisode> GetEpisodeByKey(int key)
{ {
return await Db.PlexEpisode.FirstOrDefaultAsync(x => x.Key == key); return await Db.PlexEpisode.FirstOrDefaultAsync(x => x.Key == key);

@ -8,11 +8,12 @@ namespace Ombi.Api.TheMovieDb
public interface IMovieDbApi public interface IMovieDbApi
{ {
Task<MovieResponseDto> GetMovieInformation(int movieId); Task<MovieResponseDto> GetMovieInformation(int movieId);
Task<MovieResponseDto> GetMovieInformationWithVideo(int movieId); Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId);
Task<List<MovieSearchResult>> NowPlaying(); Task<List<MovieSearchResult>> NowPlaying();
Task<List<MovieSearchResult>> PopularMovies(); Task<List<MovieSearchResult>> PopularMovies();
Task<List<MovieSearchResult>> SearchMovie(string searchTerm); Task<List<MovieSearchResult>> SearchMovie(string searchTerm);
Task<List<MovieSearchResult>> TopRated(); Task<List<MovieSearchResult>> TopRated();
Task<List<MovieSearchResult>> Upcoming(); Task<List<MovieSearchResult>> Upcoming();
Task<List<MovieSearchResult>> SimilarMovies(int movieId);
} }
} }

@ -24,6 +24,10 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System;
using System.Collections.Generic;
namespace Ombi.TheMovieDbApi.Models namespace Ombi.TheMovieDbApi.Models
{ {
@ -54,5 +58,26 @@ namespace Ombi.TheMovieDbApi.Models
public bool video { get; set; } public bool video { get; set; }
public float vote_average { get; set; } public float vote_average { get; set; }
public int vote_count { get; set; } public int vote_count { get; set; }
public ReleaseDates release_dates { get; set; }
}
public class ReleaseDates
{
public List<ReleaseResults> results { get; set; }
}
public class ReleaseResults
{
public string iso_3166_1 { get; set; }
public List<ReleaseDate> 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; }
} }
} }

@ -1,4 +1,7 @@
namespace Ombi.Api.TheMovieDb.Models using System;
using System.Collections.Generic;
namespace Ombi.Api.TheMovieDb.Models
{ {
public class MovieResponseDto public class MovieResponseDto
{ {
@ -23,5 +26,33 @@
public bool Video { get; set; } public bool Video { get; set; }
public float VoteAverage { get; set; } public float VoteAverage { get; set; }
public int VoteCount { get; set; } public int VoteCount { get; set; }
public ReleaseDatesDto ReleaseDates { get; set; }
}
public class ReleaseDatesDto
{
public List<ReleaseResultsDto> Results { get; set; }
}
public class ReleaseResultsDto
{
public string IsoCode { get; set; }
public List<ReleaseDateDto> 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
} }
} }

@ -30,11 +30,20 @@ namespace Ombi.Api.TheMovieDb
return Mapper.Map<MovieResponseDto>(result); return Mapper.Map<MovieResponseDto>(result);
} }
public async Task<MovieResponseDto> GetMovieInformationWithVideo(int movieId) public async Task<List<MovieSearchResult>> 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<TheMovieDbContainer<SearchResult>>(request);
return Mapper.Map<List<MovieSearchResult>>(result.results);
}
public async Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId)
{ {
var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get); var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get);
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); 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<MovieResponse>(request); var result = await Api.Request<MovieResponse>(request);
return Mapper.Map<MovieResponseDto>(result); return Mapper.Map<MovieResponseDto>(result);
} }

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading; using System.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -33,27 +34,27 @@ namespace Ombi.Updater
} }
// Make sure the process has been killed // 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($"[{proc.Id}] - {proc.Name} - Path: {proc.StartPath}");
_log.LogDebug("Found another process called {0}, KILLING!", opt.ProcessName); opt.OmbiProcessId = proc.Id;
var proc = p.FindProcessByName(opt.ProcessName).FirstOrDefault(); p.Kill(opt);
if (proc != null)
{
_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"); _log.LogDebug("Starting ombi");
var fileName = "Ombi.exe"; var fileName = "Ombi.exe";
@ -71,19 +72,29 @@ namespace Ombi.Updater
Arguments = $"/C net start \"{options.WindowsServiceName}\"" Arguments = $"/C net start \"{options.WindowsServiceName}\""
}; };
using (var process = new Process{StartInfo = startInfo}) using (var process = new Process { StartInfo = startInfo })
{ {
process.Start(); process.Start();
} }
} }
else 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 var start = new ProcessStartInfo
{ {
UseShellExecute = false, UseShellExecute = false,
FileName = Path.Combine(options.ApplicationPath, fileName), FileName = Path.Combine(options.ApplicationPath, fileName),
WorkingDirectory = options.ApplicationPath, WorkingDirectory = options.ApplicationPath,
Arguments = options.StartupArgs Arguments = startupArgsBuilder.ToString()
}; };
using (var proc = new Process { StartInfo = start }) using (var proc = new Process { StartInfo = start })
{ {

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RuntimeIdentifiers>win10-x64;win10-x32;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64;</RuntimeIdentifiers>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion> <AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion> <FileVersion>3.0.0.0</FileVersion>

@ -1,14 +1,10 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using CommandLine; using CommandLine;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Serilog; using Serilog;
using Serilog.Events;
using ILogger = Serilog.ILogger;
namespace Ombi.Updater namespace Ombi.Updater
{ {
@ -78,8 +74,10 @@ namespace Ombi.Updater
public string ApplicationPath { get; set; } public string ApplicationPath { get; set; }
[Option("processId", Required = false)] [Option("processId", Required = false)]
public int OmbiProcessId { get; set; } public int OmbiProcessId { get; set; }
[Option("startupArgs", Required = false)] [Option("host", Required = false)]
public string StartupArgs { get; set; } public string Host { get; set; }
[Option("storage", Required = false)]
public string Storage { get; set; }
[Option("windowsServiceName", Required = false)] [Option("windowsServiceName", Required = false)]
public string WindowsServiceName { get; set; } public string WindowsServiceName { get; set; }

@ -34,6 +34,7 @@ export interface IMovieRequests extends IFullBaseRequest {
theMovieDbId: number; theMovieDbId: number;
rootPathOverride: number; rootPathOverride: number;
qualityOverride: number; qualityOverride: number;
digitalReleaseDate: Date;
rootPathOverrideTitle: string; rootPathOverrideTitle: string;
qualityOverrideTitle: string; qualityOverrideTitle: string;

@ -22,6 +22,7 @@
available: boolean; available: boolean;
plexUrl: string; plexUrl: string;
quality: string; quality: string;
digitalReleaseDate: Date;
// for the UI // for the UI
requestProcessing: boolean; requestProcessing: boolean;

@ -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; position: fixed;
top: 50%; top: 50%;
left: 50%; left: 50%;

@ -66,7 +66,8 @@
</div> </div>
<div id="releaseDate">{{ 'Requests.ReleaseDate' | translate }} {{request.releaseDate | date}}</div> <div id="releaseDate">{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}</div>
<div *ngIf="request.digitalReleaseDate" id="digitalReleaseDate">{{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}</div>
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}</div> <div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}</div>
<br /> <br />
<div *ngIf="isAdmin"> <div *ngIf="isAdmin">
@ -168,7 +169,7 @@
<p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small"> <p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small">
<h3>{{ 'Filter.Filter' | translate }}</h3> <h3>{{ 'Requests.Filter' | translate }}</h3>
<hr> <hr>
<h4>{{ 'Filter.FilterHeaderAvailability' | translate }}</h4> <h4>{{ 'Filter.FilterHeaderAvailability' | translate }}</h4>
@ -200,5 +201,5 @@
</div> </div>
<button class="btn btn-sm btn-primary-outline" (click)="clearFilter()"> <button class="btn btn-sm btn-primary-outline" (click)="clearFilter()">
<i class="fa fa-filter"></i> {{ 'Requests.ClearFilter' | translate }}</button> <i class="fa fa-filter"></i> {{ 'Filter.ClearFilter' | translate }}</button>
</p-sidebar> </p-sidebar>

@ -41,8 +41,9 @@
<a href="https://www.themoviedb.org/movie/{{result.id}}/" target="_blank"> <a href="https://www.themoviedb.org/movie/{{result.id}}/" target="_blank">
<h4>{{result.title}} ({{result.releaseDate | date: 'yyyy'}})</h4> <h4>{{result.title}} ({{result.releaseDate | date: 'yyyy'}})</h4>
</a> </a>
<span class="tags"> <span class="tags">
<span *ngIf="result.releaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.ReleaseDate' | translate }} {{result.releaseDate | date: 'dd/MM/yyyy'}}</span> <span *ngIf="result.releaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.TheatricalRelease' | translate: {date: result.releaseDate | date: 'mediumDate'} }}</span>
<span *ngIf="result.digitalReleaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | date: 'mediumDate'} }}</span>
<a *ngIf="result.homepage" href="{{result.homepage}}" id="homePageLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.HomePage'"></span></a> <a *ngIf="result.homepage" href="{{result.homepage}}" id="homePageLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.HomePage'"></span></a>
@ -79,7 +80,8 @@
<i *ngIf="result.processed && !result.requestProcessing" class="fa fa-check"></i>{{ 'Common.Request' | translate }}</button> <i *ngIf="result.processed && !result.requestProcessing" class="fa fa-check"></i>{{ 'Common.Request' | translate }}</button>
</ng-template> </ng-template>
</div> </div>
<button style="text-align: right" class="btn btn-sm btn-info-outline" (click)="similarMovies(result.id)"> <i class="fa fa-eye"></i> {{ 'Search.Similar' | translate }}</button>
<br/> <br/>
<div *ngIf="result.available"> <div *ngIf="result.available">
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a> <a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a>

@ -146,6 +146,15 @@ export class MovieSearchComponent implements OnInit {
this.issueProviderId = req.id.toString(); this.issueProviderId = req.id.toString();
} }
public similarMovies(theMovieDbId: number) {
this.clearResults();
this.searchService.similarMovies(theMovieDbId)
.subscribe(x => {
this.movieResults = x;
this.getExtraInfo();
});
}
private getExtraInfo() { private getExtraInfo() {
this.movieResults.forEach((val, index) => { this.movieResults.forEach((val, index) => {

@ -19,6 +19,9 @@ export class SearchService extends ServiceHelpers {
public searchMovie(searchTerm: string): Observable<ISearchMovieResult[]> { public searchMovie(searchTerm: string): Observable<ISearchMovieResult[]> {
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/` + searchTerm); return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/` + searchTerm);
} }
public similarMovies(theMovieDbId: number): Observable<ISearchMovieResult[]> {
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/${theMovieDbId}/similar`);
}
public popularMovies(): Observable<ISearchMovieResult[]> { public popularMovies(): Observable<ISearchMovieResult[]> {
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/Popular`); return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/Popular`);
@ -54,15 +57,15 @@ export class SearchService extends ServiceHelpers {
} }
public popularTv(): Observable<TreeNode[]> { public popularTv(): Observable<TreeNode[]> {
return this.http.get<TreeNode[]>(`${this.url}/Tv/popular`, {headers: this.headers}); return this.http.get<TreeNode[]>(`${this.url}/Tv/popular/tree`, {headers: this.headers});
} }
public mostWatchedTv(): Observable<TreeNode[]> { public mostWatchedTv(): Observable<TreeNode[]> {
return this.http.get<TreeNode[]>(`${this.url}/Tv/mostwatched`, {headers: this.headers}); return this.http.get<TreeNode[]>(`${this.url}/Tv/mostwatched/tree`, {headers: this.headers});
} }
public anticipatedTv(): Observable<TreeNode[]> { public anticipatedTv(): Observable<TreeNode[]> {
return this.http.get<TreeNode[]>(`${this.url}/Tv/anticipated`, {headers: this.headers}); return this.http.get<TreeNode[]>(`${this.url}/Tv/anticipated/tree`, {headers: this.headers});
} }
public trendingTv(): Observable<TreeNode[]> { public trendingTv(): Observable<TreeNode[]> {
return this.http.get<TreeNode[]>(`${this.url}/Tv/trending`, {headers: this.headers}); return this.http.get<TreeNode[]>(`${this.url}/Tv/trending/tree`, {headers: this.headers});
} }
} }

@ -41,7 +41,7 @@ export class OmbiComponent implements OnInit {
} }
const result = <IOmbiSettings>form.value; const result = <IOmbiSettings>form.value;
if(result.baseUrl.length > 0) { if(result.baseUrl && result.baseUrl.length > 0) {
if(!result.baseUrl.startsWith("/")) { if(!result.baseUrl.startsWith("/")) {
this.notificationService.error("Please ensure your base url starts with a '/'"); this.notificationService.error("Please ensure your base url starts with a '/'");
return; return;

@ -1,5 +1,4 @@
@import '../base.scss'; $primary-colour: #df691a;
$primary-colour: #df691a;
$primary-colour-outline: #ff761b; $primary-colour-outline: #ff761b;
$bg-colour: #333333; $bg-colour: #333333;
$bg-colour-disabled: #252424; $bg-colour-disabled: #252424;

@ -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) { @media (max-width: 48em) {
.home { .home {
padding-top: 1rem; padding-top: 1rem;
@ -841,8 +884,13 @@ textarea {
border: 1px solid $form-color-lighter; border: 1px solid $form-color-lighter;
} }
.ui-treetable tfoot td, .ui-treetable th {
text-align: left;
}
.ui-treetable tbody td { .ui-treetable tbody td {
white-space: inherit; white-space: inherit;
overflow: visible;
} }
table a:not(.btn) { table a:not(.btn) {
@ -897,49 +945,6 @@ a > h4:hover {
padding-top:15px; 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 { .searchWidth {
width: 94%; width: 94%;
} }

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web;
using AutoMapper; using AutoMapper;
using Hangfire; using Hangfire;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -472,8 +472,18 @@ namespace Ombi.Controllers
// Get the roles // Get the roles
var userRoles = await UserManager.GetRolesAsync(user); 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) 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); await UserManager.RemoveFromRoleAsync(user, role);
} }
@ -613,25 +623,54 @@ namespace Ombi.Controllers
return defaultMessage; 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 customizationSettings = await CustomizationSettings.GetSettingsAsync();
var appName = (string.IsNullOrEmpty(customizationSettings.ApplicationName) var appName = (string.IsNullOrEmpty(customizationSettings.ApplicationName)
? "Ombi" ? "Ombi"
: customizationSettings.ApplicationName); : customizationSettings.ApplicationName);
var emailSettings = await EmailSettings.GetSettingsAsync();
customizationSettings.AddToUrl("/token?token="); customizationSettings.AddToUrl("/token?token=");
var url = customizationSettings.ApplicationUrl; 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.<br/><br/>" +
$"<a href=\"https://www.plex.tv/sign-in/password-reset/\"> Reset </a>"
}, emailSettings);
}
else if (user.UserType == UserType.EmbyUser && user.IsEmbyConnect)
{ {
To = user.Email, await EmailProvider.SendAdHoc(new NotificationMessage
Subject = $"{appName} Password Reset", {
Message = $"You recently made a request to reset your {appName} account. Please click the link below to complete the process.<br/><br/>" + To = user.Email,
$"<a href=\"{url}{token}\"> Reset </a>" Subject = $"{appName} Password Reset",
}, emailSettings); Message =
$"You recently made a request to reset your {appName} account.<br/><br/>" +
$"To reset your password you need to go to <a href=\"https://emby.media/community/index.php\">Emby.Media</a> 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.<br/><br/>" +
$"<a href=\"{url}{encodedToken}\"> Reset </a>"
}, emailSettings);
}
return defaultMessage; return defaultMessage;
} }

@ -17,7 +17,7 @@ namespace Ombi.Controllers
} }
private ILogger Logger { get; } 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] [HttpPost]
public IActionResult Log([FromBody]UiLoggingModel l) public IActionResult Log([FromBody]UiLoggingModel l)

@ -61,6 +61,19 @@ namespace Ombi.Controllers
return await MovieEngine.LookupImdbInformation(theMovieDbId); return await MovieEngine.LookupImdbInformation(theMovieDbId);
} }
/// <summary>
/// Returns similar movies to the movie id passed in
/// </summary>
/// <param name="theMovieDbId">ID of the movie</param>
/// <remarks>
/// We use TheMovieDb as the Movie Provider
/// </remarks>
[HttpGet("movie/{theMovieDbId}/similar")]
public async Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId)
{
return await MovieEngine.SimilarMovies(theMovieDbId);
}
/// <summary> /// <summary>
/// Returns Popular Movies /// Returns Popular Movies
/// </summary> /// </summary>
@ -151,43 +164,91 @@ namespace Ombi.Controllers
return await TvEngine.GetShowInformation(tvdbId); return await TvEngine.GetShowInformation(tvdbId);
} }
/// <summary>
/// Returns Popular Tv Shows
/// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/popular/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTvTree()
{
return await TvEngine.PopularTree();
}
/// <summary> /// <summary>
/// Returns Popular Tv Shows /// Returns Popular Tv Shows
/// </summary> /// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks> /// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns> /// <returns></returns>
[HttpGet("tv/popular")] [HttpGet("tv/popular")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTv() public async Task<IEnumerable<SearchTvShowViewModel>> PopularTv()
{ {
return await TvEngine.Popular(); return await TvEngine.Popular();
} }
/// <summary>
/// Returns most Anticiplateds tv shows.
/// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/anticipated/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTvTree()
{
return await TvEngine.AnticipatedTree();
}
/// <summary> /// <summary>
/// Returns most Anticiplateds tv shows. /// Returns most Anticiplateds tv shows.
/// </summary> /// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks> /// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns> /// <returns></returns>
[HttpGet("tv/anticipated")] [HttpGet("tv/anticipated")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticiplatedTv() public async Task<IEnumerable<SearchTvShowViewModel>> AnticipatedTv()
{ {
return await TvEngine.Anticipated(); return await TvEngine.Anticipated();
} }
/// <summary>
/// Returns Most watched shows.
/// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/mostwatched/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchedTree()
{
return await TvEngine.MostWatchesTree();
}
/// <summary> /// <summary>
/// Returns Most watched shows. /// Returns Most watched shows.
/// </summary> /// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks> /// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns> /// <returns></returns>
[HttpGet("tv/mostwatched")] [HttpGet("tv/mostwatched")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatched() public async Task<IEnumerable<SearchTvShowViewModel>> MostWatched()
{ {
return await TvEngine.MostWatches(); return await TvEngine.MostWatches();
} }
/// <summary>
/// Returns trending shows
/// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/trending/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree()
{
return await TvEngine.TrendingTree();
}
/// <summary> /// <summary>
/// Returns trending shows /// Returns trending shows
/// </summary> /// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks> /// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns> /// <returns></returns>
[HttpGet("tv/trending")] [HttpGet("tv/trending")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Trending() public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
{ {
return await TvEngine.Trending(); return await TvEngine.Trending();
} }

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<RuntimeIdentifiers>win10-x64;win10-x32;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64;</RuntimeIdentifiers>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<AssemblyVersion>$(SemVer)</AssemblyVersion> <AssemblyVersion>$(SemVer)</AssemblyVersion>

@ -28,6 +28,12 @@ namespace Ombi
{ {
host = o.Host; host = o.Host;
storagePath = o.StoragePath; storagePath = o.StoragePath;
}).WithNotParsed(err =>
{
foreach (var e in err)
{
Console.WriteLine(e);
}
}); });
Console.WriteLine(HelpOutput(result)); Console.WriteLine(HelpOutput(result));
@ -46,7 +52,7 @@ namespace Ombi
url = new ApplicationConfiguration url = new ApplicationConfiguration
{ {
Type = ConfigurationTypes.Url, Type = ConfigurationTypes.Url,
Value = "http://*" Value = "http://*:5000"
}; };
ctx.ApplicationConfigurations.Add(url); ctx.ApplicationConfigurations.Add(url);
@ -87,13 +93,13 @@ namespace Ombi
public class Options 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." + "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). " + " 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")] "The protocol (http:// or https://) must be included with each URL. Supported formats vary between servers.", Default = "http://*:5000")]
public string Host { get; set; } 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; } public string StoragePath { get; set; }
} }

@ -57,7 +57,6 @@ namespace Ombi
config = new LoggerConfiguration() config = new LoggerConfiguration()
.MinimumLevel.Debug() .MinimumLevel.Debug()
.WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Logs", "log-{Date}.txt")) .WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Logs", "log-{Date}.txt"))
.WriteTo.SQLite("Ombi.db", "Logs", LogEventLevel.Debug)
.CreateLogger(); .CreateLogger();
} }
else else
@ -65,7 +64,6 @@ namespace Ombi
config = new LoggerConfiguration() config = new LoggerConfiguration()
.MinimumLevel.Debug() .MinimumLevel.Debug()
.WriteTo.RollingFile(Path.Combine(StoragePath.StoragePath, "Logs", "log-{Date}.txt")) .WriteTo.RollingFile(Path.Combine(StoragePath.StoragePath, "Logs", "log-{Date}.txt"))
.WriteTo.SQLite(Path.Combine(StoragePath.StoragePath, "Ombi.db"), "Logs", LogEventLevel.Debug)
.CreateLogger(); .CreateLogger();
} }
Log.Logger = config; Log.Logger = config;

@ -2,9 +2,9 @@
"Logging": { "Logging": {
"IncludeScopes": false, "IncludeScopes": false,
"LogLevel": { "LogLevel": {
"Default": "Debug", "Default": "Trace",
"System": "Information", "System": "Trace",
"Microsoft": "Information" "Microsoft": "Warning"
} }
} }
} }

@ -4,7 +4,8 @@
"LogLevel": { "LogLevel": {
"Default": "Debug", "Default": "Debug",
"System": "Debug", "System": "Debug",
"Microsoft": "None" "Microsoft": "None",
"Hangfire": "None"
} }
}, },
"ApplicationSettings": { "ApplicationSettings": {

@ -5140,7 +5140,6 @@
"resolved": "https://registry.npmjs.org/npm/-/npm-5.6.0.tgz", "resolved": "https://registry.npmjs.org/npm/-/npm-5.6.0.tgz",
"integrity": "sha512-mt839mCsI5hzdBJLf1iRBwt610P35iUfvqLVuL7VFdanUwRBAmGtbsjdGIuzegplR95xx+fTHE0vBMuMJp1sLQ==", "integrity": "sha512-mt839mCsI5hzdBJLf1iRBwt610P35iUfvqLVuL7VFdanUwRBAmGtbsjdGIuzegplR95xx+fTHE0vBMuMJp1sLQ==",
"requires": { "requires": {
"JSONStream": "1.3.1",
"abbrev": "1.1.1", "abbrev": "1.1.1",
"ansi-regex": "3.0.0", "ansi-regex": "3.0.0",
"ansicolors": "0.3.2", "ansicolors": "0.3.2",
@ -5175,6 +5174,7 @@
"ini": "1.3.4", "ini": "1.3.4",
"init-package-json": "1.10.1", "init-package-json": "1.10.1",
"is-cidr": "1.0.0", "is-cidr": "1.0.0",
"JSONStream": "1.3.1",
"lazy-property": "1.0.0", "lazy-property": "1.0.0",
"libnpx": "9.7.1", "libnpx": "9.7.1",
"lockfile": "1.0.3", "lockfile": "1.0.3",
@ -5248,24 +5248,6 @@
"write-file-atomic": "2.1.0" "write-file-atomic": "2.1.0"
}, },
"dependencies": { "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": { "abbrev": {
"version": "1.1.1", "version": "1.1.1",
"bundled": true "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": { "lazy-property": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true "bundled": true
@ -10843,6 +10843,14 @@
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" "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": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
@ -10853,14 +10861,6 @@
"strip-ansi": "3.0.1" "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": { "stringstream": {
"version": "0.0.5", "version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",

@ -73,9 +73,11 @@
"TvTab": "Tv-serier", "TvTab": "Tv-serier",
"Suggestions": "Forslag", "Suggestions": "Forslag",
"NoResults": "Beklager, vi fandt ingen resultater!", "NoResults": "Beklager, vi fandt ingen resultater!",
"ReleaseDate": "Udgivelsesdato", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Se på Plex", "ViewOnPlex": "Se på Plex",
"RequestAdded": "{{title}} er anmodet med succes", "RequestAdded": "{{title}} er anmodet med succes",
"Similar": "Similar",
"Movies": { "Movies": {
"PopularMovies": "Populære film", "PopularMovies": "Populære film",
"UpcomingMovies": "Kommende film", "UpcomingMovies": "Kommende film",
@ -109,7 +111,8 @@
"Status": "Status:", "Status": "Status:",
"RequestStatus": "Status for anmodning:", "RequestStatus": "Status for anmodning:",
"Denied": " Afvist:", "Denied": " Afvist:",
"ReleaseDate": "Udgivelsesdato:", "TheatricalRelease": "Theatrical Release: {{date}}",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Dato for anmodning:", "RequestDate": "Dato for anmodning:",
"QualityOverride": "Tilsidesæt kvalitet:", "QualityOverride": "Tilsidesæt kvalitet:",
"RootFolderOverride": "Tilsidesæt rodmappe:", "RootFolderOverride": "Tilsidesæt rodmappe:",

@ -21,7 +21,7 @@
"Request": "Anfrage", "Request": "Anfrage",
"Denied": "Abgelehnt", "Denied": "Abgelehnt",
"Approve": "Genehmigen", "Approve": "Genehmigen",
"PartlyAvailable": "Partly Available", "PartlyAvailable": "Teilweise verfügbar",
"Errors": { "Errors": {
"Validation": "Bitte überprüfen Sie die eingegebenen Werte" "Validation": "Bitte überprüfen Sie die eingegebenen Werte"
} }
@ -73,9 +73,11 @@
"TvTab": "Serien", "TvTab": "Serien",
"Suggestions": "Vorschläge", "Suggestions": "Vorschläge",
"NoResults": "Es tut uns leid, wir haben keine Ergebnisse gefunden!", "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", "ViewOnPlex": "In Plex anschauen",
"RequestAdded": "Anfrage für {{title}} wurde erfolgreich hinzugefügt", "RequestAdded": "Anfrage für {{title}} wurde erfolgreich hinzugefügt",
"Similar": "Similar",
"Movies": { "Movies": {
"PopularMovies": "Beliebte Filme", "PopularMovies": "Beliebte Filme",
"UpcomingMovies": "Kommende Filme", "UpcomingMovies": "Kommende Filme",
@ -85,19 +87,19 @@
"Trailer": "Trailer" "Trailer": "Trailer"
}, },
"TvShows": { "TvShows": {
"Popular": "Popular", "Popular": "Beliebt",
"Trending": "Trending", "Trending": "im Trend",
"MostWatched": "Most Watched", "MostWatched": "Meist gesehen",
"MostAnticipated": "Most Anticipated", "MostAnticipated": "Meist erwartet",
"Results": "Results", "Results": "Ergebnisse",
"AirDate": "Air Date:", "AirDate": "Veröffentlicht am:",
"AllSeasons": "All Seasons", "AllSeasons": "Alle Staffeln",
"FirstSeason": "First Season", "FirstSeason": "erste Staffel",
"LatestSeason": "Latest Season", "LatestSeason": "aktuellste Staffel",
"Select": "Select ...", "Select": "Wähle...",
"SubmitRequest": "Submit Request", "SubmitRequest": "Anfrage einreichen",
"Season": "Season: {{seasonNumber}}", "Season": "Staffel: {{seasonNumber}}",
"SelectAllInSeason": "Select All in Season {{seasonNumber}}" "SelectAllInSeason": "Markiere alles in Staffel {{seasonNumber}}"
} }
}, },
"Requests": { "Requests": {
@ -109,7 +111,8 @@
"Status": "Status:", "Status": "Status:",
"RequestStatus": "Anfrage Status:", "RequestStatus": "Anfrage Status:",
"Denied": " Abgelehnt:", "Denied": " Abgelehnt:",
"ReleaseDate": "Veröffentlichungsdatum:", "TheatricalRelease": "Theatrical Release: {{date}}",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Datum der Anfrage:", "RequestDate": "Datum der Anfrage:",
"QualityOverride": "Qualitäts Überschreiben:", "QualityOverride": "Qualitäts Überschreiben:",
"RootFolderOverride": "Stammverzeichnis Überschreiben:", "RootFolderOverride": "Stammverzeichnis Überschreiben:",
@ -125,7 +128,7 @@
"GridStatus": "Status", "GridStatus": "Status",
"ReportIssue": "Problem melden", "ReportIssue": "Problem melden",
"Filter": "Filter", "Filter": "Filter",
"SeasonNumberHeading": "Season: {seasonNumber}" "SeasonNumberHeading": "Staffel: {seasonNumber}"
}, },
"Issues": { "Issues": {
"Title": "Probleme", "Title": "Probleme",

@ -77,9 +77,11 @@
"TvTab": "TV Shows", "TvTab": "TV Shows",
"Suggestions": "Suggestions", "Suggestions": "Suggestions",
"NoResults": "Sorry, we didn't find any results!", "NoResults": "Sorry, we didn't find any results!",
"ReleaseDate": "Release Date", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease":"Theatrical Release: {{date}}",
"ViewOnPlex": "View On Plex", "ViewOnPlex": "View On Plex",
"RequestAdded": "Request for {{title}} has been added successfully", "RequestAdded": "Request for {{title}} has been added successfully",
"Similar":"Similar",
"Movies": { "Movies": {
"PopularMovies": "Popular Movies", "PopularMovies": "Popular Movies",
"UpcomingMovies": "Upcoming Movies", "UpcomingMovies": "Upcoming Movies",
@ -114,7 +116,8 @@
"Status": "Status:", "Status": "Status:",
"RequestStatus": "Request status:", "RequestStatus": "Request status:",
"Denied": " Denied:", "Denied": " Denied:",
"ReleaseDate": "Release Date:", "TheatricalRelease": "Theatrical Release: {{date}}",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Request Date:", "RequestDate": "Request Date:",
"QualityOverride": "Quality Override:", "QualityOverride": "Quality Override:",
"RootFolderOverride": "Root Folder Override:", "RootFolderOverride": "Root Folder Override:",

@ -73,9 +73,11 @@
"TvTab": "Series", "TvTab": "Series",
"Suggestions": "Sugerencias", "Suggestions": "Sugerencias",
"NoResults": "¡Lo sentimos, no encontramos ningún resultado!", "NoResults": "¡Lo sentimos, no encontramos ningún resultado!",
"ReleaseDate": "Fecha de estreno", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Ver en Plex", "ViewOnPlex": "Ver en Plex",
"RequestAdded": "La solicitud de {{title}} se ha agregado con éxito", "RequestAdded": "La solicitud de {{title}} se ha agregado con éxito",
"Similar": "Similar",
"Movies": { "Movies": {
"PopularMovies": "Películas populares", "PopularMovies": "Películas populares",
"UpcomingMovies": "Próximas películas", "UpcomingMovies": "Próximas películas",
@ -109,7 +111,8 @@
"Status": "Estado:", "Status": "Estado:",
"RequestStatus": "Estado de la solicitud:", "RequestStatus": "Estado de la solicitud:",
"Denied": " Denegado:", "Denied": " Denegado:",
"ReleaseDate": "Fecha de estreno:", "TheatricalRelease": "Theatrical Release: {{date}}",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Fecha de solicitud:", "RequestDate": "Fecha de solicitud:",
"QualityOverride": "Sobreescribir calidad:", "QualityOverride": "Sobreescribir calidad:",
"RootFolderOverride": "Sobreescribir carpeta raíz:", "RootFolderOverride": "Sobreescribir carpeta raíz:",

@ -73,9 +73,11 @@
"TvTab": "TV", "TvTab": "TV",
"Suggestions": "Suggestions", "Suggestions": "Suggestions",
"NoResults": "Désolé, nous n'avons trouvé aucun résultat !", "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", "ViewOnPlex": "Regarder sur Plex",
"RequestAdded": "La demande pour {{title}} a été ajoutée avec succès", "RequestAdded": "La demande pour {{title}} a été ajoutée avec succès",
"Similar": "Similar",
"Movies": { "Movies": {
"PopularMovies": "Films populaires", "PopularMovies": "Films populaires",
"UpcomingMovies": "Films à venir", "UpcomingMovies": "Films à venir",
@ -109,7 +111,8 @@
"Status": "Statut :", "Status": "Statut :",
"RequestStatus": "Statut de la demande :", "RequestStatus": "Statut de la demande :",
"Denied": " Refusé :", "Denied": " Refusé :",
"ReleaseDate": "Date de sortie :", "TheatricalRelease": "Theatrical Release: {{date}}",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Date de la demande :", "RequestDate": "Date de la demande :",
"QualityOverride": "Remplacement de la qualité :", "QualityOverride": "Remplacement de la qualité :",
"RootFolderOverride": "Remplacement du répertoire racine :", "RootFolderOverride": "Remplacement du répertoire racine :",

@ -73,9 +73,11 @@
"TvTab": "Serie TV", "TvTab": "Serie TV",
"Suggestions": "Suggerimenti", "Suggestions": "Suggerimenti",
"NoResults": "Ci dispiace, non abbiamo trovato alcun risultato!", "NoResults": "Ci dispiace, non abbiamo trovato alcun risultato!",
"ReleaseDate": "Data di rilascio", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Guarda su Plex", "ViewOnPlex": "Guarda su Plex",
"RequestAdded": "La richiesta per {{title}} è stata aggiunta correttamente", "RequestAdded": "La richiesta per {{title}} è stata aggiunta correttamente",
"Similar": "Similar",
"Movies": { "Movies": {
"PopularMovies": "Film popolari", "PopularMovies": "Film popolari",
"UpcomingMovies": "Film in arrivo", "UpcomingMovies": "Film in arrivo",
@ -109,7 +111,8 @@
"Status": "Stato:", "Status": "Stato:",
"RequestStatus": "Stato della richiesta:", "RequestStatus": "Stato della richiesta:",
"Denied": " Rifiutato:", "Denied": " Rifiutato:",
"ReleaseDate": "Data di rilascio:", "TheatricalRelease": "Theatrical Release: {{date}}",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Data della richiesta:", "RequestDate": "Data della richiesta:",
"QualityOverride": "Sovrascrivi qualità:", "QualityOverride": "Sovrascrivi qualità:",
"RootFolderOverride": "Sovrascrivi cartella principale:", "RootFolderOverride": "Sovrascrivi cartella principale:",

@ -12,7 +12,7 @@
"Common": { "Common": {
"ContinueButton": "Doorgaan", "ContinueButton": "Doorgaan",
"Available": "Beschikbaar", "Available": "Beschikbaar",
"NotAvailable": "Not Available", "NotAvailable": "Niet Beschikbaar",
"ProcessingRequest": "Verzoek wordt verwerkt", "ProcessingRequest": "Verzoek wordt verwerkt",
"PendingApproval": "Wacht op goedkeuring", "PendingApproval": "Wacht op goedkeuring",
"RequestDenied": "Verzoek geweigerd", "RequestDenied": "Verzoek geweigerd",
@ -21,7 +21,7 @@
"Request": "Verzoek", "Request": "Verzoek",
"Denied": "Geweigerd", "Denied": "Geweigerd",
"Approve": "Accepteer", "Approve": "Accepteer",
"PartlyAvailable": "Partly Available", "PartlyAvailable": "Deels Beschikbaar",
"Errors": { "Errors": {
"Validation": "Fout: Controleer de ingevulde waardes" "Validation": "Fout: Controleer de ingevulde waardes"
} }
@ -47,7 +47,7 @@
"UserManagement": "Gebruikersbeheer", "UserManagement": "Gebruikersbeheer",
"Issues": "Problemen", "Issues": "Problemen",
"Donate": "Doneer!", "Donate": "Doneer!",
"DonateLibraryMaintainer": "Donate to Library Maintainer", "DonateLibraryMaintainer": "Doneren aan bibliotheek beheerder",
"DonateTooltip": "Zo heb ik mijn vrouw overtuigd dat ik Ombi mag ontwikkelen ;)", "DonateTooltip": "Zo heb ik mijn vrouw overtuigd dat ik Ombi mag ontwikkelen ;)",
"UpdateAvailableTooltip": "Update beschikbaar!", "UpdateAvailableTooltip": "Update beschikbaar!",
"Settings": "Instellingen", "Settings": "Instellingen",
@ -62,9 +62,9 @@
"Italian": "Italiaans", "Italian": "Italiaans",
"Danish": "Deens", "Danish": "Deens",
"Dutch": "Nederlands", "Dutch": "Nederlands",
"Norwegian": "Norwegian" "Norwegian": "Noors"
}, },
"OpenMobileApp": "Open Mobile App" "OpenMobileApp": "Open Mobiele App"
}, },
"Search": { "Search": {
"Title": "Zoeken", "Title": "Zoeken",
@ -73,9 +73,11 @@
"TvTab": "TV Series", "TvTab": "TV Series",
"Suggestions": "Suggesties", "Suggestions": "Suggesties",
"NoResults": "Sorry, we hebben geen resultaten gevonden!", "NoResults": "Sorry, we hebben geen resultaten gevonden!",
"ReleaseDate": "Releasedatum", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Bekijk op Plex", "ViewOnPlex": "Bekijk op Plex",
"RequestAdded": "Aanvraag voor {{title}} is succesvol toegevoegd", "RequestAdded": "Aanvraag voor {{title}} is succesvol toegevoegd",
"Similar": "Similar",
"Movies": { "Movies": {
"PopularMovies": "Populaire films", "PopularMovies": "Populaire films",
"UpcomingMovies": "Aankomende Films", "UpcomingMovies": "Aankomende Films",
@ -85,19 +87,19 @@
"Trailer": "Trailer" "Trailer": "Trailer"
}, },
"TvShows": { "TvShows": {
"Popular": "Popular", "Popular": "Populair",
"Trending": "Trending", "Trending": "Trending",
"MostWatched": "Most Watched", "MostWatched": "Meest Bekeken",
"MostAnticipated": "Most Anticipated", "MostAnticipated": "Meest Verwacht",
"Results": "Results", "Results": "Resultaten",
"AirDate": "Air Date:", "AirDate": "Uitzenddatum:",
"AllSeasons": "All Seasons", "AllSeasons": "Alle Seizoenen",
"FirstSeason": "First Season", "FirstSeason": "Eerste Seizoen",
"LatestSeason": "Latest Season", "LatestSeason": "Laatste Seizoen",
"Select": "Select ...", "Select": "Selecteer...",
"SubmitRequest": "Submit Request", "SubmitRequest": "Verzoek Indienen",
"Season": "Season: {{seasonNumber}}", "Season": "Seizoen: {{seasonNumber}}",
"SelectAllInSeason": "Select All in Season {{seasonNumber}}" "SelectAllInSeason": "Selecteer Alles in het Seizoen {{seasonNumber}}"
} }
}, },
"Requests": { "Requests": {
@ -109,7 +111,8 @@
"Status": "Status:", "Status": "Status:",
"RequestStatus": "Aanvraagstatus:", "RequestStatus": "Aanvraagstatus:",
"Denied": " Geweigerd:", "Denied": " Geweigerd:",
"ReleaseDate": "Releasedatum:", "TheatricalRelease": "Theatrical Release: {{date}}",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Aanvraag Datum:", "RequestDate": "Aanvraag Datum:",
"QualityOverride": "Kwaliteit overschrijven:", "QualityOverride": "Kwaliteit overschrijven:",
"RootFolderOverride": "Hoofdmap overschrijven:", "RootFolderOverride": "Hoofdmap overschrijven:",
@ -123,9 +126,9 @@
"GridTitle": "Titel", "GridTitle": "Titel",
"AirDate": "Uitzenddatum", "AirDate": "Uitzenddatum",
"GridStatus": "Status", "GridStatus": "Status",
"ReportIssue": "Report Issue", "ReportIssue": "Probleem Melden",
"Filter": "Filter", "Filter": "Filter",
"SeasonNumberHeading": "Season: {seasonNumber}" "SeasonNumberHeading": "Seizoen: {seasonNumber}"
}, },
"Issues": { "Issues": {
"Title": "Problemen", "Title": "Problemen",
@ -147,9 +150,9 @@
"ReportedBy": "Gerapporteerd door" "ReportedBy": "Gerapporteerd door"
}, },
"Filter": { "Filter": {
"ClearFilter": "Clear Filter", "ClearFilter": "Verwijder Filter",
"FilterHeaderAvailability": "Availability", "FilterHeaderAvailability": "Beschikbaarheid",
"FilterHeaderRequestStatus": "Status", "FilterHeaderRequestStatus": "Status",
"Approved": "Approved" "Approved": "Goedgekeurd"
} }
} }

@ -73,9 +73,11 @@
"TvTab": "TV serier", "TvTab": "TV serier",
"Suggestions": "Forslag", "Suggestions": "Forslag",
"NoResults": "Beklager, vi fant ingen resultater!", "NoResults": "Beklager, vi fant ingen resultater!",
"ReleaseDate": "Utgivelsesdato", "DigitalDate": "Digital utgivelse: {{date}}",
"TheatricalRelease": "Kinopremiere: {{date}}",
"ViewOnPlex": "Spill av på Plex", "ViewOnPlex": "Spill av på Plex",
"RequestAdded": "Forespørsel om {{title}} er lagt til", "RequestAdded": "Forespørsel om {{title}} er lagt til",
"Similar": "Lignende",
"Movies": { "Movies": {
"PopularMovies": "Populære filmer", "PopularMovies": "Populære filmer",
"UpcomingMovies": "Kommende filmer", "UpcomingMovies": "Kommende filmer",
@ -109,7 +111,8 @@
"Status": "Status:", "Status": "Status:",
"RequestStatus": "Status for forespørsel:", "RequestStatus": "Status for forespørsel:",
"Denied": " Avslått:", "Denied": " Avslått:",
"ReleaseDate": "Utgivelsesdato:", "TheatricalRelease": "Kinopremiere: {{date}}",
"DigitalRelease": "Digital utgivelse: {{date}}",
"RequestDate": "Dato for forespørsel:", "RequestDate": "Dato for forespørsel:",
"QualityOverride": "Overstyr kvalitet:", "QualityOverride": "Overstyr kvalitet:",
"RootFolderOverride": "Overstyring av rotmappe:", "RootFolderOverride": "Overstyring av rotmappe:",

@ -73,9 +73,11 @@
"TvTab": "TV-serier", "TvTab": "TV-serier",
"Suggestions": "Förslag", "Suggestions": "Förslag",
"NoResults": "Tyvärr, hittade vi inte några resultat!", "NoResults": "Tyvärr, hittade vi inte några resultat!",
"ReleaseDate": "Publiceringsdatum", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Visa på Plex", "ViewOnPlex": "Visa på Plex",
"RequestAdded": "Efterfrågan om {{title}} har lagts till", "RequestAdded": "Efterfrågan om {{title}} har lagts till",
"Similar": "Similar",
"Movies": { "Movies": {
"PopularMovies": "Populära filmer", "PopularMovies": "Populära filmer",
"UpcomingMovies": "Kommande filmer", "UpcomingMovies": "Kommande filmer",
@ -109,7 +111,8 @@
"Status": "Status:", "Status": "Status:",
"RequestStatus": "Status för efterfrågan:", "RequestStatus": "Status för efterfrågan:",
"Denied": " Nekad:", "Denied": " Nekad:",
"ReleaseDate": "Releasedatum:", "TheatricalRelease": "Theatrical Release: {{date}}",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Datum för efterfrågan:", "RequestDate": "Datum för efterfrågan:",
"QualityOverride": "Kvalité överskridande:", "QualityOverride": "Kvalité överskridande:",
"RootFolderOverride": "Root mapp överskridande:", "RootFolderOverride": "Root mapp överskridande:",

Loading…
Cancel
Save