pull/2947/head^2
Jamie Rees 6 years ago
commit f16b33c437

@ -1,48 +0,0 @@
<!---
!! Please use the Support / bug report template, otherwise we will close the Github issue !!
Version 2.X is not supported anymore. Please don't open a issue for the 2.x version.
See https://github.com/tidusjar/Ombi/issues/1455 for more information.
(Pleas submit a feature request over here: http://feathub.com/tidusjar/Ombi)
--->
#### Ombi build Version:
V 3.0.XX
#### Update Branch:
Open Beta
#### Media Sever:
Plex/Emby
#### Media Server Version:
<!-- If appropriate --->
#### Operating System:
(Place text here)
#### Ombi Applicable Logs (from `/logs/` directory or the Admin page):
```
(Logs go here. Don't remove the ' tags for showing your logs correctly. Please make sure you remove any personal information from the logs)
```
#### Problem Description:
(Place text here)
#### Reproduction Steps:
Please include any steps to reproduce the issue, this the request that is causing the problem etc.

@ -0,0 +1,37 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs (Logs directory where Ombi is located)**
If applicable, a snippet of the logs that seems relevant to the bug if present.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
**Ombi Version (please complete the following information):**
- Version [e.g. 3.0.1158]
- Media Server [e.g. Plex]
**Additional context**
Add any other context about the problem here.

@ -1,11 +1,105 @@
# Changelog
## (unreleased)
## v3.0.4256 (2019-02-18)
### **New Features**
- Update discord link to follow the scheme of other links. [Tom McClellan]
- Update issue templates. [Jamie]
- Update README.md. [Jamie]
- Update CHANGELOG.md. [Jamie]
- Added the functionality to remove a user from getting notifications to their mobile device #2780. [tidusjar]
- Added a demo mode, this will only show movies and shows that are in the public domain. Dam that stupid fruit company. [tidusjar]
- Added Actor Searching for Movies! [TidusJar]
- Added the ability to change where the View on Emby link goes to #2730. [TidusJar]
- Added the request queue to the notifications UI so you can turn it off per notification agent #2747. [TidusJar]
- Added new classes to the posters #2732. [TidusJar]
### **Fixes**
- Fix: src/Ombi/package.json to reduce vulnerabilities. [snyk-bot]
- Fixed #2801 this is when a season is not correctly monitored in sonarr when approved by an admin. [tidusjar]
- Small improvements to try and mitigate #2750. [tidusjar]
- Removed some areas where we clear out the cache. This should help with DB locking #2750. [tidusjar]
- Fixed #2810. [tidusjar]
- Cannot create an issue comment with the API #2811. [tidusjar]
- Set the local domain if the Application URL is set for the HELO or EHLO commands. #2636. [tidusjar]
- New translations en.json (Spanish) [Jamie]
- Delete ISSUE_TEMPLATE.md. [Jamie]
- More minor grammatical edits. [Andrew Metzger]
- Minor grammatical edits. [Andrew Metzger]
- Fixed #2802 the issue where "Issues" were not being deleted correctly. [tidusjar]
- Fixed #2797. [tidusjar]
- New translations en.json (Dutch) [Jamie]
- New translations en.json (Spanish) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- Fixed #2786. [tidusjar]
- Fixed #2756. [tidusjar]
- Ignore the UserName header as part of the Api is the value is an empty string. [tidusjar]
- Fixed #2759. [tidusjar]
- Did #2756. [TidusJar]
- Fixed the exception that sometimes makes ombi fallover. [TidusJar]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Swedish) [Jamie]
- Log the error to the ui to figure out what's going on with #2755. [tidusjar]
- Small fix when denying a request with a reason, we wasn't updating the ui. [TidusJar]
- Make sure we can only set the ApiAlias when using the API Key. [tidusjar]
- #2363 Added the ability to pass any username into the API using the ApiAlias header. [tidusjar]
- Removed the Add user to Plex from Ombi. [tidusjar]
## v3.0.4119 (2019-01-09)

@ -59,6 +59,7 @@ We integrate with the following applications:
Supported notifications:
* SMTP Notifications (Email)
* Discord
* Gotify
* Slack
* Pushbullet
* Pushover
@ -117,13 +118,12 @@ Please feel free to submit a pull request!
# Donation
If you feel like donating you can donate with the below buttons!
[![Patreon](https://www.ombi.io/img/patreondonate.svg)](https://patreon.com/tidusjar/Ombi)
[![Paypal](https://www.ombi.io/img/paypaldonate.svg)](https://paypal.me/PlexRequestsNet)
[![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi)
[![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet)
### A massive thanks to everyone for all their help!
## Stats
[![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput)
### Sponsors ###
- [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools

@ -1,13 +1,17 @@
version: 3.0.{build}
version: 4.0.{build}
configuration: Release
os: Visual Studio 2017
environment:
nodejs_version: "9.8.0"
nodejs_version: "11.5.0"
typescript_version: "3.0.1"
github_auth_token:
secure: H/7uCrjmWHGJxgN3l9fbhhdVjvvWI8VVF4ZzQqeXuJwAf+PgSNBdxv4SS+rMQ+RH
sonarrcloudtoken:
secure: WGkIog4wuMSx1q5vmSOgIBXMtI/leMpLbZhi9MJnJdBBuDfcv12zwXg3LQwY0WbE
# Do not build on tags (GitHub and BitBucket)
skip_tags: true
install:
# Get the latest stable version of Node.js or io.js
@ -16,35 +20,45 @@ install:
- cmd: set path=%programfiles(x86)%\\Microsoft SDKs\TypeScript\3.0;%path%
- cmd: tsc -v
build_script:
# - dotnet tool install --global dotnet-sonarscanner
#- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER) { dotnet sonarscanner begin /k:"tidusjar_Ombi" /d:sonar.organization="tidusjar-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$env.sonarrcloud_token" /d:sonar.analysis.mode="preview" /d:sonar.github.pullRequest="$env:APPVEYOR_PULL_REQUEST_NUMBER" /d:sonar.github.repository="https://github.com/tidusjar/ombi" /d:sonar.github.oauth="$env.github_auth_token" }
# - ps: if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) { dotnet sonarscanner begin /k:"tidusjar_Ombi" /d:sonar.organization="tidusjar-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$env.SONARRCLOUDTOKEN" }
- ps: ./build.ps1 --settings_skipverification=true
# - dotnet sonarscanner end /d:sonar.login="%sonarrcloudtoken%"
- ps: |
$deployBranches =
"feature/v4",
"develop",
"master";
test: off
If(($env:APPVEYOR_REPO_BRANCH -in $deployBranches -Or $env:APPVEYOR_REPO_COMMIT_MESSAGE -Match '!deploy') -And $env:APPVEYOR_REPO_COMMIT_MESSAGE -NotMatch '!build') {
Write-Output "This is a deployment build"
$env:Deploy = 'true'
./build.ps1
}
Else
{
$env:Deploy = 'false'
Write-Output "This is a not a deployment build"
./build.ps1 --target=build
}
skip_commits:
files:
- '**/*.md'
after_build:
- cmd: >-
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\osx.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm.tar.gz"
- ps: |
$deployBranches =
"feature/v4",
"develop",
"master";
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows-32bit.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm64.tar.gz"
If(($env:APPVEYOR_REPO_BRANCH -in $deployBranches -Or $env:APPVEYOR_REPO_COMMIT_MESSAGE -Match '!deploy') -And $env:APPVEYOR_REPO_COMMIT_MESSAGE -NotMatch '!build')
{
Write-Output "Deploying!"
Get-ChildItem -Recurse .\*.zip | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
Get-ChildItem -Recurse .\*.gz | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
}
Else
{
Write-Output "No Deployment"
}
#cache:
#- '%USERPROFILE%\.nuget\packages'

@ -1,10 +1,10 @@
#tool "nuget:?package=GitVersion.CommandLine"
#addin "Cake.Gulp"
#addin "SharpZipLib"
#addin nuget:?package=Cake.Compression&version=0.1.4
#addin "Cake.Incubator"
#addin "Cake.Yarn"
#tool "nuget:?package=GitVersion.CommandLine&version=4.0.0"
#addin nuget:?package=SharpZipLib&version=1.1.0
#addin nuget:?package=Cake.Compression&version=0.2.2
#addin "Cake.Incubator&version=3.1.0"
#addin nuget:?package=Cake.Yarn&version=0.4.5
#addin "Cake.Powershell"
//////////////////////////////////////////////////////////////////////
// ARGUMENTS
@ -82,9 +82,9 @@ Task("SetVersionInfo")
versionInfo = GitVersion(settings);
Information("GitResults -> {0}", versionInfo.Dump());
// Information("GitResults -> {0}", versionInfo.Dump());
Information(@"Build:{0}",AppVeyor.Environment.Build.Dump());
//Information(@"Build:{0}",AppVeyor.Environment.Build.Dump());
var buildVersion = string.Empty;
if(string.IsNullOrEmpty(AppVeyor.Environment.Build.Version))
@ -135,7 +135,7 @@ Task("Gulp Publish")
Task("TSLint")
.Does(() =>
{
// Yarn.FromPath(uiProjectDir).RunScript("lint");
//Yarn.FromPath(uiProjectDir).RunScript("lint");
});
Task("PrePublish")
@ -156,6 +156,7 @@ Task("Package")
});
Task("Publish")
.IsDependentOn("Run-Unit-Tests")
.IsDependentOn("PrePublish")
.IsDependentOn("Publish-Windows")
.IsDependentOn("Publish-Windows-32bit")
@ -250,9 +251,43 @@ Task("Publish-Linux-ARM-64Bit")
Task("Run-Unit-Tests")
.Does(() =>
{
DotNetCoreBuild(csProj, buildSettings);
{
var settings = new DotNetCoreTestSettings
{
ArgumentCustomization = args => args.Append("--logger \"trx;LogFileName=Test.trx\""),
Configuration = "Release"
};
var projectFiles = GetFiles("./**/*Tests.csproj");
foreach(var file in projectFiles)
{
DotNetCoreTest(file.FullPath, settings);
}
var script = @"
$wc = New-Object 'System.Net.WebClient'
foreach ($name in Resolve-Path .\src\**\TestResults\Test*.trx)
{
$wc.UploadFile(""https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)\"", $name)
}
";
// Upload the results
StartPowershellScript(script);
});
Task("Run-Server-Build")
.Does(() =>
{
var settings = new DotNetCoreBuildSettings
{
Framework = frameworkVer,
Configuration = "Release",
OutputDirectory = Directory(buildDir)
};
DotNetCoreBuild(csProj, settings);
});
Task("Run-UI-Build")
.IsDependentOn("PrePublish");
//////////////////////////////////////////////////////////////////////
// TASK TARGETS
//////////////////////////////////////////////////////////////////////
@ -260,6 +295,12 @@ Task("Run-Unit-Tests")
Task("Default")
.IsDependentOn("Publish");
Task("Build")
.IsDependentOn("SetVersionInfo")
.IsDependentOn("Run-Unit-Tests")
.IsDependentOn("Run-Server-Build");
// .IsDependentOn("Run-UI-Build");
//////////////////////////////////////////////////////////////////////
// EXECUTION
//////////////////////////////////////////////////////////////////////

@ -21,40 +21,49 @@ The build script target to run.
The build configuration to use.
.PARAMETER Verbosity
Specifies the amount of information to be displayed.
.PARAMETER Experimental
Tells Cake to use the latest Roslyn release.
.PARAMETER WhatIf
Performs a dry run of the build script.
No tasks will be executed.
.PARAMETER Mono
Tells Cake to use the Mono scripting engine.
.PARAMETER ShowDescription
Shows description about tasks.
.PARAMETER DryRun
Performs a dry run.
.PARAMETER SkipToolPackageRestore
Skips restoring of packages.
.PARAMETER ScriptArgs
Remaining arguments are added here.
.LINK
http://cakebuild.net
https://cakebuild.net
#>
[CmdletBinding()]
Param(
[string]$Script = "build.cake",
[string]$Target = "Default",
[ValidateSet("Release", "Debug")]
[string]$Configuration = "Release",
[string]$Target,
[string]$Configuration,
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
[string]$Verbosity = "Verbose",
[switch]$Experimental,
[Alias("DryRun","Noop")]
[switch]$WhatIf,
[switch]$Mono,
[string]$Verbosity,
[switch]$ShowDescription,
[Alias("WhatIf", "Noop")]
[switch]$DryRun,
[switch]$SkipToolPackageRestore,
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
[string[]]$ScriptArgs
)
# Attempt to set highest encryption available for SecurityProtocol.
# PowerShell will not set this by default (until maybe .NET 4.6.x). This
# will typically produce a message for PowerShell v2 (just an info
# message though)
try {
# Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48)
# Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't
# exist in .NET 4.0, even though they are addressable if .NET 4.5+ is
# installed (.NET 4.5 is an in-place upgrade).
[System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48
} catch {
Write-Output 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to upgrade to .NET Framework 4.5+ and PowerShell v3'
}
[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
function MD5HashFile([string] $filePath)
{
@ -80,6 +89,15 @@ function MD5HashFile([string] $filePath)
}
}
function GetProxyEnabledWebClient
{
$wc = New-Object System.Net.WebClient
$proxy = [System.Net.WebRequest]::GetSystemWebProxy()
$proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
$wc.Proxy = $proxy
return $wc
}
Write-Host "Preparing to run build script..."
if(!$PSScriptRoot){
@ -87,31 +105,15 @@ if(!$PSScriptRoot){
}
$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins"
$MODULES_DIR = Join-Path $TOOLS_DIR "Modules"
$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
# Should we use mono?
$UseMono = "";
if($Mono.IsPresent) {
Write-Verbose -Message "Using the Mono based scripting engine."
$UseMono = "-mono"
}
# Should we use the new Roslyn?
$UseExperimental = "";
if($Experimental.IsPresent -and !($Mono.IsPresent)) {
Write-Verbose -Message "Using experimental version of Roslyn."
$UseExperimental = "-experimental"
}
# Is this a dry run?
$UseDryRun = "";
if($WhatIf.IsPresent) {
$UseDryRun = "-dryrun"
}
$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config"
$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config"
# Make sure tools folder exists
if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
@ -122,7 +124,10 @@ if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
# Make sure that packages.config exist.
if (!(Test-Path $PACKAGES_CONFIG)) {
Write-Verbose -Message "Downloading packages.config..."
try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
try {
$wc = GetProxyEnabledWebClient
$wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG)
} catch {
Throw "Could not download packages.config."
}
}
@ -142,7 +147,8 @@ if (!(Test-Path $NUGET_EXE)) {
if (!(Test-Path $NUGET_EXE)) {
Write-Verbose -Message "Downloading NuGet.exe..."
try {
(New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE)
$wc = GetProxyEnabledWebClient
$wc.DownloadFile($NUGET_URL, $NUGET_EXE)
} catch {
Throw "Could not download NuGet.exe."
}
@ -161,20 +167,56 @@ if(-Not $SkipToolPackageRestore.IsPresent) {
if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
Write-Verbose -Message "Missing or changed package.config hash..."
Remove-Item * -Recurse -Exclude packages.config,nuget.exe
Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery |
Remove-Item -Recurse
}
Write-Verbose -Message "Restoring tools from NuGet..."
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
if ($LASTEXITCODE -ne 0) {
Throw "An error occured while restoring NuGet tools."
Throw "An error occurred while restoring NuGet tools."
}
else
{
$md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
}
Write-Verbose -Message ($NuGetOutput | out-string)
Pop-Location
}
# Restore addins from NuGet
if (Test-Path $ADDINS_PACKAGES_CONFIG) {
Push-Location
Set-Location $ADDINS_DIR
Write-Verbose -Message "Restoring addins from NuGet..."
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`""
if ($LASTEXITCODE -ne 0) {
Throw "An error occurred while restoring NuGet addins."
}
Write-Verbose -Message ($NuGetOutput | out-string)
Pop-Location
}
# Restore modules from NuGet
if (Test-Path $MODULES_PACKAGES_CONFIG) {
Push-Location
Set-Location $MODULES_DIR
Write-Verbose -Message "Restoring modules from NuGet..."
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`""
if ($LASTEXITCODE -ne 0) {
Throw "An error occurred while restoring NuGet modules."
}
Write-Verbose -Message ($NuGetOutput | out-string)
Pop-Location
}
@ -183,7 +225,18 @@ if (!(Test-Path $CAKE_EXE)) {
Throw "Could not find Cake.exe at $CAKE_EXE"
}
# Build Cake arguments
$cakeArguments = @("$Script");
if ($Target) { $cakeArguments += "-target=$Target" }
if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
if ($ShowDescription) { $cakeArguments += "-showdescription" }
if ($DryRun) { $cakeArguments += "-dryrun" }
$cakeArguments += $ScriptArgs
# Start Cake
Write-Host "Running build script..."
Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs"
&$CAKE_EXE $cakeArguments
exit $LASTEXITCODE

@ -0,0 +1,36 @@
using System.Net.Http;
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public class GotifyApi : IGotifyApi
{
public GotifyApi(IApi api)
{
_api = api;
}
private readonly IApi _api;
public async Task PushAsync(string baseUrl, string accessToken, string subject, string body, sbyte priority)
{
var request = new Request("/message", baseUrl, HttpMethod.Post);
request.AddQueryString("token", accessToken);
request.AddHeader("Access-Token", accessToken);
request.ApplicationJsonContentType();
var jsonBody = new
{
message = body,
title = subject,
priority = priority
};
request.AddJsonBody(jsonBody);
await _api.Request(request);
}
}
}

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public interface IGotifyApi
{
Task PushAsync(string endpoint, string accessToken, string subject, string body, sbyte priority);
}
}

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
<PackageVersion></PackageVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

@ -12,5 +12,6 @@ namespace Ombi.Api.Trakt
Task<IEnumerable<TraktMostWatchedShow>> GetMostWatchesShows(TraktTimePeriod period = null, int? page = default(int?), int? limitPerPage = default(int?));
Task<IEnumerable<TraktShow>> GetPopularShows(int? page = default(int?), int? limitPerPage = default(int?));
Task<IEnumerable<TraktTrendingShow>> GetTrendingShows(int? page = default(int?), int? limitPerPage = default(int?));
Task<TraktShow> GetTvExtendedInfo(string imdbId);
}
}

@ -23,7 +23,7 @@ namespace Ombi.Api.Trakt
public async Task<IEnumerable<TraktShow>> GetPopularShows(int? page = null, int? limitPerPage = null)
{
var popular = await Client.Shows.GetPopularShowsAsync(new TraktExtendedInfo { Full = true, Images = true}, null, page ?? 1, limitPerPage ?? 10);
var popular = await Client.Shows.GetPopularShowsAsync(new TraktExtendedInfo { Full = true, Images = true }, null, page ?? 1, limitPerPage ?? 10);
return popular.Value;
}
@ -44,6 +44,11 @@ namespace Ombi.Api.Trakt
var anticipatedShows = await Client.Shows.GetMostWatchedShowsAsync(period ?? TraktTimePeriod.Monthly, new TraktExtendedInfo { Full = true, Images = true }, null, page ?? 1, limitPerPage ?? 10);
return anticipatedShows.Value;
}
public async Task<TraktShow> GetTvExtendedInfo(string imdbId)
{
return await Client.Shows.GetShowAsync(imdbId, new TraktExtendedInfo { Full = true });
}
}
}

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.TvMaze.Models;
using Ombi.Api.TvMaze.Models.V2;
namespace Ombi.Api.TvMaze
{
@ -11,5 +12,6 @@ namespace Ombi.Api.TvMaze
Task<List<TvMazeSearch>> Search(string searchTerm);
Task<TvMazeShow> ShowLookup(int showId);
Task<TvMazeShow> ShowLookupByTheTvDbId(int theTvDbId);
Task<FullSearch> GetTvFullInformation(int id);
}
}

@ -0,0 +1,144 @@
using System;
namespace Ombi.Api.TvMaze.Models.V2
{
public class FullSearch
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public string type { get; set; }
public string language { get; set; }
public string[] genres { get; set; }
public string status { get; set; }
public int runtime { get; set; }
public string premiered { get; set; }
public string officialSite { get; set; }
public Schedule schedule { get; set; }
public Rating rating { get; set; }
public int weight { get; set; }
public Network network { get; set; }
public object webChannel { get; set; }
public Externals externals { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public int updated { get; set; }
public _Links _links { get; set; }
public _Embedded _embedded { get; set; }
}
public class Schedule
{
public string time { get; set; }
public string[] days { get; set; }
}
public class Rating
{
public float average { get; set; }
}
public class Network
{
public int id { get; set; }
public string name { get; set; }
public Country country { get; set; }
}
public class Country
{
public string name { get; set; }
public string code { get; set; }
public string timezone { get; set; }
}
public class Externals
{
public int tvrage { get; set; }
public int thetvdb { get; set; }
public string imdb { get; set; }
}
public class Image
{
public string medium { get; set; }
public string original { get; set; }
}
public class _Links
{
public Self self { get; set; }
public Previousepisode previousepisode { get; set; }
}
public class Self
{
public string href { get; set; }
}
public class Previousepisode
{
public string href { get; set; }
}
public class _Embedded
{
public Cast[] cast { get; set; }
public Crew[] crew { get; set; }
public Episode[] episodes { get; set; }
}
public class Cast
{
public Person person { get; set; }
public Character character { get; set; }
public bool self { get; set; }
public bool voice { get; set; }
}
public class Person
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public Country country { get; set; }
public string birthday { get; set; }
public object deathday { get; set; }
public string gender { get; set; }
public Image image { get; set; }
public _Links _links { get; set; }
}
public class Character
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public Image image { get; set; }
public _Links _links { get; set; }
}
public class Crew
{
public string type { get; set; }
public Person person { get; set; }
}
public class Episode
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public int season { get; set; }
public int number { get; set; }
public string airdate { get; set; }
public string airtime { get; set; }
public DateTime airstamp { get; set; }
public int runtime { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public _Links _links { get; set; }
}
}

@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.TvMaze.Models;
using Ombi.Api.TvMaze.Models.V2;
using Ombi.Helpers;
namespace Ombi.Api.TvMaze
@ -15,7 +15,6 @@ namespace Ombi.Api.TvMaze
{
Api = api;
Logger = logger;
//Mapper = mapper;
}
private string Uri = "http://api.tvmaze.com";
private IApi Api { get; }
@ -75,5 +74,17 @@ namespace Ombi.Api.TvMaze
return await Api.Request<List<TvMazeSeasons>>(request);
}
public async Task<FullSearch> GetTvFullInformation(int id)
{
var request = new Request($"shows/{id}", Uri, HttpMethod.Get);
request.AddQueryString("embed[]", "cast");
request.AddQueryString("embed[]", "crew");
request.AddQueryString("embed[]", "episodes");
request.AddContentHeader("Content-Type", "application/json");
return await Api.Request<FullSearch>(request);
}
}
}

@ -0,0 +1,106 @@
using Microsoft.AspNetCore.Identity;
using Moq;
using NUnit.Framework;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Test.Common;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Ombi.Core.Tests.Authentication
{
[TestFixture]
public class OmbiUserManagerTests
{
[SetUp]
public void Setup()
{
UserStore = new Mock<IUserStore<OmbiUser>>();
PlexApi = new Mock<IPlexApi>();
AuthenticationSettings = new Mock<ISettingsService<AuthenticationSettings>>();
AuthenticationSettings.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(new AuthenticationSettings());
_um = new OmbiUserManager(UserStore.Object, null, null, null, null, null, null, null, null,
PlexApi.Object, null, null, AuthenticationSettings.Object);
}
public OmbiUserManager _um { get; set; }
private Mock<IUserStore<OmbiUser>> UserStore { get; set; }
private Mock<IPlexApi> PlexApi { get; set; }
private Mock<ISettingsService<AuthenticationSettings>> AuthenticationSettings { get; set; }
[Test]
public async Task CheckPassword_PlexUser_EmailLogin_ValidPassword()
{
var user = new OmbiUser
{
UserType = UserType.PlexUser,
EmailLogin = true,
Email = "MyEmail@email.com"
};
PlexApi.Setup(x => x.SignIn(It.IsAny<UserRequest>()))
.ReturnsAsync(new PlexAuthentication
{
user = new User
{
authentication_token = "abc"
}
});
var result = await _um.CheckPasswordAsync(user, "pass");
Assert.That(result, Is.True);
PlexApi.Verify(x => x.SignIn(It.Is<UserRequest>(c => c.login == "MyEmail@email.com")), Times.Once);
}
[Test]
public async Task CheckPassword_PlexUser_UserNameLogin_ValidPassword()
{
var user = new OmbiUser
{
UserType = UserType.PlexUser,
EmailLogin = false,
Email = "MyEmail@email.com",
UserName = "heyhey"
};
PlexApi.Setup(x => x.SignIn(It.IsAny<UserRequest>()))
.ReturnsAsync(new PlexAuthentication
{
user = new User
{
authentication_token = "abc"
}
});
var result = await _um.CheckPasswordAsync(user, "pass");
Assert.That(result, Is.True);
PlexApi.Verify(x => x.SignIn(It.Is<UserRequest>(c => c.login == "heyhey")), Times.Once);
}
[Test]
public async Task CheckPassword_PlexUser_UserNameLogin_InvalidPassword()
{
var user = new OmbiUser
{
UserType = UserType.PlexUser,
EmailLogin = false,
Email = "MyEmail@email.com",
UserName = "heyhey"
};
PlexApi.Setup(x => x.SignIn(It.IsAny<UserRequest>()))
.ReturnsAsync(new PlexAuthentication());
var result = await _um.CheckPasswordAsync(user, "pass");
Assert.That(result, Is.False);
PlexApi.Verify(x => x.SignIn(It.Is<UserRequest>(c => c.login == "heyhey")), Times.Once);
}
}
}

@ -0,0 +1,195 @@

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using Moq;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.V2;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Tests.Engine
{
[TestFixture]
public class CalendarEngineTests
{
public Mock<IMovieRequestRepository> MovieRepo { get; set; }
public Mock<ITvRequestRepository> TvRepo { get; set; }
public CalendarEngine CalendarEngine { get; set; }
[SetUp]
public void Setup()
{
MovieRepo = new Mock<IMovieRequestRepository>();
TvRepo = new Mock<ITvRequestRepository>();
var principle = new Mock<IPrincipal>();
var identity = new Mock<IIdentity>();
identity.Setup(x => x.Name).Returns("UnitTest");
principle.Setup(x => x.Identity).Returns(identity.Object);
CalendarEngine = new CalendarEngine(principle.Object, null, null, MovieRepo.Object, TvRepo.Object);
}
[Test]
public async Task Calendar_Movies_OnlyGet_PreviousAndFuture_90_Days()
{
var movies = new List<MovieRequests>
{
new MovieRequests
{
Title="Invalid",
ReleaseDate = new DateTime(2018,10,01)
},
new MovieRequests
{
Title="Invalid",
ReleaseDate = DateTime.Now.AddDays(91)
},
new MovieRequests
{
Title="Valid",
ReleaseDate = DateTime.Now
}
};
MovieRepo.Setup(x => x.GetAll()).Returns(movies.AsQueryable());
var data = await CalendarEngine.GetCalendarData();
Assert.That(data.Count, Is.EqualTo(1));
Assert.That(data[0].Title, Is.EqualTo("Valid"));
}
[Test]
public async Task Calendar_Episodes_OnlyGet_PreviousAndFuture_90_Days()
{
var tv = new List<ChildRequests>
{
new ChildRequests
{
SeasonRequests = new List<SeasonRequests>
{
new SeasonRequests
{
Episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
Title = "Invalid",
AirDate = new DateTime(2018,01,01)
},
new EpisodeRequests
{
Title = "Invalid",
AirDate = DateTime.Now.AddDays(91)
},
new EpisodeRequests
{
Title = "Valid",
AirDate = DateTime.Now
},
}
}
}
},
};
TvRepo.Setup(x => x.GetChild()).Returns(tv.AsQueryable());
var data = await CalendarEngine.GetCalendarData();
Assert.That(data.Count, Is.EqualTo(1));
Assert.That(data[0].Title, Is.EqualTo("Valid"));
}
[TestCaseSource(nameof(StatusTvColorData))]
public async Task<string> Calendar_Tv_StatusColor(AvailabilityTestModel model)
{
var tv = new List<ChildRequests>
{
new ChildRequests
{
SeasonRequests = new List<SeasonRequests>
{
new SeasonRequests
{
Episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
Title = "Valid",
AirDate = DateTime.Now,
Approved = model.Approved,
Available = model.Available
},
}
}
}
},
};
TvRepo.Setup(x => x.GetChild()).Returns(tv.AsQueryable());
var data = await CalendarEngine.GetCalendarData();
return data[0].BackgroundColor;
}
[TestCaseSource(nameof(StatusColorData))]
public async Task<string> Calendar_Movie_StatusColor(AvailabilityTestModel model)
{
var movies = new List<MovieRequests>
{
new MovieRequests
{
Title="Valid",
ReleaseDate = DateTime.Now,
Denied = model.Denied,
Approved = model.Approved,
Available = model.Available
},
};
MovieRepo.Setup(x => x.GetAll()).Returns(movies.AsQueryable());
var data = await CalendarEngine.GetCalendarData();
return data[0].BackgroundColor;
}
public static IEnumerable<TestCaseData> StatusColorData
{
get
{
yield return new TestCaseData(new AvailabilityTestModel
{
Approved = true,
Denied = true
}).Returns("red").SetName("Calendar_DeniedRequest");
foreach (var testCaseData in StatusTvColorData)
{
yield return testCaseData;
}
}
}
public static IEnumerable<TestCaseData> StatusTvColorData
{
get
{
yield return new TestCaseData(new AvailabilityTestModel
{
Available = true,
Approved = true
}).Returns("#469c83").SetName("Calendar_AvailableRequest");
yield return new TestCaseData(new AvailabilityTestModel
{
Approved = true
}).Returns("blue").SetName("Calendar_ApprovedRequest");
}
}
}
public class AvailabilityTestModel
{
public bool Available { get; set; }
public bool Denied { get; set; }
public bool Approved { get; set; }
}
}

@ -3,16 +3,19 @@ using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using AutoFixture;
using MockQueryable.Moq;
using Moq;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Engine;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Test.Common;
namespace Ombi.Core.Tests.Engine
{
@ -30,12 +33,17 @@ namespace Ombi.Core.Tests.Engine
MovieRequestEngine = new Mock<IMovieRequestEngine>();
MovieRequestEngine = new Mock<IMovieRequestEngine>();
User = new Mock<IPrincipal>();
UserManager = new Mock<OmbiUserManager>();
UserManager.Setup(x => x.Users)
.Returns(new EnumerableQuery<OmbiUser>(new List<OmbiUser> {new OmbiUser {Id = "abc"}}));
User.Setup(x => x.Identity.Name).Returns("abc");
UserManager = MockHelper.MockUserManager(new List<OmbiUser> { new OmbiUser { Id = "abc", UserName = "abc" } });
Rule = new Mock<IRuleEvaluator>();
Engine = new VoteEngine(VoteRepository.Object, User.Object, UserManager.Object, Rule.Object, VoteSettings.Object, MusicRequestEngine.Object,
TvRequestEngine.Object, MovieRequestEngine.Object);
F.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
.ForEach(b => F.Behaviors.Remove(b));
F.Behaviors.Add(new OmitOnRecursionBehavior());
}
public Fixture F { get; set; }
@ -49,25 +57,160 @@ namespace Ombi.Core.Tests.Engine
public Mock<ITvRequestEngine> TvRequestEngine { get; set; }
public Mock<IMovieRequestEngine> MovieRequestEngine { get; set; }
[Test]
[Ignore("Need to mock the user manager")]
public async Task New_Upvote()
[TestCaseSource(nameof(VoteData))]
public async Task Vote(VoteType type, RequestType request)
{
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings());
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings
{
Enabled = true,
MovieVoteMax = 10
});
var votes = F.CreateMany<Votes>().ToList();
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes)
.AsQueryable()
.BuildMock().Object);
var result = new VoteEngineResult();
if (type == VoteType.Downvote)
{
result = await Engine.DownVote(1, request);
}
else
{
result = await Engine.UpVote(1, request);
}
Assert.That(result.Result, Is.True);
VoteRepository.Verify(x => x.Add(It.Is<Votes>(c => c.UserId == "abc" && c.VoteType == type)), Times.Once);
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Never);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
}
public static IEnumerable<TestCaseData> VoteData
{
get
{
yield return new TestCaseData(VoteType.Upvote, RequestType.Movie).SetName("Movie_Upvote");
yield return new TestCaseData(VoteType.Downvote, RequestType.Movie).SetName("Movie_Downvote");
yield return new TestCaseData(VoteType.Upvote, RequestType.TvShow).SetName("Tv_Upvote");
yield return new TestCaseData(VoteType.Downvote, RequestType.TvShow).SetName("Tv_Downvote");
}
}
[TestCaseSource(nameof(AttemptedTwiceData))]
public async Task Attempted_Twice(VoteType type, RequestType request)
{
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings
{
Enabled = true,
MovieVoteMax = 10
});
var votes = F.CreateMany<Votes>().ToList();
votes.Add(new Votes
{
RequestId = 1,
RequestType = RequestType.Movie,
UserId = "abc"
UserId = "abc",
VoteType = type
});
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes)
.AsQueryable()
.BuildMock().Object);
var result = new VoteEngineResult();
if (type == VoteType.Downvote)
{
result = await Engine.DownVote(1, request);
}
else
{
result = await Engine.UpVote(1, request);
}
Assert.That(result.Result, Is.False);
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Never);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
}
public static IEnumerable<TestCaseData> AttemptedTwiceData
{
get
{
yield return new TestCaseData(VoteType.Upvote, RequestType.Movie).SetName("Upvote_Attemped_Twice_Movie");
yield return new TestCaseData(VoteType.Downvote, RequestType.Movie).SetName("Downvote_Attempted_Twice_Movie");
yield return new TestCaseData(VoteType.Upvote, RequestType.TvShow).SetName("Upvote_Attemped_Twice_Tv");
yield return new TestCaseData(VoteType.Downvote, RequestType.TvShow).SetName("Downvote_Attempted_Twice_Tv");
}
}
[TestCaseSource(nameof(VoteConvertData))]
public async Task Downvote_Converted_To_Upvote(VoteType type, RequestType request)
{
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings
{
Enabled = true,
MovieVoteMax = 10
});
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes));
var result = await Engine.UpVote(1, RequestType.Movie);
var votes = F.CreateMany<Votes>().ToList();
votes.Add(new Votes
{
RequestId = 1,
RequestType = request,
UserId = "abc",
VoteType = type == VoteType.Upvote ? VoteType.Downvote : VoteType.Upvote
});
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes)
.AsQueryable()
.BuildMock().Object);
var result = new VoteEngineResult();
if (type == VoteType.Downvote)
{
result = await Engine.DownVote(1, request);
}
else
{
result = await Engine.UpVote(1, request);
}
Assert.That(result.Result, Is.True);
VoteRepository.Verify(x => x.Add(It.Is<Votes>(c => c.UserId == "abc" && c.VoteType == VoteType.Upvote)), Times.Once);
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Once);
VoteRepository.Verify(x => x.Add(It.Is<Votes>(v => v.VoteType == type)), Times.Once);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
}
public static IEnumerable<TestCaseData> VoteConvertData
{
get
{
yield return new TestCaseData(VoteType.Upvote, RequestType.Movie).SetName("Downvote_Converted_To_UpVote_Movie");
yield return new TestCaseData(VoteType.Downvote, RequestType.Movie).SetName("UpVote_Converted_To_DownVote_Movie");
yield return new TestCaseData(VoteType.Upvote, RequestType.TvShow).SetName("Downvote_Converted_To_UpVote_TvShow");
yield return new TestCaseData(VoteType.Downvote, RequestType.TvShow).SetName("UpVote_Converted_To_DownVote_TvShow");
}
}
[TestCaseSource(nameof(VotingDisabledData))]
public async Task Voting_Disabled(RequestType type)
{
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings
{
Enabled = false,
MovieVoteMax = 10
});
var result = await Engine.UpVote(1, type);
Assert.That(result.Result, Is.True);
VoteRepository.Verify(x => x.Add(It.IsAny<Votes>()), Times.Never);
}
public static IEnumerable<TestCaseData> VotingDisabledData
{
get
{
yield return new TestCaseData(RequestType.Movie).SetName("Voting_Disabled_Movie");
yield return new TestCaseData(RequestType.TvShow).SetName("Voting_Disabled_TV");
}
}
}
}

@ -7,14 +7,16 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.5.0" />
<PackageReference Include="Moq" Version="4.10.0" />
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="Nunit" Version="3.11.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.0.1"></packagereference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
<ProjectReference Include="..\Ombi.Test.Common\Ombi.Test.Common.csproj" />
</ItemGroup>
</Project>

@ -4,29 +4,43 @@ using Moq;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Store.Entities.Requests;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Helpers;
using Ombi.Test.Common;
using System.Collections.Generic;
using Ombi.Store.Entities;
using System;
namespace Ombi.Core.Tests.Rule.Request
{
[TestFixture]
public class AutoApproveRuleTests
{
private List<OmbiUser> _users = new List<OmbiUser>
{
new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="abc" }
};
[SetUp]
public void Setup()
{
PrincipalMock = new Mock<IPrincipal>();
Rule = new AutoApproveRule(PrincipalMock.Object);
PrincipalMock.Setup(x => x.Identity.Name).Returns("abc");
UserManager = MockHelper.MockUserManager(_users);
Rule = new AutoApproveRule(PrincipalMock.Object, UserManager.Object);
}
private AutoApproveRule Rule { get; set; }
private Mock<IPrincipal> PrincipalMock { get; set; }
private Mock<OmbiUserManager> UserManager { get; set; }
[Test]
public async Task Should_ReturnSuccess_WhenAdminAndRequestMovie()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Admin)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -37,7 +51,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenAdminAndRequestTV()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Admin)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -48,7 +62,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenAutoApproveMovieAndRequestMovie()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.AutoApproveMovie)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveMovie)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -56,10 +70,21 @@ namespace Ombi.Core.Tests.Rule.Request
Assert.True(request.Approved);
}
[Test]
public async Task Should_ReturnFail_WhenAutoApproveMovie_And_RequestTV()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveMovie)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
}
[Test]
public async Task Should_ReturnSuccess_WhenAutoApproveTVAndRequestTV()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.AutoApproveTv)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveTv)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -67,9 +92,21 @@ namespace Ombi.Core.Tests.Rule.Request
Assert.True(request.Approved);
}
[Test]
public async Task Should_ReturnFail_WhenAutoApproveTV_And_RequestMovie()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.AutoApproveTv)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
}
[Test]
public async Task Should_ReturnFail_WhenNoClaimsAndRequestMovie()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), It.IsAny<string>())).ReturnsAsync(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -80,6 +117,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnFail_WhenNoClaimsAndRequestTV()
{
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), It.IsAny<string>())).ReturnsAsync(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);

@ -1,31 +1,46 @@
using System;
using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Rule.Rules;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Test.Common;
namespace Ombi.Core.Tests.Rule.Request
{
public class CanRequestRuleTests
{
private List<OmbiUser> _users = new List<OmbiUser>
{
new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="abc" }
};
[SetUp]
public void Setup()
{
PrincipalMock = new Mock<IPrincipal>();
Rule = new CanRequestRule(PrincipalMock.Object);
PrincipalMock.Setup(x => x.Identity.Name).Returns("abc");
UserManager = MockHelper.MockUserManager(_users);
Rule = new CanRequestRule(PrincipalMock.Object, UserManager.Object);
}
private CanRequestRule Rule { get; set; }
private Mock<IPrincipal> PrincipalMock { get; set; }
private Mock<OmbiUserManager> UserManager { get; set; }
[Test]
public async Task Should_ReturnSuccess_WhenRequestingMovieWithMovieRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestMovie)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestMovie)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -35,7 +50,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnFail_WhenRequestingMovieWithoutMovieRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestMovie)).Returns(false);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestMovie)).ReturnsAsync(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -46,7 +61,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenRequestingMovieWithAdminRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Admin)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -56,7 +71,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenRequestingTVWithAdminRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.Admin)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -66,7 +81,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnSuccess_WhenRequestingTVWithTVRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestTv)).Returns(true);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestTv)).ReturnsAsync(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -76,7 +91,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Test]
public async Task Should_ReturnFail_WhenRequestingTVWithoutTVRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestTv)).Returns(false);
UserManager.Setup(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), OmbiRoles.RequestTv)).ReturnsAsync(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using MockQueryable.Moq;
using Moq;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Rule.Rules;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
using Ombi.Test.Common;
namespace Ombi.Core.Tests.Rule.Request
{
public class ExistingMovieRequestRuleTests
{
[SetUp]
public void Setup()
{
ContextMock = new Mock<IMovieRequestRepository>();
Rule = new ExistingMovieRequestRule(ContextMock.Object);
}
private ExistingMovieRequestRule Rule { get; set; }
private Mock<IMovieRequestRepository> ContextMock { get; set; }
[Test]
public async Task ExistingRequestRule_Movie_Has_Been_Requested_With_TheMovieDBId()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<MovieRequests>
{
new MovieRequests
{
TheMovieDbId = 1,
RequestType = RequestType.Movie
}
}.AsQueryable().BuildMock().Object);
var o = new MovieRequests
{
TheMovieDbId = 1,
};
var result = await Rule.Execute(o);
Assert.That(result.Success, Is.False);
Assert.That(result.Message, Is.Not.Empty);
}
[Test]
public async Task ExistingRequestRule_Movie_Has_Been_Requested_With_ImdbId()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<MovieRequests>
{
new MovieRequests
{
TheMovieDbId = 11111,
ImdbId = 1.ToString(),
RequestType = RequestType.Movie
}
}.AsQueryable().BuildMock().Object);
var o = new MovieRequests
{
ImdbId = 1.ToString(),
};
var result = await Rule.Execute(o);
Assert.That(result.Success, Is.False);
Assert.That(result.Message, Is.Not.Empty);
}
[Test]
public async Task ExistingRequestRule_Movie_HasNot_Been_Requested()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<MovieRequests>
{
new MovieRequests
{
TheMovieDbId = 2,
ImdbId = "2",
RequestType = RequestType.Movie
}
}.AsQueryable().BuildMock().Object);
var o = new MovieRequests
{
TheMovieDbId = 1,
ImdbId = "1"
};
var result = await Rule.Execute(o);
Assert.That(result.Success, Is.True);
Assert.That(result.Message, Is.Null.Or.Empty);
}
}
}

@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Tests.Rule.Search
{
public class AvailabilityRuleHelperTests
{
[Test]
public void Is_Available_When_All_We_Have_All_Aired_Episodes()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(1), // Tomorrow!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.True);
}
[Test]
public void Is_Available_When_All_We_Have_All_Aired_Episodes_With_Unknown_Dates()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.True);
}
[Test]
public void Is_PartlyAvailable_When_All_We_Have_Some_Aired_Episodes()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-14), // Yesterday
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.False);
Assert.That(model.PartlyAvailable, Is.True);
}
[Test]
public void Is_SeasonAvailable_When_All_We_Have_All_Aired_Episodes_In_A_Season()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-14), // Yesterday
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var availableEpisodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = true
},
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests>
{
new SeasonRequests { Episodes = episodes },
new SeasonRequests { Episodes = availableEpisodes },
}
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.False);
Assert.That(model.PartlyAvailable, Is.True);
Assert.That(model.SeasonRequests[1].SeasonAvailable, Is.True);
}
[Test]
public void Is_NotAvailable_When_All_We_Have_No_Aired_Episodes()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-1), // Yesterday
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.Now.AddDays(-14),
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.False);
Assert.That(model.PartlyAvailable, Is.False);
}
[Test]
public void Is_NotAvailable_When_All_Episodes_Are_Unknown()
{
var episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
AirDate = DateTime.MinValue,
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue,
Available = false
},
new EpisodeRequests
{
AirDate = DateTime.MinValue, // Unknown date!
Available = false
}
};
var model = new SearchTvShowViewModel
{
SeasonRequests = new List<SeasonRequests> { new SeasonRequests { Episodes = episodes } }
};
AvailabilityRuleHelper.CheckForUnairedEpisodes(model);
Assert.That(model.FullyAvailable, Is.False);
Assert.That(model.PartlyAvailable, Is.False);
}
}
}

@ -4,6 +4,8 @@ using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
@ -16,15 +18,18 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup()
{
ContextMock = new Mock<IEmbyContentRepository>();
Rule = new EmbyAvailabilityRule(ContextMock.Object);
SettingsMock = new Mock<ISettingsService<EmbySettings>>();
Rule = new EmbyAvailabilityRule(ContextMock.Object, SettingsMock.Object);
}
private EmbyAvailabilityRule Rule { get; set; }
private Mock<IEmbyContentRepository> ContextMock { get; set; }
private Mock<ISettingsService<EmbySettings>> SettingsMock { get; set; }
[Test]
public async Task Movie_ShouldBe_Available_WhenFoundInEmby()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings());
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
ProviderId = "123"
@ -39,6 +44,64 @@ namespace Ombi.Core.Tests.Rule.Search
Assert.True(search.Available);
}
[Test]
public async Task Movie_Has_Custom_Url_When_Specified_In_Settings()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings
{
Enable = true,
Servers = new List<EmbyServers>
{
new EmbyServers
{
ServerHostname = "http://test.com/"
}
}
});
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
ProviderId = "123",
EmbyId = 1.ToString()
});
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.That(search.EmbyUrl, Is.EqualTo("http://test.com/#!/itemdetails.html?id=1"));
}
[Test]
public async Task Movie_Uses_Default_Url_When()
{
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings
{
Enable = true,
Servers = new List<EmbyServers>
{
new EmbyServers
{
ServerHostname = string.Empty
}
}
});
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
ProviderId = "123",
EmbyId = 1.ToString()
});
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.That(search.EmbyUrl, Is.EqualTo("https://app.emby.media/#!/itemdetails.html?id=1"));
}
[Test]
public async Task Movie_ShouldBe_NotAvailable_WhenNotFoundInEmby()
{

@ -11,7 +11,7 @@ using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Tests.Rule.Search
{
public class ExistignRequestRuleTests
public class ExistingRequestRuleTests
{
[SetUp]
public void Setup()
@ -39,18 +39,16 @@ namespace Ombi.Core.Tests.Rule.Search
RequestType = RequestType.Movie
};
MovieMock.Setup(x => x.GetRequest(123)).Returns(list);
MovieMock.Setup(x => x.GetRequestAsync(123)).ReturnsAsync(list);
var search = new SearchMovieViewModel
{
Id = 123,
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.True(search.Approved);
Assert.True(search.Requested);
Assert.That(result.Success, Is.True);
Assert.That(search.Approved, Is.True);
Assert.That(search.Requested, Is.True);
}
[Test]
@ -62,7 +60,7 @@ namespace Ombi.Core.Tests.Rule.Search
Approved = true
};
MovieMock.Setup(x => x.GetRequest(123)).Returns(list);
MovieMock.Setup(x => x.GetRequestAsync(123)).ReturnsAsync(list);
var search = new SearchMovieViewModel
{
Id = 999,

@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Tests.Rule.Search
{
public class LidarrAlbumCacheRuleTests
{
[SetUp]
public void Setup()
{
ContextMock = new Mock<IExternalRepository<LidarrAlbumCache>>();
Rule = new LidarrAlbumCacheRule(ContextMock.Object);
}
private LidarrAlbumCacheRule Rule { get; set; }
private Mock<IExternalRepository<LidarrAlbumCache>> ContextMock { get; set; }
[Test]
public async Task Should_Not_Be_Monitored_Or_Available()
{
var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.False(request.Monitored);
}
[Test]
public async Task Should_Be_Monitored_But_Not_Available()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrAlbumCache>
{
new LidarrAlbumCache
{
ForeignAlbumId = "abc",
PercentOfTracks = 0
}
}.AsQueryable());
var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.True(request.Monitored);
Assert.That(request.PartiallyAvailable, Is.EqualTo(false));
Assert.That(request.Available, Is.EqualTo(false));
Assert.That(request.FullyAvailable, Is.EqualTo(false));
}
[Test]
public async Task Should_Be_Monitored_And_Partly_Available()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrAlbumCache>
{
new LidarrAlbumCache
{
ForeignAlbumId = "abc",
PercentOfTracks = 1
}
}.AsQueryable());
var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.True(request.Monitored);
Assert.That(request.PartiallyAvailable, Is.EqualTo(true));
Assert.That(request.Available, Is.EqualTo(false));
Assert.That(request.FullyAvailable, Is.EqualTo(false));
}
[Test]
public async Task Should_Be_Monitored_And_Fully_Available()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrAlbumCache>
{
new LidarrAlbumCache
{
ForeignAlbumId = "abc",
PercentOfTracks = 100
}
}.AsQueryable());
var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.True(request.Monitored);
Assert.That(request.PartiallyAvailable, Is.EqualTo(false));
Assert.That(request.FullyAvailable, Is.EqualTo(true));
}
[Test]
public async Task Should_Be_Monitored_And_Fully_Available_Casing()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrAlbumCache>
{
new LidarrAlbumCache
{
ForeignAlbumId = "abc",
PercentOfTracks = 100
}
}.AsQueryable());
var request = new SearchAlbumViewModel { ForeignAlbumId = "ABC" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Approved);
Assert.True(request.Monitored);
Assert.That(request.PartiallyAvailable, Is.EqualTo(false));
Assert.That(request.FullyAvailable, Is.EqualTo(true));
}
}
}

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Tests.Rule.Search
{
public class LidarrArtistCacheRuleTests
{
[SetUp]
public void Setup()
{
ContextMock = new Mock<IExternalRepository<LidarrArtistCache>>();
Rule = new LidarrArtistCacheRule(ContextMock.Object);
}
private LidarrArtistCacheRule Rule { get; set; }
private Mock<IExternalRepository<LidarrArtistCache>> ContextMock { get; set; }
[Test]
public async Task Should_Not_Be_Monitored()
{
var request = new SearchArtistViewModel { ForignArtistId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.False(request.Monitored);
}
[Test]
public async Task Should_Be_Monitored()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrArtistCache>
{
new LidarrArtistCache
{
ForeignArtistId = "abc",
}
}.AsQueryable());
var request = new SearchArtistViewModel { ForignArtistId = "abc" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.True(request.Monitored);
}
[Test]
public async Task Should_Be_Monitored_Casing()
{
ContextMock.Setup(x => x.GetAll()).Returns(new List<LidarrArtistCache>
{
new LidarrArtistCache
{
ForeignArtistId = "abc",
}
}.AsQueryable());
var request = new SearchArtistViewModel { ForignArtistId = "ABC" };
var result = await Rule.Execute(request);
Assert.True(result.Success);
Assert.True(request.Monitored);
}
}
}

@ -1,55 +0,0 @@
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Rules.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Tests.Rule.Search
{
public class PlexAvailabilityRuleTests
{
[SetUp]
public void Setup()
{
ContextMock = new Mock<IPlexContentRepository>();
Rule = new PlexAvailabilityRule(ContextMock.Object);
}
private PlexAvailabilityRule Rule { get; set; }
private Mock<IPlexContentRepository> ContextMock { get; set; }
[Test]
public async Task ShouldBe_Available_WhenFoundInPlex()
{
ContextMock.Setup(x => x.Get(It.IsAny<string>())).ReturnsAsync(new PlexServerContent
{
Url = "TestUrl",
ImdbId = "132"
});
var search = new SearchMovieViewModel
{
ImdbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.AreEqual("TestUrl", search.PlexUrl);
Assert.True(search.Available);
}
[Test]
public async Task ShouldBe_NotAvailable_WhenNotFoundInPlex()
{
ContextMock.Setup(x => x.Get(It.IsAny<string>())).Returns(Task.FromResult(default(PlexServerContent)));
var search = new SearchMovieViewModel();
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.Null(search.PlexUrl);
Assert.False(search.Available);
}
}
}

@ -18,7 +18,7 @@ namespace Ombi.Core.Tests
{
get
{
yield return new TestCaseData("this!is^a*string",new []{'!','^','*'}).Returns("thisisastring").SetName("Basic Strip Multipe Chars");
yield return new TestCaseData("this!is^a*string",new []{'!','^','*'}).Returns("thisisastring").SetName("Basic Strip Multiple Chars");
yield return new TestCaseData("What is this madness'",new []{'\'','^','*'}).Returns("What is this madness").SetName("Basic Strip Multipe Chars");
}
}

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Config;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine.Demo
{
public class DemoMovieSearchEngine : MovieSearchEngine, IDemoMovieSearchEngine
{
public DemoMovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper,
ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s,
IRepository<RequestSubscription> sub, IOptions<DemoLists> lists)
: base(identity, service, movApi, mapper, logger, r, um, mem, s, sub)
{
_demoLists = lists.Value;
}
private readonly DemoLists _demoLists;
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
{
var result = await MovieApi.SearchMovie(search, null, "en");
for (var i = 0; i < result.Count; i++)
{
if (!_demoLists.Movies.Contains(result[i].Id))
{
result.RemoveAt(i);
}
}
if(result.Count > 0)
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
{
var rand = new Random();
var responses = new List<SearchMovieViewModel>();
for (int i = 0; i < 10; i++)
{
var item = rand.Next(_demoLists.Movies.Length);
var movie = _demoLists.Movies[item];
if (responses.Any(x => x.Id == movie))
{
i--;
continue;
}
var movieResult = await MovieApi.GetMovieInformationWithExtraInfo(movie);
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieResult);
responses.Add(await ProcessSingleMovie(viewMovie));
}
return responses;
}
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
{
return await NowPlayingMovies();
}
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
{
return await NowPlayingMovies();
}
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
{
return await NowPlayingMovies();
}
}
public interface IDemoMovieSearchEngine
{
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies();
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
Task<IEnumerable<SearchMovieViewModel>> Search(string search);
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
}
}

@ -0,0 +1,96 @@
using AutoMapper;
using Microsoft.Extensions.Options;
using Ombi.Api.Trakt;
using Ombi.Api.TvMaze;
using Ombi.Config;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
namespace Ombi.Core.Engine.Demo
{
public class DemoTvSearchEngine : TvSearchEngine, IDemoTvSearchEngine
{
public DemoTvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper,
ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo,
IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache,
ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub, IOptions<DemoLists> lists)
: base(identity, service, tvMaze, mapper, plexSettings, embySettings, repo, embyRepo, trakt, r, um, memCache, s, sub)
{
_demoLists = lists.Value;
}
private readonly DemoLists _demoLists;
public async Task<IEnumerable<SearchTvShowViewModel>> Search(string search)
{
var searchResult = await TvMazeApi.Search(search);
for (var i = 0; i < searchResult.Count; i++)
{
if (!_demoLists.TvShows.Contains(searchResult[i].show?.externals?.thetvdb ?? 0))
{
searchResult.RemoveAt(i);
}
}
if (searchResult != null)
{
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in searchResult)
{
if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false))
{
continue;
}
retVal.Add(ProcessResult(tvMazeSearch));
}
return retVal;
}
return null;
}
public async Task<IEnumerable<SearchTvShowViewModel>> NowPlayingMovies()
{
var rand = new Random();
var responses = new List<SearchTvShowViewModel>();
for (int i = 0; i < 10; i++)
{
var item = rand.Next(_demoLists.TvShows.Length);
var tv = _demoLists.TvShows[item];
if (responses.Any(x => x.Id == tv))
{
i--;
continue;
}
var movieResult = await TvMazeApi.ShowLookup(tv);
responses.Add(ProcessResult(movieResult));
}
return responses;
}
}
public interface IDemoTvSearchEngine
{
Task<IEnumerable<SearchTvShowViewModel>> Search(string search);
Task<IEnumerable<SearchTvShowViewModel>> NowPlayingMovies();
}
}

@ -1,12 +1,24 @@
using Ombi.Core.Models.Search;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
namespace Ombi.Core
namespace Ombi.Core.Engine.Interfaces
{
public interface IMovieEngineV2
{
Task<MovieFullInfoViewModel> GetFullMovieInformation(int theMovieDbId, string langCode = null);
Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode);
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies();
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies(int currentPosition, int amountToLoad);
Task<MovieCollectionsViewModel> GetCollection(int collectionId, string langCode = null);
Task<int> GetTvDbId(int theMovieDbId);
Task<IEnumerable<SearchMovieViewModel>> PopularMovies(int currentlyLoaded, int toLoad);
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies(int currentlyLoaded, int toLoad);
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies(int currentlyLoaded, int toLoad);
int ResultLimit { get; set; }
}
}

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.UI;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Engine.Interfaces
@ -13,10 +14,11 @@ namespace Ombi.Core.Engine.Interfaces
Task RemoveMovieRequest(int requestId);
Task RemoveAllMovieRequests();
Task<MovieRequests> GetRequest(int requestId);
Task<MovieRequests> UpdateMovieRequest(MovieRequests request);
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
Task<RequestEngineResult> ApproveMovieById(int requestId);
Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason);
Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder);
}
}

@ -23,5 +23,6 @@ namespace Ombi.Core.Engine.Interfaces
Task<IEnumerable<TvRequests>> GetRequestsLite();
Task UpdateQualityProfile(int requestId, int profileId);
Task UpdateRootPath(int requestId, int rootPath);
Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder);
}
}

@ -9,9 +9,13 @@ namespace Ombi.Core.Engine.Interfaces
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
Task<IEnumerable<SearchTvShowViewModel>> Popular();
Task<IEnumerable<SearchTvShowViewModel>> Popular(int currentlyLoaded, int amountToLoad);
Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
Task<IEnumerable<SearchTvShowViewModel>> Anticipated(int currentlyLoaded, int amountToLoad);
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
Task<IEnumerable<SearchTvShowViewModel>> Trending();
Task<IEnumerable<SearchTvShowViewModel>> MostWatches(int currentlyLoaded, int amountToLoad);
Task<IEnumerable<SearchTvShowViewModel>> Trending(int currentlyLoaded, int amountToLoad);
int ResultLimit { get; set; }
}
}

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Ombi.Core.Models.Search.V2;
namespace Ombi.Core
{
public interface ITVSearchEngineV2
{
Task<SearchFullInfoTvShowViewModel> GetShowInformation(int tvdbid);
}
}

@ -4,6 +4,7 @@ using Ombi.Helpers;
using Ombi.Store.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
@ -200,6 +201,54 @@ namespace Ombi.Core.Engine
};
}
public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder)
{
var shouldHide = await HideFromOtherUsers();
IQueryable<MovieRequests> allRequests;
if (shouldHide.Hide)
{
allRequests =
MovieRepository.GetWithUser(shouldHide
.UserId);
}
else
{
allRequests =
MovieRepository
.GetWithUser();
}
var prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(sortProperty, true);
if (sortProperty.Contains('.'))
{
// This is a navigation property currently not supported
prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find("RequestedDate", true);
//var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
//var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
//var propType = firstProp.PropertyType;
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
}
allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
? allRequests.OrderBy(x => prop.GetValue(x))
: allRequests.OrderByDescending(x => prop.GetValue(x));
var total = await allRequests.CountAsync();
var requests = await allRequests.Skip(position).Take(count)
.ToListAsync();
requests.ForEach(async x =>
{
x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath);
await CheckForSubscription(shouldHide, x);
});
return new RequestsViewModel<MovieRequests>
{
Collection = requests,
Total = total
};
}
private IQueryable<MovieRequests> OrderMovies(IQueryable<MovieRequests> allRequests, OrderType type)
{
switch (type)
@ -259,6 +308,15 @@ namespace Ombi.Core.Engine
return allRequests;
}
public async Task<MovieRequests> GetRequest(int requestId)
{
var request = await MovieRepository.GetWithUser().Where(x => x.Id == requestId).FirstOrDefaultAsync();
request.PosterPath = PosterPathHelper.FixPosterPath(request.PosterPath);
await CheckForSubscription(new HideResult(), request);
return request;
}
private async Task CheckForSubscription(HideResult shouldHide, MovieRequests x)
{
if (shouldHide.UserId == x.RequestedUserId)
@ -493,7 +551,7 @@ namespace Ombi.Core.Engine
RequestType = RequestType.Movie,
});
return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!", RequestId = model.Id};
return new RequestEngineResult { Result = true, Message = $"{movieName} has been successfully added!", RequestId = model.Id };
}
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
@ -533,7 +591,7 @@ namespace Ombi.Core.Engine
return new RequestQuotaCountModel()
{
HasLimit = true,
HasLimit = true,
Limit = limit,
Remaining = count,
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),

@ -31,10 +31,11 @@ namespace Ombi.Core.Engine
Logger = logger;
}
private IMovieDbApi MovieApi { get; }
private IMapper Mapper { get; }
protected IMovieDbApi MovieApi { get; }
protected IMapper Mapper { get; }
private ILogger<MovieSearchEngine> Logger { get; }
protected const int MovieLimit = 10;
/// <summary>
/// Lookups the imdb information.
@ -185,7 +186,7 @@ namespace Ombi.Core.Engine
return null;
}
private async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
IEnumerable<MovieSearchResult> movies)
{
var viewMovies = new List<SearchMovieViewModel>();
@ -196,7 +197,7 @@ namespace Ombi.Core.Engine
return viewMovies;
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false)
protected async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false)
{
if (lookupExtraInfo && viewMovie.ImdbId.IsNullOrEmpty())
{
@ -214,7 +215,7 @@ namespace Ombi.Core.Engine
// This requires the rules to be run first to populate the RequestId property
await CheckForSubscription(viewMovie);
return viewMovie;
}
@ -228,7 +229,7 @@ namespace Ombi.Core.Engine
}
var request = await RequestService.MovieRequestService.GetAll()
.AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id);
if (request)
if (request || viewModel.Available)
{
viewModel.ShowSubscribe = false;
}

@ -69,6 +69,12 @@ namespace Ombi.Core.Engine
};
}
if(album?.artist == null)
{
// Lookup the artist
//album.artist = await _lidarrApi.ArtistLookup(album.artist, s.ApiKey, s.FullUri);
}
var userDetails = await GetUser();
var requestModel = new AlbumRequest
@ -83,7 +89,7 @@ namespace Ombi.Core.Engine
Title = album.title,
Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url,
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty,
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty, // This needs to be populated to send to Lidarr for new requests
RequestedByAlias = model.RequestedByAlias
};
if (requestModel.Cover.IsNullOrEmpty())

@ -7,6 +7,7 @@ using Ombi.Core.Models.Search;
using Ombi.Helpers;
using Ombi.Store.Entities;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
@ -31,14 +32,13 @@ namespace Ombi.Core.Engine
{
public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager,
ITvSender sender, IAuditRepository audit, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
ITvSender sender, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
IRepository<RequestSubscription> sub) : base(user, requestService, rule, manager, cache, settings, sub)
{
TvApi = tvApi;
MovieDbApi = movApi;
NotificationHelper = helper;
TvSender = sender;
Audit = audit;
_requestLog = rl;
}
@ -46,7 +46,6 @@ namespace Ombi.Core.Engine
private ITvMazeApi TvApi { get; }
private IMovieDbApi MovieDbApi { get; }
private ITvSender TvSender { get; }
private IAuditRepository Audit { get; }
private readonly IRepository<RequestLog> _requestLog;
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
@ -84,8 +83,6 @@ namespace Ombi.Core.Engine
}
}
await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username);
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId);
if (existingRequest != null)
{
@ -160,7 +157,7 @@ namespace Ombi.Core.Engine
.ThenInclude(x => x.Episodes)
.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
.Skip(position).Take(count).ToListAsync();
}
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
@ -225,6 +222,59 @@ namespace Ombi.Core.Engine
}
public async Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder)
{
var shouldHide = await HideFromOtherUsers();
List<ChildRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = await TvRepository.GetChild(shouldHide.UserId).ToListAsync();
// Filter out children
FilterChildren(allRequests, shouldHide);
}
else
{
allRequests = await TvRepository.GetChild().ToListAsync();
}
if (allRequests == null)
{
return new RequestsViewModel<ChildRequests>();
}
var total = allRequests.Count;
var prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find(sortProperty, true);
if (sortProperty.Contains('.'))
{
// This is a navigation property currently not supported
prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find("Title", true);
//var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
//var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
//var propType = firstProp.PropertyType;
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
}
allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
? allRequests.OrderBy(x => prop.GetValue(x)).ToList()
: allRequests.OrderByDescending(x => prop.GetValue(x)).ToList();
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
// Make sure we do not show duplicate child requests
allRequests = allRequests.DistinctBy(x => x.ParentRequest.Title).ToList();
return new RequestsViewModel<ChildRequests>
{
Collection = allRequests,
Total = total,
};
}
public async Task<IEnumerable<TvRequests>> GetRequestsLite()
{
var shouldHide = await HideFromOtherUsers();
@ -280,19 +330,24 @@ namespace Ombi.Core.Engine
}
private static void FilterChildren(TvRequests t, HideResult shouldHide)
{
// Filter out children
FilterChildren(t.ChildRequests, shouldHide);
}
private static void FilterChildren(List<ChildRequests> t, HideResult shouldHide)
{
// Filter out children
for (var j = 0; j < t.ChildRequests.Count; j++)
for (var j = 0; j < t.Count; j++)
{
var child = t.ChildRequests[j];
var child = t[j];
if (child.RequestedUserId != shouldHide.UserId)
{
t.ChildRequests.RemoveAt(j);
t.RemoveAt(j);
j--;
}
}
}
public async Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId)
@ -351,7 +406,6 @@ namespace Ombi.Core.Engine
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id);
@ -385,6 +439,7 @@ namespace Ombi.Core.Engine
foreach (var ep in s.Episodes)
{
ep.Approved = true;
ep.Requested = true;
}
}
@ -393,7 +448,6 @@ namespace Ombi.Core.Engine
if (request.Approved)
{
NotificationHelper.Notify(request, NotificationType.RequestApproved);
await Audit.Record(AuditType.Approved, AuditArea.TvRequest, $"Approved Request {request.Title}", Username);
// Autosend
await TvSender.Send(request);
}
@ -425,9 +479,7 @@ namespace Ombi.Core.Engine
public async Task<ChildRequests> UpdateChildRequest(ChildRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
await TvRepository.UpdateChild(request);
await TvRepository.UpdateChild(request);
return request;
}
@ -445,16 +497,14 @@ namespace Ombi.Core.Engine
// Delete the parent
TvRepository.Db.TvRequests.Remove(parent);
}
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
await TvRepository.Db.SaveChangesAsync();
}
public async Task RemoveTvRequest(int requestId)
{
var request = await TvRepository.Get().FirstOrDefaultAsync(x => x.Id == requestId);
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
await TvRepository.Delete(request);
await TvRepository.Delete(request);
}
public async Task<bool> UserHasRequest(string userId)
@ -569,7 +619,7 @@ namespace Ombi.Core.Engine
return await AfterRequest(model.ChildRequests.FirstOrDefault());
}
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
{
foreach (var value in items)
{
@ -606,7 +656,7 @@ namespace Ombi.Core.Engine
var result = await TvSender.Send(model);
if (result.Success)
{
return new RequestEngineResult { Result = true, RequestId = model.Id};
return new RequestEngineResult { Result = true, RequestId = model.Id };
}
return new RequestEngineResult
{
@ -659,10 +709,10 @@ namespace Ombi.Core.Engine
DateTime oldestRequestedAt = await log.OrderBy(x => x.RequestDate)
.Select(x => x.RequestDate)
.FirstOrDefaultAsync();
return new RequestQuotaCountModel()
{
HasLimit = true,
HasLimit = true,
Limit = limit,
Remaining = count,
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),

@ -21,6 +21,8 @@ using Ombi.Core.Authentication;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using TraktApiSharp.Objects.Get.Shows;
using TraktApiSharp.Objects.Get.Shows.Common;
namespace Ombi.Core.Engine
{
@ -40,8 +42,8 @@ namespace Ombi.Core.Engine
EmbyContentRepo = embyRepo;
}
private ITvMazeApi TvMazeApi { get; }
private IMapper Mapper { get; }
protected ITvMazeApi TvMazeApi { get; }
protected IMapper Mapper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexContentRepository PlexContentRepo { get; }
@ -99,7 +101,7 @@ namespace Ombi.Core.Engine
{
Url = e.url,
Title = e.name,
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()),
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
EpisodeNumber = e.number,
});
@ -112,7 +114,7 @@ namespace Ombi.Core.Engine
{
Url = e.url,
Title = e.name,
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()),
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
EpisodeNumber = e.number,
});
}
@ -127,6 +129,19 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> Popular(int currentlyLoaded, int amountToLoad)
{
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktShow>();
foreach (var pagesToLoad in pages)
{
var apiResult = await TraktApi.GetPopularShows(pagesToLoad.Page, ResultLimit);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated()
{
@ -135,6 +150,19 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated(int currentlyLoaded, int amountToLoad)
{
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktMostAnticipatedShow>();
foreach (var pagesToLoad in pages)
{
var apiResult = await TraktApi.GetAnticipatedShows(pagesToLoad.Page, ResultLimit);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
{
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(null, ResultLimit), DateTime.Now.AddHours(12));
@ -149,7 +177,33 @@ namespace Ombi.Core.Engine
return processed;
}
private IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches(int currentlyLoaded, int amountToLoad)
{
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktMostWatchedShow>();
foreach (var pagesToLoad in pages)
{
var apiResult = await TraktApi.GetMostWatchesShows(null, pagesToLoad.Page, ResultLimit);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
return processed;
}
public async Task<IEnumerable<SearchTvShowViewModel>> Trending(int currentlyLoaded, int amountToLoad)
{
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktTrendingShow>();
foreach (var pagesToLoad in pages)
{
var apiResult = await TraktApi.GetTrendingShows(pagesToLoad.Page, ResultLimit);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
var processed = ProcessResults(results);
return processed;
}
protected IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
{
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items)
@ -159,7 +213,7 @@ namespace Ombi.Core.Engine
return retVal;
}
private SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
protected SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
{
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
}

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Ombi.Api.Sonarr.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.Search.V2;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Engine.V2
{
public class CalendarEngine : BaseEngine, ICalendarEngine
{
public DateTime DaysAgo => DateTime.Now.AddDays(-90);
public DateTime DaysAhead => DateTime.Now.AddDays(90);
public CalendarEngine(IPrincipal user, OmbiUserManager um, IRuleEvaluator rules, IMovieRequestRepository movieRepo,
ITvRequestRepository tvRequestRepo) : base(user, um, rules)
{
_movieRepo = movieRepo;
_tvRepo = tvRequestRepo;
}
private readonly IMovieRequestRepository _movieRepo;
private readonly ITvRequestRepository _tvRepo;
public async Task<List<CalendarViewModel>> GetCalendarData()
{
var viewModel = new List<CalendarViewModel>();
var movies = _movieRepo.GetAll().Where(x =>
x.ReleaseDate > DaysAgo && x.ReleaseDate < DaysAhead);
var episodes = _tvRepo.GetChild().SelectMany(x => x.SeasonRequests.SelectMany(e => e.Episodes
.Where(w => w.AirDate > DaysAgo && w.AirDate < DaysAhead)));
foreach (var e in episodes)
{
viewModel.Add(new CalendarViewModel
{
Title = e.Title,
Start = e.AirDate.Date,
Type = RequestType.TvShow,
BackgroundColor = GetBackgroundColor(e),
ExtraParams = new List<ExtraParams>
{
new ExtraParams { Overview = e.Season?.ChildRequest?.ParentRequest?.Overview ?? string.Empty, ProviderId = e.Season?.ChildRequest?.ParentRequest?.TvDbId ?? 0}
}
});
}
foreach (var m in movies)
{
viewModel.Add(new CalendarViewModel
{
Title = m.Title,
Start = m.ReleaseDate.Date,
BackgroundColor = GetBackgroundColor(m),
Type = RequestType.Movie,
ExtraParams = new List<ExtraParams>
{
new ExtraParams { Overview = m.Overview, ProviderId = m.TheMovieDbId}
}
});
}
return viewModel;
}
private string GetBackgroundColor(BaseRequest req)
{
if (req.Available)
{
return "#469c83";
}
if (!req.Available)
{
if (req.Denied ?? false)
{
return "red";
}
if (req.Approved)
{
// We are approved state
return "blue";
}
if (!req.Approved)
{
// Processing
return "teal";
}
}
return "gray";
}
private string GetBackgroundColor(EpisodeRequests req)
{
if (req.Available)
{
return "#469c83";
}
if (!req.Available)
{
if (req.Approved)
{
// We are approved state
return "blue";
}
if (!req.Approved)
{
// Processing
return "teal";
}
}
return "gray";
}
}
}

@ -0,0 +1,11 @@
using Ombi.Core.Models.Search.V2;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Ombi.Core.Engine.V2
{
public interface ICalendarEngine
{
Task<List<CalendarViewModel>> GetCalendarData();
}
}

@ -4,8 +4,10 @@ using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Helpers;
@ -17,9 +19,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Ombi.Core.Models.Search.V2;
namespace Ombi.Core.Engine
namespace Ombi.Core.Engine.V2
{
public class MovieSearchEngineV2 : BaseMediaEngine, IMovieEngineV2
{
@ -45,6 +46,198 @@ namespace Ombi.Core.Engine
return await ProcessSingleMovie(movieInfo);
}
public async Task<MovieCollectionsViewModel> GetCollection(int collectionId, string langCode = null)
{
langCode = await DefaultLanguageCode(langCode);
var collections = await MovieApi.GetCollection(langCode, collectionId);
var c = await ProcessCollection(collections);
c.Collection = c.Collection.OrderBy(x => x.ReleaseDate).ToList();
return c;
}
public async Task<int> GetTvDbId(int theMovieDbId)
{
var result = await MovieApi.GetTvExternals(theMovieDbId);
return result.tvdb_id;
}
/// <summary>
/// Get similar movies to the id passed in
/// </summary>
/// <param name="theMovieDbId"></param>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode)
{
langCode = await DefaultLanguageCode(langCode);
var result = await MovieApi.SimilarMovies(theMovieDbId, langCode);
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
/// <summary>
/// Gets popular movies.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
{
var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () =>
{
var langCode = await DefaultLanguageCode(null);
return await MovieApi.PopularMovies(langCode);
}, DateTime.Now.AddHours(12));
if (result != null)
{
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
private const int _theMovieDbMaxPageItems = 20;
/// <summary>
/// Gets popular movies by paging
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies(int currentlyLoaded, int toLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await MovieApi.PopularMovies(langCode, pagesToLoad.Page);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
return await TransformMovieResultsToResponse(results);
}
/// <summary>
/// Gets top rated movies.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
{
var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () =>
{
var langCode = await DefaultLanguageCode(null);
return await MovieApi.TopRated(langCode);
}, DateTime.Now.AddHours(12));
if (result != null)
{
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies(int currentPosition, int amountToLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await MovieApi.TopRated(langCode, pagesToLoad.Page);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
return await TransformMovieResultsToResponse(results);
}
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies(int currentPosition, int amountToLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await MovieApi.NowPlaying(langCode, pagesToLoad.Page);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
return await TransformMovieResultsToResponse(results);
}
/// <summary>
/// Gets upcoming movies.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
{
var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () =>
{
var langCode = await DefaultLanguageCode(null);
return await MovieApi.Upcoming(langCode);
}, DateTime.Now.AddHours(12));
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies(int currentPosition, int amountToLoad)
{
var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems);
var results = new List<MovieSearchResult>();
foreach (var pagesToLoad in pages)
{
var apiResult = await MovieApi.Upcoming(langCode, pagesToLoad.Page);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
}
return await TransformMovieResultsToResponse(results);
}
/// <summary>
/// Gets now playing movies.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
{
var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () =>
{
var langCode = await DefaultLanguageCode(null);
return await MovieApi.NowPlaying(langCode);
}, DateTime.Now.AddHours(12));
if (result != null)
{
return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API
}
return null;
}
protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
IEnumerable<MovieSearchResult> movies)
{
var viewMovies = new List<SearchMovieViewModel>();
foreach (var movie in movies)
{
viewMovies.Add(await ProcessSingleMovie(movie));
}
return viewMovies;
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieSearchResult movie)
{
var viewMovie = Mapper.Map<SearchMovieViewModel>(movie);
return await ProcessSingleMovie(viewMovie);
}
private async Task<MovieFullInfoViewModel> ProcessSingleMovie(FullMovieInfo movie)
{
var viewMovie = Mapper.Map<SearchMovieViewModel>(movie);
@ -65,6 +258,50 @@ namespace Ombi.Core.Engine
return mapped;
}
private async Task<MovieCollectionsViewModel> ProcessCollection(Collections collection)
{
var viewMovie = Mapper.Map<MovieCollectionsViewModel>(collection);
foreach (var movie in viewMovie.Collection)
{
var mappedMovie = Mapper.Map<SearchMovieViewModel>(movie);
await RunSearchRules(mappedMovie);
// This requires the rules to be run first to populate the RequestId property
await CheckForSubscription(mappedMovie);
var mapped = Mapper.Map<MovieCollection>(movie);
mapped.Available = movie.Available;
mapped.RequestId = movie.RequestId;
mapped.Requested = movie.Requested;
mapped.PlexUrl = movie.PlexUrl;
mapped.EmbyUrl = movie.EmbyUrl;
mapped.Subscribed = movie.Subscribed;
mapped.ShowSubscribe = movie.ShowSubscribe;
mapped.ReleaseDate = movie.ReleaseDate;
}
return viewMovie;
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie)
{
if (viewMovie.ImdbId.IsNullOrEmpty())
{
var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id);
viewMovie.Id = showInfo.Id; // TheMovieDbId
viewMovie.ImdbId = showInfo.ImdbId;
}
viewMovie.TheMovieDbId = viewMovie.Id.ToString();
await RunSearchRules(viewMovie);
// This requires the rules to be run first to populate the RequestId property
await CheckForSubscription(viewMovie);
return viewMovie;
}
private async Task CheckForSubscription(SearchViewModel viewModel)
{
// Check if this user requested it

@ -0,0 +1,154 @@
using AutoMapper;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using TraktApiSharp.Objects.Get.Shows;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Repository.Requests;
using Ombi.Core.Authentication;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Api.Trakt;
using Ombi.Api.TvMaze;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine.V2
{
public class TvSearchEngineV2 : BaseMediaEngine, ITVSearchEngineV2
{
public TvSearchEngineV2(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um,
ICacheService memCache, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub)
: base(identity, service, r, um, memCache, s, sub)
{
TvMazeApi = tvMaze;
Mapper = mapper;
PlexSettings = plexSettings;
EmbySettings = embySettings;
PlexContentRepo = repo;
TraktApi = trakt;
EmbyContentRepo = embyRepo;
}
private ITvMazeApi TvMazeApi { get; }
private IMapper Mapper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexContentRepository PlexContentRepo { get; }
private IEmbyContentRepository EmbyContentRepo { get; }
private ITraktApi TraktApi { get; }
public async Task<SearchFullInfoTvShowViewModel> GetShowInformation(int tvdbid)
{
var tvdbshow = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid);
var show = await TvMazeApi.GetTvFullInformation(tvdbshow.id);
if (show == null)
{
// We don't have enough information
return null;
}
// Setup the task so we can get the data later on if we have a IMDBID
Task<TraktShow> traktInfoTask = new Task<TraktShow>(() => null);
if (show.externals?.imdb.HasValue() ?? false)
{
traktInfoTask = TraktApi.GetTvExtendedInfo(show.externals?.imdb);
}
var mapped = Mapper.Map<SearchFullInfoTvShowViewModel>(show);
foreach (var e in show._embedded.episodes)
{
var season = mapped.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == e.season);
if (season == null)
{
var newSeason = new SeasonRequests
{
SeasonNumber = e.season,
Episodes = new List<EpisodeRequests>()
};
newSeason.Episodes.Add(new EpisodeRequests
{
Url = e.url,
Title = e.name,
AirDate = e.airstamp,
EpisodeNumber = e.number,
});
mapped.SeasonRequests.Add(newSeason);
}
else
{
// We already have the season, so just add the episode
season.Episodes.Add(new EpisodeRequests
{
Url = e.url,
Title = e.name,
AirDate = e.airstamp,
EpisodeNumber = e.number,
});
}
}
return await ProcessResult(mapped, traktInfoTask);
}
private IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
{
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items)
{
retVal.Add(ProcessResult(tvMazeSearch));
}
return retVal;
}
private SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
{
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
}
private async Task<SearchFullInfoTvShowViewModel> ProcessResult(SearchFullInfoTvShowViewModel item, Task<TraktShow> showInfoTask)
{
item.TheTvDbId = item.Id.ToString();
var oldModel = Mapper.Map<SearchTvShowViewModel>(item);
await RunSearchRules(oldModel);
item.Available = oldModel.Available;
item.FullyAvailable = oldModel.FullyAvailable;
item.PartlyAvailable = oldModel.PartlyAvailable;
item.Requested = oldModel.Requested;
item.Available = oldModel.Available;
item.Approved = oldModel.Approved;
item.SeasonRequests = oldModel.SeasonRequests;
item.RequestId = oldModel.RequestId;
return await GetExtraInfo(showInfoTask, item);
}
private async Task<SearchFullInfoTvShowViewModel> GetExtraInfo(Task<TraktShow> showInfoTask, SearchFullInfoTvShowViewModel model)
{
var result = await showInfoTask;
if(result == null)
{
return model;
}
model.Trailer = result.Trailer;
model.Certification = result.Certification;
model.Homepage = result.Homepage;
return model;
}
}
}

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search.V2
{
public class CalendarViewModel
{
public string Title { get; set; }
public DateTime Start { get; set; }
public string BackgroundColor { get; set; }
public RequestType Type { get; set; }
public List<ExtraParams> ExtraParams { get; set; }
public string BorderColor
{
get
{
switch (Type)
{
case RequestType.TvShow:
return "#ff0000";
case RequestType.Movie:
return "#0d5a3e";
case RequestType.Album:
return "#797979";
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
public class ExtraParams
{
public int ProviderId { get; set; }
public string Overview { get; set; }
}
}

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search.V2
{
public class MovieCollectionsViewModel
{
public string Name { get; set; }
public string Overview { get; set; }
public List<MovieCollection> Collection { get; set; }
}
public class MovieCollection : SearchViewModel
{
public int Id { get; set; }
public string Overview { get; set; }
public string PosterPath { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public override RequestType Type => RequestType.Movie;
}
}

@ -8,6 +8,7 @@ namespace Ombi.Core.Models.Search.V2
public class MovieFullInfoViewModel : SearchViewModel
{
public bool Adult { get; set; }
public CollectionsViewModel BelongsToCollection { get; set; }
public string BackdropPath { get; set; }
public string OriginalLanguage { get; set; }
public int Budget { get; set; }
@ -39,8 +40,26 @@ namespace Ombi.Core.Models.Search.V2
public Similar Similar { get; set; }
public Recommendations Recommendations { get; set; }
public ExternalIds ExternalIds { get; set; }
public Keywords Keywords { get; set; }
}
public class Keywords
{
public List<KeywordsValue> KeywordsValue { get; set; }
}
public class KeywordsValue
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CollectionsViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string PosterPath { get; set; }
public string BackdropPath { get; set; }
}
public class ExternalIds
{
public string ImdbId { get; set; }

@ -0,0 +1,114 @@
using Ombi.Store.Repository.Requests;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search.V2
{
public class SearchFullInfoTvShowViewModel : SearchViewModel
{
public string Title { get; set; }
public List<string> Aliases { get; set; }
public string Banner { get; set; }
public int SeriesId { get; set; }
public string Status { get; set; }
public string FirstAired { get; set; }
public string NetworkId { get; set; }
public string Runtime { get; set; }
public List<string> Genre { get; set; }
public string Overview { get; set; }
public int LastUpdated { get; set; }
public string AirsDayOfWeek { get; set; }
public string AirsTime { get; set; }
public string Rating { get; set; }
public int SiteRating { get; set; }
public NetworkViewModel Network { get; set; }
public Images Images { get; set; }
public List<CastViewModel> Cast { get; set; }
public List<CrewViewModel> Crew { get; set; }
public string Certification { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Trailer { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Homepage { get; set; }
public List<SeasonRequests> SeasonRequests { get; set; } = new List<SeasonRequests>();
/// <summary>
/// If we are requesting the entire series
/// </summary>
public bool RequestAll { get; set; }
public bool FirstSeason { get; set; }
public bool LatestSeason { get; set; }
/// <summary>
/// This is where we have EVERY Episode for that series
/// </summary>
public bool FullyAvailable { get; set; }
// We only have some episodes
public bool PartlyAvailable { get; set; }
public override RequestType Type => RequestType.TvShow;
}
public class NetworkViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public Country Country { get; set; }
}
public class Country
{
public string Name { get; set; }
public string Code { get; set; }
public string Timezone { get; set; }
}
public class Images
{
public string Medium { get; set; }
public string Original { get; set; }
}
public class CastViewModel
{
public PersonViewModel Person { get; set; }
public CharacterViewModel Character { get; set; }
public bool Self { get; set; }
public bool Voice { get; set; }
}
public class PersonViewModel
{
public int Id { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public Images Image { get; set; }
}
public class CharacterViewModel
{
public int Id { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public Images Image { get; set; }
}
public class CrewViewModel
{
public string Type { get; set; }
public PersonViewModel Person { get; set; }
}
}

@ -0,0 +1,23 @@

using System.Collections.Generic;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.UI
{
/// <summary>
/// The view model for the notification settings page
/// </summary>
/// <seealso cref="GotifyNotificationSettings" />
public class GotifyNotificationViewModel : GotifySettings
{
/// <summary>
/// Gets or sets the notification templates.
/// </summary>
/// <value>
/// The notification templates.
/// </value>
public List<NotificationTemplates> NotificationTemplates { get; set; }
}
}

@ -11,10 +11,9 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
<PackageReference Include="Hangfire" Version="1.6.21" />
<PackageReference Include="Hangfire" Version="1.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
</ItemGroup>

@ -1,5 +1,7 @@
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
@ -10,28 +12,31 @@ namespace Ombi.Core.Rule.Rules.Request
{
public class AutoApproveRule : BaseRequestRule, IRules<BaseRequest>
{
public AutoApproveRule(IPrincipal principal)
public AutoApproveRule(IPrincipal principal, OmbiUserManager um)
{
User = principal;
_manager = um;
}
private IPrincipal User { get; }
private readonly OmbiUserManager _manager;
public Task<RuleResult> Execute(BaseRequest obj)
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiRoles.Admin))
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
{
obj.Approved = true;
return Task.FromResult(Success());
return Success();
}
if (obj.RequestType == RequestType.Movie && User.IsInRole(OmbiRoles.AutoApproveMovie))
if (obj.RequestType == RequestType.Movie && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
obj.Approved = true;
if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv))
if (obj.RequestType == RequestType.TvShow && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
obj.Approved = true;
if (obj.RequestType == RequestType.Album && User.IsInRole(OmbiRoles.AutoApproveMusic))
if (obj.RequestType == RequestType.Album && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
obj.Approved = true;
return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve
return Success(); // We don't really care, we just don't set the obj to approve
}
}
}

@ -1,46 +1,52 @@
using Ombi.Store.Entities;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Rule.Rules
namespace Ombi.Core.Rule.Rules.Request
{
public class CanRequestRule : BaseRequestRule, IRules<BaseRequest>
{
public CanRequestRule(IPrincipal principal)
public CanRequestRule(IPrincipal principal, OmbiUserManager manager)
{
User = principal;
_manager = manager;
}
private IPrincipal User { get; }
private readonly OmbiUserManager _manager;
public Task<RuleResult> Execute(BaseRequest obj)
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiRoles.Admin))
return Task.FromResult(Success());
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
return Success();
if (obj.RequestType == RequestType.Movie)
{
if (User.IsInRole(OmbiRoles.RequestMovie) || User.IsInRole(OmbiRoles.AutoApproveMovie))
return Task.FromResult(Success());
return Task.FromResult(Fail("You do not have permissions to Request a Movie"));
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
return Success();
return Fail("You do not have permissions to Request a Movie");
}
if (obj.RequestType == RequestType.TvShow)
{
if (User.IsInRole(OmbiRoles.RequestTv) || User.IsInRole(OmbiRoles.AutoApproveTv))
return Task.FromResult(Success());
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestTv) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
return Success();
}
if (obj.RequestType == RequestType.Album)
{
if (User.IsInRole(OmbiRoles.RequestMusic) || User.IsInRole(OmbiRoles.AutoApproveMusic))
return Task.FromResult(Success());
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMusic) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
return Success();
}
return Task.FromResult(Fail("You do not have permissions to Request a TV Show"));
return Fail("You do not have permissions to Request a TV Show");
}
}
}

@ -1,6 +1,8 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
@ -28,8 +30,24 @@ namespace Ombi.Core.Rule.Rules.Request
{
var movie = (MovieRequests) obj;
var movieRequests = Movie.GetAll();
var found = false;
var existing = await movieRequests.FirstOrDefaultAsync(x => x.TheMovieDbId == movie.TheMovieDbId);
if (existing != null) // Do we already have a request for this?
{
found = true;
}
if (!found && movie.ImdbId.HasValue())
{
// Let's check imdbid
existing = await movieRequests.FirstOrDefaultAsync(x =>
x.ImdbId.Equals(movie.ImdbId, StringComparison.CurrentCultureIgnoreCase));
if (existing != null)
{
found = true;
}
}
if(found)
{
return Fail($"\"{obj.Title}\" has already been requested");
}

@ -32,7 +32,7 @@ namespace Ombi.Core.Rule.Rules.Request
var tvContent = _plexContent.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Show);
// We need to do a check on the TVDBId
var anyTvDbMatches = await tvContent.Include(x => x.Episodes).FirstOrDefaultAsync(x => x.HasTvDb && x.TvDbId.Equals(tvRequest.Id.ToString())); // the Id on the child is the tvdbid at this point
var anyTvDbMatches = await tvContent.Include(x => x.Episodes).FirstOrDefaultAsync(x => x.HasTvDb && x.TvDbId.Equals(tvRequest.Id.ToString(), StringComparison.InvariantCultureIgnoreCase)); // the Id on the child is the tvdbid at this point
if (anyTvDbMatches == null)
{
// So we do not have a TVDB Id, that really sucks.
@ -42,7 +42,7 @@ namespace Ombi.Core.Rule.Rules.Request
&& x.ReleaseYear == tvRequest.ReleaseYear.Year.ToString());
if (titleAndYearMatch != null)
{
// We have a match! Suprise Motherfucker
// We have a match! Surprise Motherfucker
return CheckExistingContent(tvRequest, titleAndYearMatch);
}

@ -2,7 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository.Requests;
@ -13,6 +13,18 @@ namespace Ombi.Core.Rule.Rules.Search
{
public static void CheckForUnairedEpisodes(SearchTvShowViewModel search)
{
foreach (var season in search.SeasonRequests)
{
// If we have all the episodes for this season, then this season is available
if (season.Episodes.All(x => x.Available))
{
season.SeasonAvailable = true;
}
}
if(search.SeasonRequests.Any(x => x.Episodes.Any(e => e.Available)))
{
search.PartlyAvailable = true;
}
if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available)))
{
search.FullyAvailable = true;
@ -24,7 +36,7 @@ namespace Ombi.Core.Rule.Rules.Search
if (!airedButNotAvailable)
{
var unairedEpisodes = search.SeasonRequests.Any(x =>
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date));
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date || c.AirDate != DateTime.MinValue));
if (unairedEpisodes)
{
search.FullyAvailable = true;
@ -34,28 +46,36 @@ namespace Ombi.Core.Rule.Rules.Search
}
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<PlexEpisode> allEpisodes, EpisodeRequests episode,
SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb)
SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb, ILogger log)
{
PlexEpisode epExists = null;
if (useImdb)
try
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ImdbId == item.ImdbId.ToString());
}
if (useTheMovieDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TheMovieDbId == item.TheMovieDbId.ToString());
}
if (useImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ImdbId == item.ImdbId);
}
if (useTvDb)
if (useTheMovieDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TheMovieDbId == item.TheMovieDbId);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId);
}
}
catch (Exception e)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId.ToString());
log.LogError(e, "Exception thrown when attempting to check if something is available");
}
if (epExists != null)
@ -71,21 +91,21 @@ namespace Ombi.Core.Rule.Rules.Search
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ImdbId == item.ImdbId.ToString());
x.Series.ImdbId == item.ImdbId);
}
if (useTheMovieDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TheMovieDbId == item.TheMovieDbId.ToString());
x.Series.TheMovieDbId == item.TheMovieDbId);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId.ToString());
x.Series.TvDbId == item.TvDbId);
}
if (epExists != null)

@ -3,6 +3,8 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
@ -11,12 +13,14 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class EmbyAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{
public EmbyAvailabilityRule(IEmbyContentRepository repo)
public EmbyAvailabilityRule(IEmbyContentRepository repo, ISettingsService<EmbySettings> s)
{
EmbyContentRepository = repo;
EmbySettings = s;
}
private IEmbyContentRepository EmbyContentRepository { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
public async Task<RuleResult> Execute(SearchViewModel obj)
{
@ -60,7 +64,19 @@ namespace Ombi.Core.Rule.Rules.Search
if (item != null)
{
obj.Available = true;
obj.EmbyUrl = item.Url;
var s = await EmbySettings.GetSettingsAsync();
if (s.Enable)
{
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
if ((server?.ServerHostname ?? string.Empty).HasValue())
{
obj.EmbyUrl = $"{server.ServerHostname}#!/itemdetails.html?id={item.EmbyId}";
}
else
{
obj.EmbyUrl = $"https://app.emby.media/#!/itemdetails.html?id={item.EmbyId}";
}
}
if (obj.Type == RequestType.TvShow)
{

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
@ -29,7 +30,6 @@ namespace Ombi.Core.Rule.Rules.Search
var movieRequests = await Movie.GetRequestAsync(obj.Id);
if (movieRequests != null) // Do we already have a request for this?
{
obj.Requested = true;
obj.RequestId = movieRequests.Id;
obj.Approved = movieRequests.Approved;
@ -41,22 +41,11 @@ namespace Ombi.Core.Rule.Rules.Search
}
if (obj.Type == RequestType.TvShow)
{
//var tvRequests = Tv.GetRequest(obj.Id);
//if (tvRequests != null) // Do we already have a request for this?
//{
// obj.Requested = true;
// obj.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
// obj.Available = tvRequests.ChildRequests.Any(x => x.Available);
// return Task.FromResult(Success());
//}
var request = (SearchTvShowViewModel)obj;
var tvRequests = Tv.GetRequest(obj.Id);
if (tvRequests != null) // Do we already have a request for this?
{
request.RequestId = tvRequests.Id;
request.Requested = true;
request.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
@ -87,11 +76,11 @@ namespace Ombi.Core.Rule.Rules.Search
}
}
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Available)))
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Available && e.AirDate > DateTime.MinValue)))
{
request.FullyAvailable = true;
}
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available)))
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available && e.AirDate > DateTime.MinValue)))
{
request.PartlyAvailable = true;
}

@ -1,5 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
@ -10,12 +11,14 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class PlexAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{
public PlexAvailabilityRule(IPlexContentRepository repo)
public PlexAvailabilityRule(IPlexContentRepository repo, ILogger<PlexAvailabilityRule> log)
{
PlexContentRepository = repo;
Log = log;
}
private IPlexContentRepository PlexContentRepository { get; }
private ILogger Log { get; }
public async Task<RuleResult> Execute(SearchViewModel obj)
{
@ -72,7 +75,7 @@ namespace Ombi.Core.Rule.Rules.Search
{
foreach (var episode in season.Episodes)
{
await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb);
await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb, Log);
}
}

@ -49,7 +49,6 @@ namespace Ombi.Core.Senders
{
try
{
var cpSettings = await CouchPotatoSettings.GetSettingsAsync();
//var watcherSettings = await WatcherSettings.GetSettingsAsync();
var radarrSettings = await RadarrSettings.GetSettingsAsync();
@ -76,7 +75,7 @@ namespace Ombi.Core.Senders
}
catch (Exception e)
{
Log.LogError(e, "Error when seing movie to DVR app, added to the request queue");
Log.LogError(e, "Error when sending movie to DVR app, added to the request queue");
// Check if already in request quee
var existingQueue = await _requestQueuRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);

@ -16,6 +16,7 @@ using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Remotion.Linq.Parsing.Structure.IntermediateModel;
namespace Ombi.Core.Senders
{
@ -57,7 +58,7 @@ namespace Ombi.Core.Senders
var sonarr = await SonarrSettings.GetSettingsAsync();
if (sonarr.Enabled)
{
var result = await SendToSonarr(model);
var result = await SendToSonarr(model, sonarr);
if (result != null)
{
return new SenderResult
@ -109,7 +110,7 @@ namespace Ombi.Core.Senders
catch (Exception e)
{
Logger.LogError(e, "Exception thrown when sending a movie to DVR app, added to the request queue");
// Check if already in request quee
// Check if already in request queue
var existingQueue = await _requestQueueRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);
if (existingQueue != null)
{
@ -134,7 +135,7 @@ namespace Ombi.Core.Senders
return new SenderResult
{
Success = false,
Message = "Something wen't wrong!"
Message = "Something went wrong!"
};
}
@ -150,13 +151,8 @@ namespace Ombi.Core.Senders
/// <param name="s"></param>
/// <param name="model"></param>
/// <returns></returns>
public async Task<NewSeries> SendToSonarr(ChildRequests model)
public async Task<NewSeries> SendToSonarr(ChildRequests model, SonarrSettings s)
{
var s = await SonarrSettings.GetSettingsAsync();
if (!s.Enabled)
{
return null;
}
if (string.IsNullOrEmpty(s.ApiKey))
{
return null;
@ -319,10 +315,19 @@ namespace Ombi.Core.Senders
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber);
var sonarrEpCount = sonarrSeason.Count();
var sonarrEpisodeList = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber).ToList();
var sonarrEpCount = sonarrEpisodeList.Count;
var ourRequestCount = season.Episodes.Count;
var ourEpisodes = season.Episodes.Select(x => x.EpisodeNumber).ToList();
var unairedEpisodes = sonarrEpisodeList.Where(x => x.airDateUtc > DateTime.UtcNow).Select(x => x.episodeNumber).ToList();
//// Check if we have requested all the latest episodes, if we have then monitor
//// NOTE, not sure if needed since ombi ui displays future episodes anyway...
//ourEpisodes.AddRange(unairedEpisodes);
//var distinctEpisodes = ourEpisodes.Distinct().ToList();
//var missingEpisodes = Enumerable.Range(distinctEpisodes.Min(), distinctEpisodes.Count).Except(distinctEpisodes);
var existingSeason =
result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
if (existingSeason == null)
@ -332,7 +337,7 @@ namespace Ombi.Core.Senders
}
if (sonarrEpCount == ourRequestCount)
if (sonarrEpCount == ourRequestCount /*|| !missingEpisodes.Any()*/)
{
// We have the same amount of requests as all of the episodes in the season.

@ -33,6 +33,7 @@ using Ombi.Api.CouchPotato;
using Ombi.Api.DogNzb;
using Ombi.Api.FanartTv;
using Ombi.Api.Github;
using Ombi.Api.Gotify;
using Ombi.Api.Lidarr;
using Ombi.Api.Mattermost;
using Ombi.Api.Notifications;
@ -51,16 +52,15 @@ using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Sonarr;
using Ombi.Store.Repository.Requests;
using Ombi.Updater;
using PlexContentCacher = Ombi.Schedule.Jobs.Plex;
using Ombi.Api.Telegram;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Demo;
using Ombi.Core.Engine.V2;
using Ombi.Core.Processor;
using Ombi.Schedule.Jobs.Lidarr;
using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Schedule.Jobs.SickRage;
using Ombi.Schedule.Processor;
using Ombi.Store.Entities;
namespace Ombi.DependencyInjection
{
@ -95,12 +95,16 @@ namespace Ombi.DependencyInjection
services.AddTransient<IMassEmailSender, MassEmailSender>();
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
services.AddTransient<IVoteEngine, VoteEngine>();
services.AddTransient<IDemoMovieSearchEngine, DemoMovieSearchEngine>();
services.AddTransient<IDemoTvSearchEngine, DemoTvSearchEngine>();
}
public static void RegisterEnginesV2(this IServiceCollection services)
{
services.AddTransient<IMultiSearchEngine, MultiSearchEngine>();
services.AddTransient<IMovieEngineV2, MovieSearchEngineV2>();
services.AddTransient<ITVSearchEngineV2, TvSearchEngineV2>();
services.AddTransient<ICalendarEngine, CalendarEngine>();
}
public static void RegisterHttp(this IServiceCollection services)
@ -127,6 +131,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IOmbiService, OmbiService>();
services.AddTransient<IFanartTvApi, FanartTvApi>();
services.AddTransient<IPushoverApi, PushoverApi>();
services.AddTransient<IGotifyApi, GotifyApi>();
services.AddTransient<IMattermostApi, MattermostApi>();
services.AddTransient<ICouchPotatoApi, CouchPotatoApi>();
services.AddTransient<IDogNzbApi, DogNzbApi>();
@ -139,28 +144,28 @@ namespace Ombi.DependencyInjection
}
public static void RegisterStore(this IServiceCollection services) {
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>();
services.AddEntityFrameworkSqlite().AddDbContext<SettingsContext>();
services.AddEntityFrameworkSqlite().AddDbContext<ExternalContext>();
services.AddDbContext<OmbiContext>();
services.AddDbContext<SettingsContext>();
services.AddDbContext<ExternalContext>();
services.AddScoped<IOmbiContext, OmbiContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddScoped<ISettingsContext, SettingsContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddScoped<IExternalContext, ExternalContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
services.AddTransient<ISettingsResolver, SettingsResolver>();
services.AddTransient<IPlexContentRepository, PlexServerContentRepository>();
services.AddTransient<IEmbyContentRepository, EmbyContentRepository>();
services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddScoped<ISettingsRepository, SettingsJsonRepository>();
services.AddScoped<ISettingsResolver, SettingsResolver>();
services.AddScoped<IPlexContentRepository, PlexServerContentRepository>();
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddTransient<ITvRequestRepository, TvRequestRepository>();
services.AddTransient<IMovieRequestRepository, MovieRequestRepository>();
services.AddTransient<IMusicRequestRepository, MusicRequestRepository>();
services.AddTransient<IAuditRepository, AuditRepository>();
services.AddTransient<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddTransient<ITokenRepository, TokenRepository>();
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>));
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
services.AddTransient(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
services.AddScoped<ITvRequestRepository, TvRequestRepository>();
services.AddScoped<IMovieRequestRepository, MovieRequestRepository>();
services.AddScoped<IMusicRequestRepository, MusicRequestRepository>();
services.AddScoped<IAuditRepository, AuditRepository>();
services.AddScoped<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddScoped<ITokenRepository, TokenRepository>();
services.AddScoped(typeof(ISettingsService<>), typeof(SettingsService<>));
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
}
public static void RegisterServices(this IServiceCollection services)
{
@ -168,7 +173,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<INotificationService, NotificationService>();
services.AddTransient<IEmailProvider, GenericEmailProvider>();
services.AddTransient<INotificationHelper, NotificationHelper>();
services.AddTransient<ICacheService, CacheService>();
services.AddSingleton<ICacheService, CacheService>();
services.AddTransient<IDiscordNotification, DiscordNotification>();
services.AddTransient<IEmailNotification, EmailNotification>();
@ -177,6 +182,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ISlackNotification, SlackNotification>();
services.AddTransient<IMattermostNotification, MattermostNotification>();
services.AddTransient<IPushoverNotification, PushoverNotification>();
services.AddTransient<IGotifyNotification, GotifyNotification>();
services.AddTransient<ITelegramNotification, TelegramNotification>();
services.AddTransient<IMobileNotification, MobileNotification>();
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();

@ -0,0 +1,28 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Helpers.Tests
{
[TestFixture]
public class EmbyHelperTests
{
[TestCaseSource(nameof(UrlData))]
public string TestUrl(string mediaId, string url)
{
return EmbyHelper.GetEmbyMediaUrl(mediaId, url);
}
public static IEnumerable<TestCaseData> UrlData
{
get
{
var mediaId = 1;
yield return new TestCaseData(mediaId.ToString(), "http://google.com").Returns($"http://google.com/#!/itemdetails.html?id={mediaId}").SetName("EmbyHelper_GetMediaUrl_WithCustomDomain_WithoutTrailingSlash");
yield return new TestCaseData(mediaId.ToString(), "http://google.com/").Returns($"http://google.com/#!/itemdetails.html?id={mediaId}").SetName("EmbyHelper_GetMediaUrl_WithCustomDomain");
yield return new TestCaseData(mediaId.ToString(), "https://google.com/").Returns($"https://google.com/#!/itemdetails.html?id={mediaId}").SetName("EmbyHelper_GetMediaUrl_WithCustomDomain_Https");
}
}
}
}

@ -0,0 +1,41 @@
using NUnit.Framework;
using System.Collections.Generic;
namespace Ombi.Helpers.Tests
{
[TestFixture]
public class HtmlHelperTests
{
[TestCaseSource(nameof(HtmlData))]
public string RemoveHtmlTests(string input)
{
return HtmlHelper.RemoveHtml(input);
}
public static IEnumerable<TestCaseData> HtmlData
{
get
{
yield return new TestCaseData("<h1>hi</h1>").Returns("hi").SetName("Simple Html");
yield return new TestCaseData("<html><body><head></head><h1>hi</h1></body></html>").Returns("hi").SetName("Nested text inside Html");
yield return new TestCaseData("there is no html here").Returns("there is no html here").SetName("No Html");
yield return new TestCaseData("there is <b>some</b> html here").Returns("there is some html here").SetName("Html in middle");
yield return new TestCaseData("<a>there</a> <u>is</u> <b>lots</b> <i>html</i> <span>here</span>").Returns("there is lots html here").SetName("Html in everywhere");
yield return new TestCaseData("there is <span class=\"abc\">some</span> html here").Returns("there is some html here").SetName("Html in with classes");
yield return new TestCaseData("there is <span id=\"sometag\">some</span> html here").Returns("there is some html here").SetName("Html in with attribute");
yield return new TestCaseData("there is <span data-tag=\"sometag\" class=\"abc\">some</span> html here").Returns("there is some html here").SetName("Html in with attribute and class");
}
}
public static IEnumerable<TestCaseData> OtherData
{
get
{
foreach (var data in HtmlData)
{
yield return data;
}
yield return new TestCaseData("xyz").Returns("xyz").SetName("More Tests");
}
}
}
}

@ -8,8 +8,8 @@
<ItemGroup>
<PackageReference Include="nunit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
</ItemGroup>
<ItemGroup>

@ -0,0 +1,120 @@
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Ombi.Helpers.Tests
{
[TestFixture]
public class PaginationHelperTests
{
[TestCaseSource(nameof(TestPageData))]
public void TestPaginationPages(int currentlyLoaded, int toLoad, int maxItemsPerPage, int[] expectedPages)
{
var result = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, maxItemsPerPage);
var pages = result.Select(x => x.Page).ToArray();
Assert.That(pages.Length, Is.EqualTo(expectedPages.Length), "Did not contain the correct amount of pages");
for (var i = 0; i < pages.Length; i++)
{
Assert.That(pages[i], Is.EqualTo(expectedPages[i]));
}
}
public static IEnumerable<TestCaseData> TestPageData
{
get
{
yield return new TestCaseData(0, 10, 20, new [] { 1 }).SetName("Pagination_Load_First_Page");
yield return new TestCaseData(20, 10, 20, new [] { 2 }).SetName("Pagination_Load_Second_Page");
yield return new TestCaseData(0, 20, 20, new [] { 1 }).SetName("Pagination_Load_Full_First_Page");
yield return new TestCaseData(20, 20, 20, new [] { 2 }).SetName("Pagination_Load_Full_Second_Page");
yield return new TestCaseData(10, 20, 20, new [] { 1, 2 }).SetName("Pagination_Load_Half_First_Page_And_Half_Second_Page");
yield return new TestCaseData(19, 20, 20, new[] { 1, 2 }).SetName("Pagination_Load_End_First_Page_And_Most_Second_Page");
yield return new TestCaseData(19, 40, 20, new[] { 1, 2, 3 }).SetName("Pagination_Load_End_First_Page_And_Most_Second_And_Third_Page");
yield return new TestCaseData(10, 10, 20, new[] { 1 }).SetName("Pagination_Load_Half_First_Page");
yield return new TestCaseData(10, 9, 20, new[] { 1 }).SetName("Pagination_Load_LessThan_Half_First_Page");
yield return new TestCaseData(20, 10, 20, new[] { 2 }).SetName("Pagination_Load_Half_Second_Page");
yield return new TestCaseData(20, 9, 20, new[] { 2 }).SetName("Pagination_Load_LessThan_Half_Second_Page");
yield return new TestCaseData(30, 10, 20, new[] { 2 }).SetName("Pagination_Load_All_Second_Page_With_Half_Take");
yield return new TestCaseData(49, 1, 50, new[] { 1 }).SetName("Pagination_Load_49_OutOf_50");
yield return new TestCaseData(49, 1, 100,new[] { 1 }).SetName("Pagination_Load_50_OutOf_100");
}
}
[TestCaseSource(nameof(CurrentPositionTestData))]
public void TestCurrentPositionOfPagination(int currentlyLoaded, int toLoad, int maxItemsPerPage, int expectedTake, int expectedSkip)
{
var result = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, maxItemsPerPage);
var first = result.FirstOrDefault();
Assert.That(first.Take, Is.EqualTo(expectedTake));
Assert.That(first.Skip, Is.EqualTo(expectedSkip));
}
public static IEnumerable<TestCaseData> CurrentPositionTestData
{
get
{
yield return new TestCaseData(0, 10, 20, 10, 0).SetName("PaginationPosition_Load_First_Half_Of_Page");
yield return new TestCaseData(10, 10, 20, 10, 10).SetName("PaginationPosition_Load_EndHalf_First_Page");
yield return new TestCaseData(19, 1, 20, 1, 19).SetName("PaginationPosition_Load_LastItem_Of_First_Page");
yield return new TestCaseData(20, 20, 300, 20, 20).SetName("PaginationPosition_Load_Full_Second_Page");
}
}
[TestCaseSource(nameof(CurrentPositionMultiplePagesTestData))]
public void TestCurrentPositionOfPaginationWithMultiplePages(int currentlyLoaded, int toLoad, int maxItemsPerPage, List<MultiplePagesTestData> data)
{
var result = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, maxItemsPerPage);
foreach (var r in result)
{
// get result data for this page
var expectedPage = data.FirstOrDefault(x => x.Page == r.Page);
Assert.That(r.Take, Is.EqualTo(expectedPage.ExpectedTake));
Assert.That(r.Skip, Is.EqualTo(expectedPage.ExpectedSkip));
}
}
public static IEnumerable<TestCaseData> CurrentPositionMultiplePagesTestData
{
get
{
yield return new TestCaseData(10, 20, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(1, 10, 10), new MultiplePagesTestData(2, 10, 0) })
.SetName("PaginationPosition_Load_SecondHalf_FirstPage_FirstHalf_SecondPage");
yield return new TestCaseData(0, 40, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(1, 20, 0), new MultiplePagesTestData(2, 20, 0) })
.SetName("PaginationPosition_Load_Full_First_And_SecondPage");
yield return new TestCaseData(35, 15, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(2, 5, 15), new MultiplePagesTestData(3, 10, 0) })
.SetName("PaginationPosition_Load_EndSecondPage_Beginning_ThirdPage");
yield return new TestCaseData(18, 22, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(1, 2, 18), new MultiplePagesTestData(2, 20, 0) })
.SetName("PaginationPosition_Load_EndFirstPage_Full_SecondPage");
yield return new TestCaseData(38, 4, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(2, 2, 18), new MultiplePagesTestData(3, 2, 0) })
.SetName("PaginationPosition_Load_EndSecondPage_Some_ThirdPage");
yield return new TestCaseData(15, 20, 10, new List<MultiplePagesTestData> { new MultiplePagesTestData(2, 5, 5), new MultiplePagesTestData(3, 10, 0), new MultiplePagesTestData(4, 5, 0) })
.SetName("PaginationPosition_Load_EndSecondPage_All_ThirdPage_Some_ForthPage");
yield return new TestCaseData(24, 12, 12, new List<MultiplePagesTestData> { new MultiplePagesTestData(3, 12, 0) })
.SetName("PaginationPosition_Load_ThirdPage_Of_12");
yield return new TestCaseData(12, 12, 12, new List<MultiplePagesTestData> { new MultiplePagesTestData(2, 12, 0) })
.SetName("PaginationPosition_Load_SecondPage_Of_12");
yield return new TestCaseData(40, 20, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(3, 20, 0) })
.SetName("PaginationPosition_Load_FullThird_Page");
yield return new TestCaseData(240, 12, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(13, 12, 0) })
.SetName("PaginationPosition_Load_Page_13");
}
}
public class MultiplePagesTestData
{
public MultiplePagesTestData(int page, int take, int skip)
{
Page = page;
ExpectedTake = take;
ExpectedSkip = skip;
}
public int Page { get; set; }
public int ExpectedTake { get; set; }
public int ExpectedSkip { get; set; }
}
}
}

@ -0,0 +1,115 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Ombi.Helpers.Tests
{
[TestFixture]
public class UriHelperTests
{
[TestCaseSource(nameof(UrlData))]
public string ReturnUri(string uri)
{
return UriHelper.ReturnUri(uri).ToString();
}
public static IEnumerable<TestCaseData> UrlData
{
get
{
yield return new TestCaseData("https://google.com/").Returns($"https://google.com/").SetName("ReturnUri_With_HttpScheme");
yield return new TestCaseData("google.com/").Returns($"http://google.com/").SetName("ReturnUri_HttpScheme_Not_Provided");
yield return new TestCaseData("http://google.com:9090/").Returns($"http://google.com:9090/").SetName("ReturnUri_WithPort");
yield return new TestCaseData("https://google.com/").Returns($"https://google.com/").SetName("ReturnUri_With_HttpsScheme");
yield return new TestCaseData("https://hi.google.com/").Returns($"https://hi.google.com/").SetName("ReturnUri_With_SubDomain");
yield return new TestCaseData("https://google.com/hi").Returns($"https://google.com/hi").SetName("ReturnUri_With_Path");
yield return new TestCaseData("https://hi.google.com/hi").Returns($"https://hi.google.com/hi").SetName("ReturnUri_With_Path_And_SubDomain");
}
}
[TestCaseSource(nameof(UrlWithPortData))]
public string ReturnUriWithPort(string uri, int port)
{
return UriHelper.ReturnUri(uri, port).ToString();
}
public static IEnumerable<TestCaseData> UrlWithPortData
{
get
{
yield return new TestCaseData("https://google.com", 443).Returns($"https://google.com/").SetName("ReturnUri_With_HttpsPort");
yield return new TestCaseData("https://google.com/", 123).Returns($"https://google.com:123/").SetName("ReturnUri_With_HttpScheme_With_Port");
yield return new TestCaseData("google.com/", 80).Returns($"http://google.com/").SetName("ReturnUri_HttpScheme_Not_Provided_With_Port");
yield return new TestCaseData("https://google.com/", 7000).Returns($"https://google.com:7000/").SetName("ReturnUri_With_HttpsScheme_With_Port");
yield return new TestCaseData("https://hi.google.com/", 1).Returns($"https://hi.google.com:1/").SetName("ReturnUri_With_SubDomain_With_Port");
yield return new TestCaseData("https://google.com/hi", 443).Returns($"https://google.com/hi").SetName("ReturnUri_With_Path_With_Port");
yield return new TestCaseData("https://hi.google.com/hi", 443).Returns($"https://hi.google.com/hi").SetName("ReturnUri_With_Path_And_SubDomain_With_Port");
}
}
[TestCaseSource(nameof(UrlWithPortWithSSLData))]
public string ReturnUriWithPortAndSSL(string uri, int port, bool ssl)
{
return UriHelper.ReturnUri(uri, port, ssl).ToString();
}
public static IEnumerable<TestCaseData> UrlWithPortWithSSLData
{
get
{
foreach (var d in UrlWithPortData)
{
var expected = (string)d.ExpectedResult;
var args = d.Arguments.ToList();
args.Add(true);
var newExpected = expected.Replace("http://", "https://");
if (args.Contains(80))
{
newExpected = expected;
}
d.TestName += "_With_SSL";
yield return new TestCaseData(args.ToArray()).Returns(newExpected).SetName(d.TestName);
}
}
}
[TestCaseSource(nameof(UrlWithPortWithSSLDataCasing))]
public string ReturnUriWithPortAndSSLCasing(string uri, int port, bool ssl)
{
return UriHelper.ReturnUri(uri, port, ssl).ToString();
}
public static IEnumerable<TestCaseData> UrlWithPortWithSSLDataCasing
{
get
{
foreach (var d in UrlWithPortData)
{
if (d.TestName.Contains("_Path_"))
{
continue;
}
var expected = (string)d.ExpectedResult;
var args = d.Arguments.ToList();
for (int i = 0; i < args.Count; i++)
{
if(args[i] is string)
{
args[i] = ((string)args[i]).ToUpper();
}
}
args.Add(true);
var newExpected = expected.Replace("http://", "https://");
if (args.Contains(80))
{
newExpected = expected;
}
d.TestName += "_With_SSL_ToUpper";
yield return new TestCaseData(args.ToArray()).Returns(newExpected).SetName(d.TestName);
}
}
}
}
}

@ -28,7 +28,7 @@ namespace Ombi.Helpers
return result;
}
using (await _mutex.LockAsync())
//using (await _mutex.LockAsync())
{
if (_memoryCache.TryGetValue(cacheKey, out result))
{

@ -1,46 +0,0 @@
using System;
using System.Security.Claims;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Ombi.Helpers
{
public class ClaimConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(System.Security.Claims.Claim));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var claim = (System.Security.Claims.Claim)value;
JObject jo = new JObject();
jo.Add("Type", claim.Type);
jo.Add("Value", IsJson(claim.Value) ? new JRaw(claim.Value) : new JValue(claim.Value));
jo.Add("ValueType", claim.ValueType);
jo.Add("Issuer", claim.Issuer);
jo.Add("OriginalIssuer", claim.OriginalIssuer);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string type = (string)jo["Type"];
JToken token = jo["Value"];
string value = token.Type == JTokenType.String ? (string)token : token.ToString(Formatting.None);
string valueType = (string)jo["ValueType"];
string issuer = (string)jo["Issuer"];
string originalIssuer = (string)jo["OriginalIssuer"];
return new Claim(type, value, valueType, issuer, originalIssuer);
}
private bool IsJson(string val)
{
return (val != null &&
(val.StartsWith("[") && val.EndsWith("]")) ||
(val.StartsWith("{") && val.EndsWith("}")));
}
}
}

@ -0,0 +1,11 @@
namespace Ombi.Config
{
public class DemoLists
{
public bool Enabled { get; set; }
public int[] Movies { get; set; }
public int[] TvShows { get; set; }
}
}

@ -0,0 +1,13 @@
namespace Ombi.Helpers
{
public class DemoSingleton
{
private static DemoSingleton instance;
private DemoSingleton() { }
public static DemoSingleton Instance => instance ?? (instance = new DemoSingleton());
public bool Demo { get; set; }
}
}

@ -1,9 +1,4 @@
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Helpers
namespace Ombi.Helpers
{
public class EmbyHelper
{
@ -11,6 +6,10 @@ namespace Ombi.Helpers
{
if (customerServerUrl.HasValue())
{
if(!customerServerUrl.EndsWith("/"))
{
return $"{customerServerUrl}/#!/itemdetails.html?id={mediaId}";
}
return $"{customerServerUrl}#!/itemdetails.html?id={mediaId}";
}
else

@ -14,6 +14,5 @@ namespace Ombi.Helpers
var step2 = Regex.Replace(step1, @"\s{2,}", " ");
return step2;
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ombi.Helpers
{
@ -21,5 +22,31 @@ namespace Ombi.Helpers
{
return new HashSet<T>(source, comparer);
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
{
return source.Shuffle(new Random());
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (rng == null) throw new ArgumentNullException(nameof(rng));
return source.ShuffleIterator(rng);
}
private static IEnumerable<T> ShuffleIterator<T>(
this IEnumerable<T> source, Random rng)
{
var buffer = source.ToList();
for (int i = 0; i < buffer.Count; i++)
{
int j = rng.Next(i, buffer.Count);
yield return buffer[j];
buffer[j] = buffer[i];
}
}
}
}

@ -32,6 +32,7 @@ namespace Ombi.Helpers
public static EventId MattermostNotification => new EventId(4004);
public static EventId PushoverNotification => new EventId(4005);
public static EventId TelegramNotifcation => new EventId(4006);
public static EventId GotifyNotification => new EventId(4007);
public static EventId TvSender => new EventId(5000);
public static EventId SonarrSender => new EventId(5001);

@ -10,5 +10,6 @@
Slack = 5,
Mattermost = 6,
Mobile = 7,
Gotify = 8,
}
}

@ -15,5 +15,6 @@
public const string Disabled = nameof(Disabled);
public const string ReceivesNewsletter = nameof(ReceivesNewsletter);
public const string ManageOwnRequests = nameof(ManageOwnRequests);
public const string EditCustomPage = nameof(EditCustomPage);
}
}

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Helpers
{
public static class PaginationHelper
{
public static List<PagesToLoad> GetNextPages(int currentlyLoaded, int toTake, int maxItemsPerPage)
{
var result = new List<PagesToLoad>();
var firstPage = currentlyLoaded / maxItemsPerPage + 1;
var startPos = currentlyLoaded % maxItemsPerPage + 1;
var lastItemIndex = currentlyLoaded + toTake - 1;
var lastPage = lastItemIndex / maxItemsPerPage + 1;
var stopPos = lastItemIndex % maxItemsPerPage + 1;
while (currentlyLoaded > maxItemsPerPage)
{
currentlyLoaded -= maxItemsPerPage;
}
if ((currentlyLoaded % maxItemsPerPage) == 0 && (currentlyLoaded % toTake) == 0)
{
currentlyLoaded = 0;
}
var page1 = new PagesToLoad { Page = firstPage, Skip = currentlyLoaded, Take = toTake };
if (toTake + startPos - 1 > maxItemsPerPage)
{
page1.Take = maxItemsPerPage - startPos + 1;
result.Add(page1);
for (var i = firstPage + 1; i < lastPage; i++)
{
var nextPage = new PagesToLoad { Page = i, Skip = 0, Take = maxItemsPerPage };
result.Add(nextPage);
}
var pageN = new PagesToLoad { Page = lastPage, Skip = 0, Take = stopPos };
result.Add(pageN);
}
else
{
if (page1.Skip + page1.Take > maxItemsPerPage)
{
page1.Skip = 0;
}
result.Add(page1);
}
return result;
}
}
public class PagesToLoad
{
public int Page { get; set; }
public int Take { get; set; }
public int Skip { get; set; }
}
}

@ -6,46 +6,44 @@ namespace Ombi.Helpers
{
private const string Https = "Https";
private const string Http = "Http";
public static Uri ReturnUri(this string val)
{
if (val == null)
{
throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly.");
}
try
{
var uri = new UriBuilder();
var uri = new UriBuilder();
if (val.StartsWith("http://", StringComparison.Ordinal))
{
uri = new UriBuilder(val);
}
else if (val.StartsWith("https://", StringComparison.Ordinal))
{
uri = new UriBuilder(val);
}
else if (val.Contains(":"))
{
var split = val.Split(':', '/');
int port;
int.TryParse(split[1], out port);
uri = split.Length == 3
? new UriBuilder(Http, split[0], port, "/" + split[2])
: new UriBuilder(Http, split[0], port);
}
else
{
uri = new UriBuilder(Http, val);
}
if (val.StartsWith("http://", StringComparison.Ordinal))
{
uri = new UriBuilder(val);
}
else if (val.StartsWith("https://", StringComparison.Ordinal))
{
uri = new UriBuilder(val);
}
else if (val.Contains(":"))
{
var split = val.Split(':', '/');
int port;
int.TryParse(split[1], out port);
return uri.Uri;
uri = split.Length == 3
? new UriBuilder(Http, split[0], port, "/" + split[2])
: new UriBuilder(Http, split[0], port);
}
catch (Exception exception)
else
{
throw new Exception(exception.Message, exception);
if(val.EndsWith("/"))
{
// Remove a trailing slash, since the URIBuilder adds one
val = val.Remove(val.Length - 1, 1);
}
uri = new UriBuilder(Http, val);
}
return uri.Uri;
}
/// <summary>
@ -64,37 +62,40 @@ namespace Ombi.Helpers
{
throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly.");
}
try
{
var uri = new UriBuilder();
var uri = new UriBuilder();
if (val.StartsWith("http://", StringComparison.Ordinal))
{
var split = val.Split('/');
uri = split.Length >= 4 ? new UriBuilder(Http, split[2], port, "/" + split[3]) : new UriBuilder(new Uri($"{val}:{port}"));
}
else if (val.StartsWith("https://", StringComparison.Ordinal))
{
var split = val.Split('/');
uri = split.Length >= 4
? new UriBuilder(Https, split[2], port, "/" + split[3])
: new UriBuilder(Https, split[2], port);
}
else if (ssl)
{
uri = new UriBuilder(Https, val, port);
}
else
if (val.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
{
var split = val.Split('/');
uri = split.Length >= 4 ? new UriBuilder(Http, split[2], port, "/" + split[3]) : new UriBuilder(new Uri($"{val}:{port}"));
}
else if (val.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
{
var split = val.Split('/');
uri = split.Length >= 4
? new UriBuilder(Https, split[2], port, "/" + split[3])
: new UriBuilder(Https, split[2], port);
}
else if ((ssl || port == 443) && port != 80)
{
if (val.EndsWith("/"))
{
uri = new UriBuilder(Http, val, port);
// Remove a trailing slash, since the URIBuilder adds one
val = val.Remove(val.Length - 1, 1);
}
return uri.Uri;
uri = new UriBuilder(Https, val, port);
}
catch (Exception exception)
else
{
throw new Exception(exception.Message, exception);
if (val.EndsWith("/"))
{
// Remove a trailing slash, since the URIBuilder adds one
val = val.Remove(val.Length - 1, 1);
}
uri = new UriBuilder(Http, val, port);
}
return uri.Uri;
}
public static Uri ReturnUriWithSubDir(this string val, int port, bool ssl, string subDir)
@ -112,7 +113,7 @@ namespace Ombi.Helpers
return uriBuilder.Uri;
}
}
public class ApplicationSettingsException : Exception

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AutoMapper;
using AutoMapper.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -12,9 +11,9 @@ namespace Ombi.Mapping
{
public static IServiceCollection AddOmbiMappingProfile(this IServiceCollection services)
{
System.Reflection.Assembly ass = typeof(AutoMapperProfile).GetTypeInfo().Assembly;
Assembly ass = typeof(AutoMapperProfile).GetTypeInfo().Assembly;
var assemblies = new List<Type>();
foreach (System.Reflection.TypeInfo ti in ass.DefinedTypes)
foreach (TypeInfo ti in ass.DefinedTypes)
{
if (ti.ImplementedInterfaces.Contains(typeof(IProfileConfiguration)))
{

@ -4,6 +4,8 @@ using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
using Ombi.TheMovieDbApi.Models;
using Keywords = Ombi.Core.Models.Search.V2.Keywords;
using KeywordsValue = Ombi.Api.TheMovieDb.Models.KeywordsValue;
namespace Ombi.Mapping.Profiles
{
@ -86,6 +88,22 @@ namespace Ombi.Mapping.Profiles
CreateMap<Ombi.Api.TheMovieDb.Models.FullMovieCast, Ombi.Core.Models.Search.V2.FullMovieCastViewModel>().ReverseMap();
CreateMap<Ombi.Api.TheMovieDb.Models.FullMovieCrew, Ombi.Core.Models.Search.V2.FullMovieCrewViewModel>().ReverseMap();
CreateMap<Ombi.Api.TheMovieDb.Models.ExternalIds, Ombi.Core.Models.Search.V2.ExternalIds>().ReverseMap();
CreateMap<BelongsToCollection, Ombi.Core.Models.Search.V2.CollectionsViewModel>().ReverseMap();
CreateMap<Api.TheMovieDb.Models.Keywords, Ombi.Core.Models.Search.V2.Keywords>().ReverseMap();
CreateMap<KeywordsValue, Ombi.Core.Models.Search.V2.KeywordsValue>().ReverseMap();
CreateMap<Collections, Ombi.Core.Models.Search.V2.MovieCollectionsViewModel>()
.ForMember(x => x.Name, o => o.MapFrom(s => s.name))
.ForMember(x => x.Overview, o => o.MapFrom(s => s.overview))
.ForMember(x => x.Collection, o => o.MapFrom(s => s.parts));
CreateMap<Part, MovieCollection>()
.ForMember(x => x.Id, o => o.MapFrom(s => s.id))
.ForMember(x => x.Overview, o => o.MapFrom(s => s.overview))
.ForMember(x => x.PosterPath, o => o.MapFrom(s => s.poster_path))
.ForMember(x => x.Title, o => o.MapFrom(s => s.title));
CreateMap<SearchMovieViewModel, MovieCollection>().ReverseMap();
}
}
}

@ -19,6 +19,7 @@ namespace Ombi.Mapping.Profiles
CreateMap<UpdateSettingsViewModel, UpdateSettings>().ReverseMap();
CreateMap<MobileNotificationsViewModel, MobileNotificationSettings>().ReverseMap();
CreateMap<NewsletterNotificationViewModel, NewsletterSettings>().ReverseMap();
CreateMap<GotifyNotificationViewModel, GotifySettings>().ReverseMap();
}
}
}

@ -0,0 +1,82 @@
using System.Globalization;
using AutoMapper;
using Ombi.Api.TvMaze.Models.V2;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
using Ombi.Helpers;
namespace Ombi.Mapping.Profiles
{
public class TvProfileV2 : Profile
{
public TvProfileV2()
{
CreateMap<FullSearch, SearchFullInfoTvShowViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.externals.thetvdb))
.ForMember(dest => dest.FirstAired, opts => opts.MapFrom(src => src.premiered))
.ForMember(dest => dest.ImdbId, opts => opts.MapFrom(src => src.externals.imdb))
.ForMember(dest => dest.Network, opts => opts.MapFrom(src => src.network.name))
.ForMember(dest => dest.NetworkId, opts => opts.MapFrom(src => src.network.id.ToString()))
.ForMember(dest => dest.Overview, opts => opts.MapFrom(src => src.summary.RemoveHtml()))
.ForMember(dest => dest.Rating,
opts => opts.MapFrom(src => src.rating.average.ToString(CultureInfo.CurrentUICulture)))
.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.runtime.ToString()))
.ForMember(dest => dest.SeriesId, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Network, opts => opts.MapFrom(src => src.network))
.ForMember(dest => dest.Images, opts => opts.MapFrom(src => src.image))
.ForMember(dest => dest.Cast, opts => opts.MapFrom(src => src._embedded.cast))
.ForMember(dest => dest.Crew, opts => opts.MapFrom(src => src._embedded.crew))
.ForMember(dest => dest.Banner,
opts => opts.MapFrom(src =>
!string.IsNullOrEmpty(src.image.medium)
? src.image.medium.Replace("http", "https")
: string.Empty))
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status));
CreateMap<Network, NetworkViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Country, opts => opts.MapFrom(src => src.country))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name));
CreateMap<Api.TvMaze.Models.V2.Country, Core.Models.Search.V2.Country>()
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Code, opts => opts.MapFrom(src => src.code))
.ForMember(dest => dest.Timezone, opts => opts.MapFrom(src => src.timezone));
CreateMap<Api.TvMaze.Models.V2.Image, Images>()
.ForMember(dest => dest.Medium, opts => opts.MapFrom(src => src.medium))
.ForMember(dest => dest.Original, opts => opts.MapFrom(src => src.original));
CreateMap<Api.TvMaze.Models.V2.Cast, CastViewModel>()
.ForMember(dest => dest.Character, opts => opts.MapFrom(src => src.character))
.ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.person))
.ForMember(dest => dest.Voice, opts => opts.MapFrom(src => src.voice))
.ForMember(dest => dest.Self, opts => opts.MapFrom(src => src.self));
CreateMap<Api.TvMaze.Models.V2.Person, PersonViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Image, opts => opts.MapFrom(src => src.image))
.ForMember(dest => dest.Url, opts => opts.MapFrom(src => src.url));
CreateMap<Api.TvMaze.Models.V2.Crew, CrewViewModel>()
.ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.person))
.ForMember(dest => dest.Type, opts => opts.MapFrom(src => src.type));
CreateMap<Api.TvMaze.Models.V2.Cast, CastViewModel>()
.ForMember(dest => dest.Person, opts => opts.MapFrom(src => src.person))
.ForMember(dest => dest.Self, opts => opts.MapFrom(src => src.self))
.ForMember(dest => dest.Voice, opts => opts.MapFrom(src => src.voice))
.ForMember(dest => dest.Character, opts => opts.MapFrom(src => src.character));
CreateMap<Api.TvMaze.Models.V2.Character, CharacterViewModel>()
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name))
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.Url, opts => opts.MapFrom(src => src.url))
.ForMember(dest => dest.Image, opts => opts.MapFrom(src => src.image));
CreateMap<SearchTvShowViewModel, SearchFullInfoTvShowViewModel>().ReverseMap();
}
}
}

@ -13,7 +13,7 @@ namespace Ombi.Notifications.Templates
if (string.IsNullOrEmpty(_templateLocation))
{
#if DEBUG
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates",
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.2", "Templates",
"BasicTemplate.html");
#else
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html");

@ -5,10 +5,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="Nunit" Version="3.11.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.0.1"></packagereference>
<PackageReference Include="Moq" Version="4.10.0" />
</ItemGroup>

@ -0,0 +1,116 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Gotify;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
namespace Ombi.Notifications.Agents
{
public class GotifyNotification : BaseNotification<GotifySettings>, IGotifyNotification
{
public GotifyNotification(IGotifyApi api, ISettingsService<GotifySettings> sn, ILogger<GotifyNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
{
Api = api;
Logger = log;
}
public override string NotificationName => "GotifyNotification";
private IGotifyApi Api { get; }
private ILogger<GotifyNotification> Logger { get; }
protected override bool ValidateConfiguration(GotifySettings settings)
{
return settings.Enabled && !string.IsNullOrEmpty(settings.BaseUrl) && !string.IsNullOrEmpty(settings.ApplicationToken);
}
protected override async Task NewRequest(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.NewRequest);
}
protected override async Task NewIssue(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.Issue);
}
protected override async Task IssueComment(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.IssueComment);
}
protected override async Task IssueResolved(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.IssueResolved);
}
protected override async Task AddedToRequestQueue(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
}
protected override async Task RequestDeclined(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestDeclined);
}
protected override async Task RequestApproved(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestApproved);
}
protected override async Task AvailableRequest(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestAvailable);
}
protected override async Task Send(NotificationMessage model, GotifySettings settings)
{
try
{
await Api.PushAsync(settings.BaseUrl, settings.ApplicationToken, model.Subject, model.Message, settings.Priority);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.GotifyNotification, e, "Failed to send Gotify notification");
}
}
protected override async Task Test(NotificationOptions model, GotifySettings settings)
{
var message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
private async Task Run(NotificationOptions model, GotifySettings settings, NotificationType type)
{
var parsed = await LoadTemplate(NotificationAgent.Gotify, type, model);
if (parsed.Disabled)
{
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Gotify}");
return;
}
var notification = new NotificationMessage
{
Message = parsed.Message,
};
await Send(notification, settings);
}
}
}

@ -0,0 +1,6 @@
namespace Ombi.Notifications.Agents
{
public interface IGotifyNotification : INotification
{
}
}

@ -52,7 +52,7 @@ namespace Ombi.Notifications.Agents
private void AddOtherInformation(NotificationOptions model, NotificationMessage notification,
NotificationMessageContent parsed)
{
notification.Other.Add("image", parsed.Image);
notification.Other.Add("image", parsed?.Image ?? string.Empty);
notification.Other.Add("title", model.RequestType == RequestType.Movie ? MovieRequest.Title : TvRequest.Title);
}

@ -26,8 +26,6 @@ namespace Ombi.Notifications
MovieRepository = movie;
TvRepository = tv;
CustomizationSettings = customization;
Settings.ClearCache();
CustomizationSettings.ClearCache();
RequestSubscription = sub;
_log = log;
AlbumRepository = album;
@ -55,14 +53,12 @@ namespace Ombi.Notifications
public async Task NotifyAsync(NotificationOptions model)
{
Settings.ClearCache();
var configuration = await GetConfiguration();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationOptions model, Settings.Settings.Models.Settings settings)
{
Settings.ClearCache();
if (settings == null) await NotifyAsync(model);
var notificationSettings = (T)settings;

@ -4,7 +4,9 @@ using EnsureThat;
using MailKit.Net.Smtp;
using Microsoft.Extensions.Logging;
using MimeKit;
using MimeKit.Utils;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Notifications.Templates;
using Ombi.Settings.Settings.Models;
@ -36,6 +38,15 @@ namespace Ombi.Notifications
var customization = await CustomizationSettings.GetSettingsAsync();
var html = email.LoadTemplate(model.Subject, model.Message, null, customization.Logo);
var messageId = MimeUtils.GenerateMessageId();
if (customization.ApplicationUrl.HasValue())
{
if (Uri.TryCreate(customization.ApplicationUrl, UriKind.RelativeOrAbsolute, out var url))
{
messageId = MimeUtils.GenerateMessageId(url.IdnHost);
}
}
var textBody = string.Empty;
@ -49,7 +60,8 @@ namespace Ombi.Notifications
var message = new MimeMessage
{
Body = body.ToMessageBody(),
Subject = model.Subject
Subject = model.Subject,
MessageId = messageId
};
message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress));
message.To.Add(new MailboxAddress(model.To, model.To));

@ -17,7 +17,7 @@ namespace Ombi.Notifications
public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s, UserNotificationPreferences pref)
{
LoadIssues(opts);
RequestId = req.Id.ToString();
string title;
if (req == null)
{
@ -68,6 +68,7 @@ namespace Ombi.Notifications
{
LoadIssues(opts);
RequestId = req.Id.ToString();
string title;
if (req == null)
{
@ -114,6 +115,7 @@ namespace Ombi.Notifications
public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s, UserNotificationPreferences pref)
{
LoadIssues(opts);
RequestId = req.Id.ToString();
string title;
if (req == null)
{
@ -239,6 +241,7 @@ namespace Ombi.Notifications
public string UserPreference { get; set; }
public string DenyReason { get; set; }
public string AvailableDate { get; set; }
public string RequestId { get; set; }
// System Defined
private string LongDate => DateTime.Now.ToString("D");
@ -275,6 +278,7 @@ namespace Ombi.Notifications
{nameof(UserPreference),UserPreference},
{nameof(DenyReason),DenyReason},
{nameof(AvailableDate),AvailableDate},
{nameof(RequestId),RequestId},
};
}
}

@ -10,11 +10,12 @@
<ItemGroup>
<PackageReference Include="Ensure.That" Version="7.0.0-pre32" />
<PackageReference Include="MailKit" Version="2.0.5" />
<PackageReference Include="MailKit" Version="2.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
<ProjectReference Include="..\Ombi.Api.Gotify\Ombi.Api.Gotify.csproj" />
<ProjectReference Include="..\Ombi.Api.Mattermost\Ombi.Api.Mattermost.csproj" />
<ProjectReference Include="..\Ombi.Api.Notifications\Ombi.Api.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj" />

@ -44,12 +44,12 @@ namespace Ombi.Schedule.Tests
new Issues
{
Status = IssueStatus.Resolved,
ResovledDate = DateTime.Now.AddDays(-5).AddHours(-1)
ResovledDate = DateTime.UtcNow.AddDays(-5).AddHours(-8)
}
};
Settings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new IssueSettings { DeleteIssues = true, DaysAfterResolvedToDelete = 5 });
Repo.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Issues>(issues));
Repo.Setup(x => x.GetAll()).Returns(issues.AsQueryable());
await Job.Start();
Assert.That(issues.First().Status, Is.EqualTo(IssueStatus.Deleted));
@ -57,7 +57,7 @@ namespace Ombi.Schedule.Tests
}
[Test]
public async Task DoesNot_Delete_AnyIssues()
public async Task DoesNot_Delete_AllIssues()
{
var issues = new List<Issues>()
{
@ -81,5 +81,31 @@ namespace Ombi.Schedule.Tests
Assert.That(issues[1].Status, Is.EqualTo(IssueStatus.Deleted));
Repo.Verify(x => x.SaveChangesAsync(), Times.Once);
}
[Test]
public async Task DoesNot_Delete_AnyIssues()
{
var issues = new List<Issues>()
{
new Issues
{
Status = IssueStatus.Resolved,
ResovledDate = DateTime.Now.AddDays(-2)
},
new Issues
{
Status = IssueStatus.Resolved,
ResovledDate = DateTime.Now.AddDays(-4)
}
};
Settings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new IssueSettings { DeleteIssues = true, DaysAfterResolvedToDelete = 5 });
Repo.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Issues>(issues));
await Job.Start();
Assert.That(issues[0].Status, Is.Not.EqualTo(IssueStatus.Deleted));
Assert.That(issues[1].Status, Is.Not.EqualTo(IssueStatus.Deleted));
Repo.Verify(x => x.SaveChangesAsync(), Times.Once);
}
}
}

@ -6,11 +6,12 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="MockQueryable.Moq" Version="1.1.0" />
<PackageReference Include="Moq" Version="4.10.0" />
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="Nunit" Version="3.11.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.0.1"></packagereference>
</ItemGroup>
<ItemGroup>

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Castle.Components.DictionaryAdapter;
using Hangfire;
using Moq;
using MockQueryable.Moq;
using NUnit.Framework;
using Ombi.Core.Notifications;
using Ombi.Schedule.Jobs.Plex;
@ -68,7 +69,6 @@ namespace Ombi.Schedule.Tests
}
[Test]
[Ignore("EF IAsyncQueryProvider")]
public async Task ProcessTv_ShouldMark_Episode_Available_WhenInPlex()
{
var request = new ChildRequests
@ -90,21 +90,25 @@ namespace Ombi.Schedule.Tests
}
}
}
},
RequestedUser = new OmbiUser
{
Email = "abc"
}
};
_tv.Setup(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable());
_tv.Setup(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable().BuildMock().Object);
_repo.Setup(x => x.GetAllEpisodes()).Returns(new List<PlexEpisode>
{
new PlexEpisode
{
Series = new PlexServerContent
{
ImdbId = 1.ToString(),
TvDbId = 1.ToString(),
},
EpisodeNumber = 1,
SeasonNumber = 2
}
}.AsQueryable);
}.AsQueryable().BuildMock().Object);
_repo.Setup(x => x.Include(It.IsAny<IQueryable<PlexEpisode>>(),It.IsAny<Expression<Func<PlexEpisode, PlexServerContent>>>()));
await Checker.Start();

@ -81,7 +81,6 @@ namespace Ombi.Schedule
RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
RecurringJob.AddOrUpdate(() => _resender.Start(), JobSettingsHelper.ResendFailedRequests(s));
RecurringJob.AddOrUpdate(() => _mediaDatabaseRefresh.Start(), JobSettingsHelper.MediaDatabaseRefresh(s));
}

@ -28,7 +28,6 @@ namespace Ombi.Schedule.Jobs.Emby
_repo = repo;
_episodeSync = epSync;
_metadata = metadata;
_settings.ClearCache();
}
private readonly ILogger<EmbyContentSync> _logger;

@ -49,7 +49,6 @@ namespace Ombi.Schedule.Jobs.Emby
_settings = s;
_repo = repo;
_avaliabilityChecker = checker;
_settings.ClearCache();
}
private readonly ISettingsService<EmbySettings> _settings;

@ -50,8 +50,6 @@ namespace Ombi.Schedule.Jobs.Emby
_log = log;
_embySettings = embySettings;
_userManagementSettings = ums;
_userManagementSettings.ClearCache();
_embySettings.ClearCache();
}
private readonly IEmbyApi _api;

@ -1,19 +1,16 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr;
using Ombi.Api.Radarr;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Ombi.Schedule.Jobs.Lidarr
@ -29,7 +26,6 @@ namespace Ombi.Schedule.Jobs.Lidarr
_ctx = ctx;
_job = job;
_availability = availability;
_lidarrSettings.ClearCache();
}
private readonly ISettingsService<LidarrSettings> _lidarrSettings;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save