update poc quartz with develop

This commit is contained in:
tidusjar 2019-04-09 21:59:33 +01:00
commit e9ead2eddb
402 changed files with 30984 additions and 4844 deletions

View file

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

37
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

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

View file

@ -1,6 +1,479 @@
# Changelog # 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)
### **New Features**
- Update CHANGELOG.md. [Jamie]
- Added a page where the admin can write/style/basically do whatever they want with e.g. FAQ for the users #2715 This needs to be enabled in the Customization Settings and then it's all configured on the page. [TidusJar]
- Updated the AspnetCore.App package to remove the CVE-2019-0564 vulnerability. [TidusJar]
- Added a global language flag that now applies to the search by default. [tidusjar]
- Updated the frontend packages (Using Angular 7 now) [TidusJar]
- Added capture of anonymous analytical data. [tidusjar]
- Added {AvailableDate} as a Notification Variable, this is the date the request was marked as available. See here: https://github.com/tidusjar/Ombi/wiki/Notification-Template-Variables. [tidusjar]
- Added the ability to search movies via the movie db with a different language! [tidusjar]
- Added the ability to specify a year when searching for movies. [tidusjar]
- Update NewsletterTemplate.html. [d1slact0r]
- Update NewsletterTemplate.html. [d1slact0r]
- Update NewsletterTemplate.html. [d1slact0r]
- Update HtmlTemplateGenerator.cs. [d1slact0r]
- Update NewsletterTemplate.html. [d1slact0r]
- Update HtmlTemplateGenerator.cs. [d1slact0r]
- Update NewsletterTemplate.html. [d1slact0r]
- Update NewsletterTemplate.html. [d1slact0r]
- Update NewsletterTemplate.html. [d1slact0r]
- Update HtmlTemplateGenerator.cs. [d1slact0r]
- Updated boostrap #2694. [Jamie]
- Added the ability to deny a request with a reason. [TidusJar]
- Update EmbyEpisodeSync.cs. [Jamie]
- Updated to .net core 2.2 and included a linux-arm64 build. [TidusJar]
### **Fixes**
- There is now a new Job in ombi that will clear out the Plex/Emby data and recache. This will prevent the issues going forward that we have when Ombi and the Media server fall out of sync with deletions/updates #2641 #2362 #1566. [TidusJar]
- Potentially fix #2726. [TidusJar]
- New translations en.json (Spanish) [Jamie]
- New translations en.json (Spanish) [Jamie]
- New translations en.json (Dutch) [Jamie]
- Fixed #2725 and #2721. [TidusJar]
- Made the newsletter use the default lanuage code set in the Ombi settings for movie information. [TidusJar]
- Save the language code against the request so we can use it later e.g. Sending to the DVR apps. [tidusjar]
- Fixed #2716. [tidusjar]
- Make the newsletter BCC the users rather than creating a million newsletters (Hopefully will stop SMTP providers from marking as spam). This does mean that the custom user customization in the newsletter will no longer work. [TidusJar]
- If we don't know the Plex agent, then see if it's a ImdbId, if it's not check the string for any episode and season hints #2695. [tidusjar]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Spanish) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Norwegian) [Jamie]
- New translations en.json (Italian) [Jamie]
- New translations en.json (German) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (Dutch) [Jamie]
- New translations en.json (Danish) [Jamie]
- New translations en.json (Dutch) [Jamie]
- New translations en.json (Dutch) [Jamie]
- New translations en.json (Dutch) [Jamie]
- Made the search results the language specified in the search refinement. [tidusjar]
- Fixed #2704. [tidusjar]
- Now it is fixed :) [d1slact0r]
- Android please be nice now. [d1slact0r]
- Fixed title bit better. [d1slact0r]
- Fixed titles. [d1slact0r]
- This should fix the build for sure (stupid quotes) [d1slact0r]
- Fixes build. [d1slact0r]
- Rewritten the whole newsletter template. [d1slact0r]
- Fixed #2697. [tidusjar]
- Add linux-arm runtime identifier. [aptalca]
- Add back arm packages. [aptalca]
- Add arm32 package. [aptalca]
- Fixed #2691. [tidusjar]
- Fixed linting. [TidusJar]
- Fixed the Plex OAuth when going through the wizard. [TidusJar]
- Fixed #2678. [TidusJar]
- Deny reason for movie requests. [TidusJar]
- Set the landing and login pages background refresh to 15 seconds rather than 10 and 7. [TidusJar]
- Fixed a bug with us thinking future dated emby episodes are not available, Consoldated the emby and plex search rules (since they have the same logic) [TidusJar]
- Fixed build. [TidusJar]
## v3.0.4036 (2018-12-11)
### **New Features**
- Changelog. [Jamie]
- Added Sonarr v3 #2359. [TidusJar]
### **Fixes**
- !changelog. [Jamie]
- Fixed a missing translation. [Jamie]
- Fixed a potential security vulnerability. [Jamie]
- Sorted out some of the settings pages, trying to make it consistent. [Jamie]
- #2669 Fixed missing translations. [TidusJar]
- Maps alias email variable for welcome emails. [Victor Usoltsev]
- Increased the logo size on the landing page to match the container below it. [Jamie]
- Think the request queue is done! [Jamie]
- Finished off the job. [TidusJar]
## v3.0.3988 (2018-11-23)
### **New Features**
- Updated the emby api since we no longer need the extra parameters to send to emby to log in a local user #2546. [Jamie]
- Added the ability to get the ombi user via a Plex Token #2591. [Jamie]
- Update CHANGELOG.md. [Jamie]
### **Fixes**
- !changelog. [Jamie]
- Made the subscribe/unsubscribe button more obvious on the UI #2309. [Jamie]
- Fixed #2603. [Jamie]
- Fixed the issue with the user overrides #2646. [Jamie]
- Fixed the issue where we could sometimes allow the request of a whole series when the user shouldn't be able to. [Jamie]
- Fixed the issue where we were marking episodes as available with the Emby connection when they have not yet aired #2417 #2623. [TidusJar]
- Fixed the issue where we were marking the whole season as wanted in Sonarr rather than the individual episode #2629. [TidusJar]
- Fixed #2623. [Jamie]
- Fixed #2633. [TidusJar]
- Fixed #2639. [Jamie]
- Show the TV show as available when we have all the episodes but future episodes have not aired. #2585. [Jamie]
## v3.0.3945 (2018-10-25)
### **New Features**
- Update Readme for Lidarr. [Qstick]
- Update CHANGELOG.md. [Jamie]
### **Fixes**
- New translations en.json (French) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (Dutch) [Jamie]
- Fixed the issue with mobile notifications. [Jamie]
- Fixed #2514. [Jamie]
## v3.0.3923 (2018-10-19)
### **New Features**
- Update CHANGELOG.md. [Jamie]
### **Fixes**
- Fixed #2601. [Jamie]
## v3.0.3919 (2018-10-17)
### **New Features**
- Added automation tests for the voting feature. [TidusJar]
- Update LidarrAvailabilityChecker.cs. [Jamie]
- Update CHANGELOG.md. [Jamie]
- Changes language selector to always show native language name. [Victor Usoltsev]
- Updated test dependancies. [TidusJar]
- Added in the external repo so we can rip out external stuff. [TidusJar]
- Added the ability to purge/remove issues. [TidusJar]
### **Fixes**
- New translations en.json (French) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Spanish) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Norwegian) [Jamie]
- New translations en.json (Italian) [Jamie]
- New translations en.json (German) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (Dutch) [Jamie]
- New translations en.json (Danish) [Jamie]
- When a users requests content and the voting is enabled, the user who requested is an automatic +1 vote. [TidusJar]
- Revert, no idea how this happened. [TidusJar]
- Fixed the build. Thanks Matt! [TidusJar]
- Fixes untickable mass email checkboxes in Safari. [Victor Usoltsev]
- [ImgBot] optimizes images. [ImgBotApp]
- Revert "Feature/purge issues" [Jamie]
- Fixed the issue where user preferences was not being inported into some notifications. [TidusJar]
- New role to enable users to remove their own requests. [Anojh]
- Users can now remove their own requests. [Anojh]
- New translations en.json (Danish) [Jamie]
- Fixed lidarr newsletter bug. [Jamie]
- Potentially fix the user profiles issue. [Jamie]
- Hides Radarr options on movie requests page if only 1 option available. [Victor Usoltsev]
- Hides Sonarr options on tv requests page if only 1 option available. [Victor Usoltsev]
- Fixed the issue where we could not delete users #2558. [TidusJar]
- New translations en.json (German) [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Spanish) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Norwegian) [Jamie]
- New translations en.json (Italian) [Jamie]
- New translations en.json (German) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (Dutch) [Jamie]
- New translations en.json (Danish) [Jamie]
- Subscribe the user to the request when they vote on it. [TidusJar]
- Fixed #2555. [Jamie]
- Fixed #2549. [Jamie]
- Removed the pinID from the OAuth url #2548. [Jamie]
- Put the issue purge limit on the issues page. [Jamie]
- Date and times are now in the local users date time. [TidusJar]
- Fixed the migration. [TidusJar]
- ExternalContext migrations. [TidusJar]
- The settings have now been split out of the main db. [TidusJar]
- Search for the Lidarr Album when it's a new artist. [TidusJar]
- The album in Lidarr does not need to be marked as monitored for us to pick up it's available. Fixes #2536. [Jamie]
- Truncate the request title. [Jamie]
- Fixed #2535. [Jamie]
## v3.0.3795 (2018-09-23)
### **New Features**
- Update CHANGELOG.md. [Jamie]
### **Fixes** ### **Fixes**

View file

@ -9,6 +9,15 @@ ____
[![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi) [![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) [![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet)
___
[![Twitter](https://img.shields.io/twitter/follow/tidusjar.svg?style=social)](https://twitter.com/intent/follow?screen_name=tidusjar)
Follow me developing Ombi!
[![Twitch](https://img.shields.io/badge/Twitch-Watch-blue.svg?style=flat-square&logo=twitch)](https://www.twitch.tv/tidusjar)
___ ___
<a href='https://play.google.com/store/apps/details?id=com.tidusjar.Ombi&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img width="150" alt='Get it on Google Play' src='https://play.google.com/intl/en_gb/badges/images/generic/en_badge_web_generic.png'/></a> <a href='https://play.google.com/store/apps/details?id=com.tidusjar.Ombi&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img width="150" alt='Get it on Google Play' src='https://play.google.com/intl/en_gb/badges/images/generic/en_badge_web_generic.png'/></a>
@ -32,7 +41,7 @@ ___
# Features # Features
Here are some of the features Ombi V3 has: Here are some of the features Ombi V3 has:
* Now working without crashes on Linux. * Now working without crashes on Linux.
* Lets users request Movies and TV Shows (whether it being the entire series, an entire season, or even single episodes.) * Lets users request Movies, Music, and TV Shows (whether it being the entire series, an entire season, or even single episodes.)
* Easily manage your requests * Easily manage your requests
* User management system (supports plex.tv, Emby and local accounts) * User management system (supports plex.tv, Emby and local accounts)
* A landing page that will give you the availability of your Plex/Emby server and also add custom notification text to inform your users of downtime. * A landing page that will give you the availability of your Plex/Emby server and also add custom notification text to inform your users of downtime.
@ -50,6 +59,7 @@ We integrate with the following applications:
* Emby * Emby
* Sonarr * Sonarr
* Radarr * Radarr
* Lidarr
* DogNzb * DogNzb
* Couch Potato * Couch Potato
@ -58,6 +68,7 @@ We integrate with the following applications:
Supported notifications: Supported notifications:
* SMTP Notifications (Email) * SMTP Notifications (Email)
* Discord * Discord
* Gotify
* Slack * Slack
* Pushbullet * Pushbullet
* Pushover * Pushover
@ -87,6 +98,7 @@ We are planning to bring back these features in V3 but for now you can find a li
| DogNzb | Yes | No | | DogNzb | Yes | No |
| Issues | Yes | Yes | | Issues | Yes | Yes |
| Headphones | No | Yes | | Headphones | No | Yes |
| Lidarr | Yes | No |
# Feature Requests # Feature Requests
Feature requests are handled on FeatHub. Feature requests are handled on FeatHub.
@ -115,13 +127,12 @@ Please feel free to submit a pull request!
# Donation # Donation
If you feel like donating you can donate with the below buttons! 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! ### 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 ### ### Sponsors ###
- [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools - [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools

View file

@ -3,34 +3,46 @@ configuration: Release
os: Visual Studio 2017 os: Visual Studio 2017
environment: environment:
nodejs_version: "9.8.0" nodejs_version: "9.8.0"
typescript_version: "3.0.1"
github_auth_token:
secure: H/7uCrjmWHGJxgN3l9fbhhdVjvvWI8VVF4ZzQqeXuJwAf+PgSNBdxv4SS+rMQ+RH
sonarrcloudtoken:
secure: WGkIog4wuMSx1q5vmSOgIBXMtI/leMpLbZhi9MJnJdBBuDfcv12zwXg3LQwY0WbE
install: install:
# Get the latest stable version of Node.js or io.js # Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version - ps: Install-Product node $env:nodejs_version
- cmd: set path=%programfiles(x86)%\\Microsoft SDKs\TypeScript\3.0;%path%
- cmd: tsc -v
build_script: 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 - ps: ./build.ps1 --settings_skipverification=true
# - dotnet sonarscanner end /d:sonar.login="%sonarrcloudtoken%"
test: off test: off
after_build: after_build:
- cmd: >- - cmd: >-
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows.zip" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\osx.tar.gz" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\osx.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux.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.1\linux-arm.tar.gz" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows-32bit.zip" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows-32bit.zip"
# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm64.tar.gz"

View file

@ -3,7 +3,7 @@
#addin "Cake.Gulp" #addin "Cake.Gulp"
#addin "SharpZipLib" #addin "SharpZipLib"
#addin nuget:?package=Cake.Compression&version=0.1.4 #addin nuget:?package=Cake.Compression&version=0.1.4
#addin "Cake.Incubator" #addin "Cake.Incubator&version=3.1.0"
#addin "Cake.Yarn" #addin "Cake.Yarn"
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -26,7 +26,7 @@ var csProj = "./src/Ombi/Ombi.csproj"; // Path to the project.csproj
var solutionFile = "Ombi.sln"; // Solution file if needed var solutionFile = "Ombi.sln"; // Solution file if needed
GitVersion versionInfo = null; GitVersion versionInfo = null;
var frameworkVer = "netcoreapp2.1"; var frameworkVer = "netcoreapp2.2";
var buildSettings = new DotNetCoreBuildSettings var buildSettings = new DotNetCoreBuildSettings
{ {
@ -81,9 +81,9 @@ Task("SetVersionInfo")
versionInfo = GitVersion(settings); 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; var buildVersion = string.Empty;
if(string.IsNullOrEmpty(AppVeyor.Environment.Build.Version)) if(string.IsNullOrEmpty(AppVeyor.Environment.Build.Version))
@ -151,7 +151,7 @@ Task("Package")
GZipCompress(osxArtifactsFolder, artifactsFolder + "osx.tar.gz"); GZipCompress(osxArtifactsFolder, artifactsFolder + "osx.tar.gz");
GZipCompress(linuxArtifactsFolder, artifactsFolder + "linux.tar.gz"); GZipCompress(linuxArtifactsFolder, artifactsFolder + "linux.tar.gz");
GZipCompress(linuxArmArtifactsFolder, artifactsFolder + "linux-arm.tar.gz"); GZipCompress(linuxArmArtifactsFolder, artifactsFolder + "linux-arm.tar.gz");
//GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz"); GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz");
}); });
Task("Publish") Task("Publish")

291
package-lock.json generated
View file

@ -1,291 +0,0 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "1.0.3"
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"requires": {
"chalk": "1.1.3",
"esutils": "2.0.2",
"js-tokens": "3.0.2"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
}
}
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.4.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "1.9.1"
}
},
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"color-convert": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
},
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
"requires": {
"argparse": "1.0.10",
"esprima": "4.0.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "1.1.11"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1.0.2"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-parse": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
},
"resolve": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
"integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
"requires": {
"path-parse": "1.0.5"
}
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
},
"tslib": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz",
"integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw=="
},
"tslint": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz",
"integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=",
"requires": {
"babel-code-frame": "6.26.0",
"builtin-modules": "1.1.1",
"chalk": "2.4.1",
"commander": "2.15.1",
"diff": "3.5.0",
"glob": "7.1.2",
"js-yaml": "3.12.0",
"minimatch": "3.0.4",
"resolve": "1.7.1",
"semver": "5.5.0",
"tslib": "1.9.2",
"tsutils": "2.27.1"
}
},
"tsutils": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz",
"integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==",
"requires": {
"tslib": "1.9.2"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

View file

@ -53,8 +53,6 @@ namespace Ombi.Api.Emby
{ {
username, username,
pw = password, pw = password,
password = password.GetSha1Hash().ToLower(),
passwordMd5 = password.CalcuateMd5Hash()
}; };
request.AddJsonBody(body); request.AddJsonBody(body);
@ -98,7 +96,7 @@ namespace Ombi.Api.Emby
request.AddQueryString("Fields", "ProviderIds,Overview"); request.AddQueryString("Fields", "ProviderIds,Overview");
request.AddQueryString("VirtualItem", "False"); request.AddQueryString("IsVirtualItem", "False");
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request); return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
} }
@ -149,7 +147,7 @@ namespace Ombi.Api.Emby
request.AddQueryString("IncludeItemTypes", type); request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds"); request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("VirtualItem", "False"); request.AddQueryString("IsVirtualItem", "False");
AddHeaders(request, apiKey); AddHeaders(request, apiKey);
@ -167,7 +165,7 @@ namespace Ombi.Api.Emby
request.AddQueryString("startIndex", startIndex.ToString()); request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString()); request.AddQueryString("limit", count.ToString());
request.AddQueryString("VirtualItem", "False"); request.AddQueryString("IsVirtualItem", "False");
AddHeaders(request, apiKey); AddHeaders(request, apiKey);

View file

@ -24,16 +24,5 @@ namespace Ombi.Api.Github
request.AddHeader("User-Agent", "Ombi"); request.AddHeader("User-Agent", "Ombi");
return await _api.Request<List<CakeThemes>>(request); return await _api.Request<List<CakeThemes>>(request);
} }
public async Task<string> GetThemesRawContent(string url)
{
var sections = url.Split('/');
var lastPart = sections.Last();
url = url.Replace(lastPart, string.Empty);
var request = new Request(lastPart, url, HttpMethod.Get);
request.AddHeader("Accept", "application/vnd.github.v3+json");
request.AddHeader("User-Agent", "Ombi");
return await _api.RequestContent(request);
}
} }
} }

View file

@ -7,6 +7,5 @@ namespace Ombi.Api.Github
public interface IGithubApi public interface IGithubApi
{ {
Task<List<CakeThemes>> GetCakeThemes(); Task<List<CakeThemes>> GetCakeThemes();
Task<string> GetThemesRawContent(string url);
} }
} }

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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>

View file

@ -23,5 +23,6 @@ namespace Ombi.Api.Lidarr
Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl); Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl);
Task<LidarrStatus> Status(string apiKey, string baseUrl); Task<LidarrStatus> Status(string apiKey, string baseUrl);
Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl); Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl);
Task<AlbumResponse> AlbumInformation(string albumId, string apiKey, string baseUrl);
} }
} }

View file

@ -105,6 +105,32 @@ namespace Ombi.Api.Lidarr
return Api.Request<List<AlbumResponse>>(request); return Api.Request<List<AlbumResponse>>(request);
} }
public async Task<AlbumResponse> AlbumInformation(string albumId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
request.AddQueryString("foreignAlbumId", albumId);
AddHeaders(request, apiKey);
var albums = await Api.Request<List<AlbumResponse>>(request);
return albums.Where(x => x.foreignAlbumId.Equals(albumId, StringComparison.InvariantCultureIgnoreCase))
.FirstOrDefault();
}
/// <summary>
/// THIS ONLY SUPPORTS ALBUMS THAT THE ARTIST IS IN LIDARR
/// </summary>
/// <param name="albumId"></param>
/// <param name="apiKey"></param>
/// <param name="baseUrl"></param>
/// <returns></returns>
public Task<List<LidarrTrack>> GetTracksForAlbum(int albumId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
request.AddQueryString("albumId", albumId.ToString());
AddHeaders(request, apiKey);
return Api.Request<List<LidarrTrack>>(request);
}
public Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl) public Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl)
{ {
var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post); var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post);

View file

@ -1,10 +1,15 @@
using System; using System;
using System.Collections.Generic;
namespace Ombi.Api.Lidarr.Models namespace Ombi.Api.Lidarr.Models
{ {
public class AlbumLookup public class AlbumLookup
{ {
public string title { get; set; } public string title { get; set; }
public string status { get; set; }
public string artistType { get; set; }
public string disambiguation { get; set; }
public List<LidarrLinks> links { get; set; }
public int artistId { get; set; } public int artistId { get; set; }
public string foreignAlbumId { get; set; } public string foreignAlbumId { get; set; }
public bool monitored { get; set; } public bool monitored { get; set; }

View file

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Api.Lidarr.Models
{
public class LidarrLinks
{
public string url { get; set; }
public string name { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class LidarrRatings
{
public int votes { get; set; }
public decimal value { get; set; }
}
}

View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Api.Lidarr.Models
{
public class LidarrTrack
{
public int artistId { get; set; }
public int trackFileId { get; set; }
public int albumId { get; set; }
public bool _explicit { get; set; }
public int absoluteTrackNumber { get; set; }
public string trackNumber { get; set; }
public string title { get; set; }
public int duration { get; set; }
public int mediumNumber { get; set; }
public bool hasFile { get; set; }
public bool monitored { get; set; }
public int id { get; set; }
}
}

View file

@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -26,7 +26,7 @@ namespace Ombi.Api.Notifications
{ {
return null; return null;
} }
var id = await _appConfig.Get(ConfigurationTypes.Notification); var id = await _appConfig.GetAsync(ConfigurationTypes.Notification);
var request = new Request(string.Empty, ApiUrl, HttpMethod.Post); var request = new Request(string.Empty, ApiUrl, HttpMethod.Post);
var body = new OneSignalNotificationBody var body = new OneSignalNotificationBody

View file

@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -11,7 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Sonarr.Models;
using System.Net.Http;
using Ombi.Api.Sonarr.Models.V3;
namespace Ombi.Api.Sonarr
{
public interface ISonarrV3Api : ISonarrApi
{
Task<IEnumerable<LanguageProfiles>> LanguageProfiles(string apiKey, string baseUrl);
}
}

View file

@ -27,6 +27,9 @@ namespace Ombi.Api.Sonarr.Models
public int id { get; set; } public int id { get; set; }
public List<SonarrImage> images { get; set; } public List<SonarrImage> images { get; set; }
// V3 Property
public int languageProfileId { get; set; }
/// <summary> /// <summary>
/// This is for us /// This is for us
/// </summary> /// </summary>

View file

@ -44,6 +44,7 @@ namespace Ombi.Api.Sonarr.Models
public DateTime added { get; set; } public DateTime added { get; set; }
public Ratings ratings { get; set; } public Ratings ratings { get; set; }
public int qualityProfileId { get; set; } public int qualityProfileId { get; set; }
public int languageProfileId { get; set; }
public int id { get; set; } public int id { get; set; }
public DateTime nextAiring { get; set; } public DateTime nextAiring { get; set; }
} }

View file

@ -0,0 +1,30 @@
namespace Ombi.Api.Sonarr.Models.V3
{
public class LanguageProfiles
{
public string name { get; set; }
public bool upgradeAllowed { get; set; }
public Cutoff cutoff { get; set; }
public Languages[] languages { get; set; }
public int id { get; set; }
}
public class Cutoff
{
public int id { get; set; }
public string name { get; set; }
}
public class Languages
{
public Language languages { get; set; }
public bool allowed { get; set; }
}
public class Language
{
public int id { get; set; }
public string name { get; set; }
}
}

View file

@ -16,18 +16,19 @@ namespace Ombi.Api.Sonarr
Api = api; Api = api;
} }
private IApi Api { get; } protected IApi Api { get; }
protected virtual string ApiBaseUrl => "/api/";
public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl) public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl)
{ {
var request = new Request("/api/profile", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}profile", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<SonarrProfile>>(request); return await Api.Request<List<SonarrProfile>>(request);
} }
public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl) public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
{ {
var request = new Request("/api/rootfolder", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}rootfolder", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<SonarrRootFolder>>(request); return await Api.Request<List<SonarrRootFolder>>(request);
} }
@ -40,7 +41,7 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<IEnumerable<SonarrSeries>> GetSeries(string apiKey, string baseUrl) public async Task<IEnumerable<SonarrSeries>> GetSeries(string apiKey, string baseUrl)
{ {
var request = new Request("/api/series", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}series", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
var results = await Api.Request<List<SonarrSeries>>(request); var results = await Api.Request<List<SonarrSeries>>(request);
@ -63,7 +64,7 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<SonarrSeries> GetSeriesById(int id, string apiKey, string baseUrl) public async Task<SonarrSeries> GetSeriesById(int id, string apiKey, string baseUrl)
{ {
var request = new Request($"/api/series/{id}", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}series/{id}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
var result = await Api.Request<SonarrSeries>(request); var result = await Api.Request<SonarrSeries>(request);
if (result?.seasons?.Length > 0) if (result?.seasons?.Length > 0)
@ -82,7 +83,7 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<SonarrSeries> UpdateSeries(SonarrSeries updated, string apiKey, string baseUrl) public async Task<SonarrSeries> UpdateSeries(SonarrSeries updated, string apiKey, string baseUrl)
{ {
var request = new Request("/api/series/", baseUrl, HttpMethod.Put); var request = new Request($"{ApiBaseUrl}series/", baseUrl, HttpMethod.Put);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(updated); request.AddJsonBody(updated);
return await Api.Request<SonarrSeries>(request); return await Api.Request<SonarrSeries>(request);
@ -94,7 +95,7 @@ namespace Ombi.Api.Sonarr
{ {
return new NewSeries { ErrorMessages = new List<string> { seriesToAdd.Validate() } }; return new NewSeries { ErrorMessages = new List<string> { seriesToAdd.Validate() } };
} }
var request = new Request("/api/series/", baseUrl, HttpMethod.Post); var request = new Request($"{ApiBaseUrl}series/", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(seriesToAdd); request.AddJsonBody(seriesToAdd);
@ -120,7 +121,7 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<IEnumerable<Episode>> GetEpisodes(int seriesId, string apiKey, string baseUrl) public async Task<IEnumerable<Episode>> GetEpisodes(int seriesId, string apiKey, string baseUrl)
{ {
var request = new Request($"/api/Episode?seriesId={seriesId}", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}Episode?seriesId={seriesId}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<Episode>>(request); return await Api.Request<List<Episode>>(request);
} }
@ -134,14 +135,14 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<Episode> GetEpisodeById(int episodeId, string apiKey, string baseUrl) public async Task<Episode> GetEpisodeById(int episodeId, string apiKey, string baseUrl)
{ {
var request = new Request($"/api/Episode/{episodeId}", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}Episode/{episodeId}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<Episode>(request); return await Api.Request<Episode>(request);
} }
public async Task<EpisodeUpdateResult> UpdateEpisode(Episode episodeToUpdate, string apiKey, string baseUrl) public async Task<EpisodeUpdateResult> UpdateEpisode(Episode episodeToUpdate, string apiKey, string baseUrl)
{ {
var request = new Request($"/api/Episode/", baseUrl, HttpMethod.Put); var request = new Request($"{ApiBaseUrl}Episode/", baseUrl, HttpMethod.Put);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(episodeToUpdate); request.AddJsonBody(episodeToUpdate);
return await Api.Request<EpisodeUpdateResult>(request); return await Api.Request<EpisodeUpdateResult>(request);
@ -189,7 +190,7 @@ namespace Ombi.Api.Sonarr
private async Task<CommandResult> Command(string apiKey, string baseUrl, object body) private async Task<CommandResult> Command(string apiKey, string baseUrl, object body)
{ {
var request = new Request($"/api/Command/", baseUrl, HttpMethod.Post); var request = new Request($"{ApiBaseUrl}Command/", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(body); request.AddJsonBody(body);
return await Api.Request<CommandResult>(request); return await Api.Request<CommandResult>(request);
@ -197,7 +198,7 @@ namespace Ombi.Api.Sonarr
public async Task<SystemStatus> SystemStatus(string apiKey, string baseUrl) public async Task<SystemStatus> SystemStatus(string apiKey, string baseUrl)
{ {
var request = new Request("/api/system/status", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}system/status", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<SystemStatus>(request); return await Api.Request<SystemStatus>(request);
@ -217,7 +218,7 @@ namespace Ombi.Api.Sonarr
ignoreEpisodesWithoutFiles = false, ignoreEpisodesWithoutFiles = false,
} }
}; };
var request = new Request("/api/seasonpass", baseUrl, HttpMethod.Post); var request = new Request($"{ApiBaseUrl}seasonpass", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(seasonPass); request.AddJsonBody(seasonPass);

View file

@ -0,0 +1,25 @@
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Sonarr.Models.V3;
namespace Ombi.Api.Sonarr
{
public class SonarrV3Api : SonarrApi, ISonarrV3Api
{
public SonarrV3Api(IApi api) : base(api)
{
}
protected override string ApiBaseUrl => "/api/v3/";
public async Task<IEnumerable<LanguageProfiles>> LanguageProfiles(string apiKey, string baseUrl)
{
var request = new Request($"{ApiBaseUrl}languageprofile", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<LanguageProfiles>>(request);
}
}
}

View file

@ -41,7 +41,7 @@ namespace Ombi.Api
{ {
if (!request.IgnoreErrors) if (!request.IgnoreErrors)
{ {
LogError(request, httpResponseMessage); await LogError(request, httpResponseMessage);
} }
if (request.Retry) if (request.Retry)
@ -105,7 +105,7 @@ namespace Ombi.Api
{ {
if (!request.IgnoreErrors) if (!request.IgnoreErrors)
{ {
LogError(request, httpResponseMessage); await LogError(request, httpResponseMessage);
} }
} }
// do something with the response // do something with the response
@ -126,7 +126,7 @@ namespace Ombi.Api
{ {
if (!request.IgnoreErrors) if (!request.IgnoreErrors)
{ {
LogError(request, httpResponseMessage); await LogError(request, httpResponseMessage);
} }
} }
} }
@ -149,10 +149,15 @@ namespace Ombi.Api
} }
} }
private void LogError(Request request, HttpResponseMessage httpResponseMessage) private async Task LogError(Request request, HttpResponseMessage httpResponseMessage)
{ {
Logger.LogError(LoggingEvents.Api, Logger.LogError(LoggingEvents.Api,
$"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}, RequestUri: {request.FullUri}"); $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}, RequestUri: {request.FullUri}");
if (Logger.IsEnabled(LogLevel.Debug))
{
var content = await httpResponseMessage.Content.ReadAsStringAsync();
Logger.LogDebug(content);
}
} }
} }
} }

View file

@ -9,8 +9,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Polly" Version="6.1.0" /> <PackageReference Include="Polly" Version="6.1.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup> </ItemGroup>

View file

@ -0,0 +1,73 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using AutoFixture;
using Moq;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Engine;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Tests.Engine
{
[TestFixture]
public class VoteEngineTests
{
[SetUp]
public void Setup()
{
F = new Fixture();
VoteRepository = new Mock<IRepository<Votes>>();
VoteSettings = new Mock<ISettingsService<VoteSettings>>();
MusicRequestEngine = new Mock<IMusicRequestEngine>();
TvRequestEngine = new Mock<ITvRequestEngine>();
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"}}));
Rule = new Mock<IRuleEvaluator>();
Engine = new VoteEngine(VoteRepository.Object, User.Object, UserManager.Object, Rule.Object, VoteSettings.Object, MusicRequestEngine.Object,
TvRequestEngine.Object, MovieRequestEngine.Object);
}
public Fixture F { get; set; }
public VoteEngine Engine { get; set; }
public Mock<IPrincipal> User { get; set; }
public Mock<OmbiUserManager> UserManager { get; set; }
public Mock<IRuleEvaluator> Rule { get; set; }
public Mock<IRepository<Votes>> VoteRepository { get; set; }
public Mock<ISettingsService<VoteSettings>> VoteSettings { get; set; }
public Mock<IMusicRequestEngine> MusicRequestEngine { get; set; }
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()
{
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings());
var votes = F.CreateMany<Votes>().ToList();
votes.Add(new Votes
{
RequestId = 1,
RequestType = RequestType.Movie,
UserId = "abc"
});
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes));
var result = await Engine.UpVote(1, RequestType.Movie);
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);
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
}
}
}

View file

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Moq" Version="4.9.0" /> <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.10.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" /> <PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference> <packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -4,6 +4,7 @@ using Moq;
using Ombi.Core.Rule.Rules.Request; using Ombi.Core.Rule.Rules.Request;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using NUnit.Framework; using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Helpers; using Ombi.Helpers;
namespace Ombi.Core.Tests.Rule.Request namespace Ombi.Core.Tests.Rule.Request
@ -16,7 +17,7 @@ namespace Ombi.Core.Tests.Rule.Request
{ {
PrincipalMock = new Mock<IPrincipal>(); PrincipalMock = new Mock<IPrincipal>();
Rule = new AutoApproveRule(PrincipalMock.Object); Rule = new AutoApproveRule(PrincipalMock.Object, null);
} }

View file

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using Ombi.Core.Rule.Rules; using Ombi.Core.Rule.Rules;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
@ -15,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Request
{ {
PrincipalMock = new Mock<IPrincipal>(); PrincipalMock = new Mock<IPrincipal>();
Rule = new CanRequestRule(PrincipalMock.Object); Rule = new CanRequestRule(PrincipalMock.Object, null);
} }

View file

@ -18,13 +18,13 @@ namespace Ombi.Core.Tests.Rule.Search
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
ContextMock = new Mock<IRepository<CouchPotatoCache>>(); ContextMock = new Mock<IExternalRepository<CouchPotatoCache>>();
Rule = new CouchPotatoCacheRule(ContextMock.Object); Rule = new CouchPotatoCacheRule(ContextMock.Object);
} }
private CouchPotatoCacheRule Rule { get; set; } private CouchPotatoCacheRule Rule { get; set; }
private Mock<IRepository<CouchPotatoCache>> ContextMock { get; set; } private Mock<IExternalRepository<CouchPotatoCache>> ContextMock { get; set; }
[Test] [Test]
public async Task Should_ReturnApproved_WhenMovieIsInCouchPotato() public async Task Should_ReturnApproved_WhenMovieIsInCouchPotato()

View file

@ -16,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup() public void Setup()
{ {
ContextMock = new Mock<IEmbyContentRepository>(); ContextMock = new Mock<IEmbyContentRepository>();
Rule = new EmbyAvailabilityRule(ContextMock.Object); Rule = new EmbyAvailabilityRule(ContextMock.Object, null);
} }
private EmbyAvailabilityRule Rule { get; set; } private EmbyAvailabilityRule Rule { get; set; }

View file

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
@ -14,7 +15,7 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup() public void Setup()
{ {
ContextMock = new Mock<IPlexContentRepository>(); ContextMock = new Mock<IPlexContentRepository>();
Rule = new PlexAvailabilityRule(ContextMock.Object); Rule = new PlexAvailabilityRule(ContextMock.Object, new Mock<ILogger<PlexAvailabilityRule>>().Object);
} }
private PlexAvailabilityRule Rule { get; set; } private PlexAvailabilityRule Rule { get; set; }

View file

@ -15,13 +15,13 @@ namespace Ombi.Core.Tests.Rule.Search
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
ContextMock = new Mock<IRepository<RadarrCache>>(); ContextMock = new Mock<IExternalRepository<RadarrCache>>();
Rule = new RadarrCacheRule(ContextMock.Object); Rule = new RadarrCacheRule(ContextMock.Object);
} }
private RadarrCacheRule Rule { get; set; } private RadarrCacheRule Rule { get; set; }
private Mock<IRepository<RadarrCache>> ContextMock { get; set; } private Mock<IExternalRepository<RadarrCache>> ContextMock { get; set; }
[Test] [Test]
public async Task Should_ReturnApproved_WhenMovieIsInRadarr() public async Task Should_ReturnApproved_WhenMovieIsInRadarr()

View file

@ -29,6 +29,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Ombi.Api.Emby; using Ombi.Api.Emby;
@ -101,6 +102,22 @@ namespace Ombi.Core.Authentication
return true; return true;
} }
public async Task<OmbiUser> GetOmbiUserFromPlexToken(string plexToken)
{
var plexAccount = await _plexApi.GetAccount(plexToken);
// Check for a ombi user
if (plexAccount?.user != null)
{
var potentialOmbiUser = await Users.FirstOrDefaultAsync(x =>
x.ProviderUserId == plexAccount.user.id);
return potentialOmbiUser;
}
return null;
}
/// <summary> /// <summary>
/// Sign the user into plex and make sure we can get the authentication token. /// Sign the user into plex and make sure we can get the authentication token.
/// <remarks>We do not check if the user is in the owners "friends" since they must have a local user account to get this far</remarks> /// <remarks>We do not check if the user is in the owners "friends" since they must have a local user account to get this far</remarks>

View file

@ -157,6 +157,24 @@ namespace Ombi.Core.Engine
} }
} }
private string defaultLangCode;
protected async Task<string> DefaultLanguageCode(string currentCode)
{
if (currentCode.HasValue())
{
return currentCode;
}
var s = await GetOmbiSettings();
return s.DefaultLanguageCode;
}
private OmbiSettings ombiSettings;
protected async Task<OmbiSettings> GetOmbiSettings()
{
return ombiSettings ?? (ombiSettings = await OmbiSettings.GetSettingsAsync());
}
public class HideResult public class HideResult
{ {
public bool Hide { get; set; } public bool Hide { get; set; }

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -12,7 +12,7 @@ namespace Ombi.Core.Engine
{ {
Task<RequestEngineResult>ApproveAlbum(AlbumRequest request); Task<RequestEngineResult>ApproveAlbum(AlbumRequest request);
Task<RequestEngineResult> ApproveAlbumById(int requestId); Task<RequestEngineResult> ApproveAlbumById(int requestId);
Task<RequestEngineResult> DenyAlbumById(int modelId); Task<RequestEngineResult> DenyAlbumById(int modelId, string reason);
Task<IEnumerable<AlbumRequest>> GetRequests(); Task<IEnumerable<AlbumRequest>> GetRequests();
Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position, OrderFilterModel orderFilter); Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position, OrderFilterModel orderFilter);
Task<int> GetTotal(); Task<int> GetTotal();

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Core.Models;
using Ombi.Core.Models.UI;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine
{
public interface IVoteEngine
{
Task<VoteEngineResult> DownVote(int requestId, RequestType requestType);
Task<Votes> GetVoteForUser(int requestId, string userId);
IQueryable<Votes> GetVotes(int requestId, RequestType requestType);
Task RemoveCurrentVote(Votes currentVote);
Task<VoteEngineResult> UpVote(int requestId, RequestType requestType);
Task<List<VoteViewModel>> GetMovieViewModel();
}
}

View file

@ -7,11 +7,8 @@ using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using Ombi.Store.Entities; using Ombi.Store.Entities;
using Microsoft.AspNetCore.Identity;
using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Helpers;
namespace Ombi.Core.Engine.Interfaces namespace Ombi.Core.Engine.Interfaces
{ {

View file

@ -10,14 +10,15 @@ namespace Ombi.Core
Task<IEnumerable<SearchMovieViewModel>> PopularMovies(); Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
Task<IEnumerable<SearchMovieViewModel>> Search(string search); Task<IEnumerable<SearchMovieViewModel>> Search(string search, int? year, string languageCode);
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies(); Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies(); Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId); Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId, string langCode = null);
Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId); Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode);
Task<IEnumerable<SearchMovieViewModel>> SearchActor(string search, string langaugeCode);
} }
} }

View file

@ -12,10 +12,11 @@ namespace Ombi.Core.Engine.Interfaces
Task<IEnumerable<MovieRequests>> SearchMovieRequest(string search); Task<IEnumerable<MovieRequests>> SearchMovieRequest(string search);
Task RemoveMovieRequest(int requestId); Task RemoveMovieRequest(int requestId);
Task RemoveAllMovieRequests();
Task<MovieRequests> UpdateMovieRequest(MovieRequests request); Task<MovieRequests> UpdateMovieRequest(MovieRequests request);
Task<RequestEngineResult> ApproveMovie(MovieRequests request); Task<RequestEngineResult> ApproveMovie(MovieRequests request);
Task<RequestEngineResult> ApproveMovieById(int requestId); Task<RequestEngineResult> ApproveMovieById(int requestId);
Task<RequestEngineResult> DenyMovieById(int modelId); Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason);
} }
} }

View file

@ -12,5 +12,6 @@ namespace Ombi.Core.Engine
Task<IEnumerable<SearchAlbumViewModel>> GetArtistAlbums(string foreignArtistId); Task<IEnumerable<SearchAlbumViewModel>> GetArtistAlbums(string foreignArtistId);
Task<IEnumerable<SearchAlbumViewModel>> SearchAlbum(string search); Task<IEnumerable<SearchAlbumViewModel>> SearchAlbum(string search);
Task<IEnumerable<SearchArtistViewModel>> SearchArtist(string search); Task<IEnumerable<SearchArtistViewModel>> SearchArtist(string search);
Task<SearchAlbumViewModel> GetAlbumInformation(string foreignAlbumId);
} }
} }

View file

@ -12,7 +12,7 @@ namespace Ombi.Core.Engine.Interfaces
Task RemoveTvRequest(int requestId); Task RemoveTvRequest(int requestId);
Task<TvRequests> GetTvRequest(int requestId); Task<TvRequests> GetTvRequest(int requestId);
Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv); Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv);
Task<RequestEngineResult> DenyChildRequest(int requestId); Task<RequestEngineResult> DenyChildRequest(int requestId, string reason);
Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type); Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type);
Task<IEnumerable<TvRequests>> SearchTvRequest(string search); Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
Task<TvRequests> UpdateTvRequest(TvRequests request); Task<TvRequests> UpdateTvRequest(TvRequests request);

View file

@ -51,7 +51,7 @@ namespace Ombi.Core.Engine
/// <returns></returns> /// <returns></returns>
public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model) public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model)
{ {
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId); var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId, model.LanguageCode);
if (movieInfo == null || movieInfo.Id == 0) if (movieInfo == null || movieInfo.Id == 0)
{ {
return new RequestEngineResult return new RequestEngineResult
@ -82,7 +82,9 @@ namespace Ombi.Core.Engine
RequestedDate = DateTime.UtcNow, RequestedDate = DateTime.UtcNow,
Approved = false, Approved = false,
RequestedUserId = userDetails.Id, RequestedUserId = userDetails.Id,
Background = movieInfo.BackdropPath Background = movieInfo.BackdropPath,
LangCode = model.LanguageCode,
RequestedByAlias = model.RequestedByAlias
}; };
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
@ -305,7 +307,7 @@ namespace Ombi.Core.Engine
return await ApproveMovie(request); return await ApproveMovie(request);
} }
public async Task<RequestEngineResult> DenyMovieById(int modelId) public async Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason)
{ {
var request = await MovieRepository.Find(modelId); var request = await MovieRepository.Find(modelId);
if (request == null) if (request == null)
@ -317,12 +319,14 @@ namespace Ombi.Core.Engine
} }
request.Denied = true; request.Denied = true;
request.DeniedReason = denyReason;
// We are denying a request // We are denying a request
NotificationHelper.Notify(request, NotificationType.RequestDeclined); NotificationHelper.Notify(request, NotificationType.RequestDeclined);
await MovieRepository.Update(request); await MovieRepository.Update(request);
return new RequestEngineResult return new RequestEngineResult
{ {
Result = true,
Message = "Request successfully deleted", Message = "Request successfully deleted",
}; };
} }
@ -416,6 +420,12 @@ namespace Ombi.Core.Engine
await MovieRepository.Delete(request); await MovieRepository.Delete(request);
} }
public async Task RemoveAllMovieRequests()
{
var request = MovieRepository.GetAll();
await MovieRepository.DeleteRange(request);
}
public async Task<bool> UserHasRequest(string userId) public async Task<bool> UserHasRequest(string userId)
{ {
return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId); return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId);
@ -483,7 +493,7 @@ namespace Ombi.Core.Engine
RequestType = RequestType.Movie, RequestType = RequestType.Movie,
}); });
return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!"}; return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!", RequestId = model.Id};
} }
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user) public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)

View file

@ -1,23 +1,22 @@
using System; using AutoMapper;
using AutoMapper; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests; using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using Ombi.Core.Authentication;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities; using Ombi.Store.Entities;
using Ombi.Store.Repository; 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 namespace Ombi.Core.Engine
{ {
@ -32,18 +31,21 @@ namespace Ombi.Core.Engine
Logger = logger; Logger = logger;
} }
private IMovieDbApi MovieApi { get; } protected IMovieDbApi MovieApi { get; }
private IMapper Mapper { get; } protected IMapper Mapper { get; }
private ILogger<MovieSearchEngine> Logger { get; } private ILogger<MovieSearchEngine> Logger { get; }
protected const int MovieLimit = 10;
/// <summary> /// <summary>
/// Lookups the imdb information. /// Lookups the imdb information.
/// </summary> /// </summary>
/// <param name="theMovieDbId">The movie database identifier.</param> /// <param name="theMovieDbId">The movie database identifier.</param>
/// <returns></returns> /// <returns></returns>
public async Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId) public async Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId, string langCode = null)
{ {
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId); langCode = await DefaultLanguageCode(langCode);
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId, langCode);
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo); var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo);
return await ProcessSingleMovie(viewMovie, true); return await ProcessSingleMovie(viewMovie, true);
@ -52,31 +54,58 @@ namespace Ombi.Core.Engine
/// <summary> /// <summary>
/// Searches the specified movie. /// Searches the specified movie.
/// </summary> /// </summary>
/// <param name="search">The search.</param> public async Task<IEnumerable<SearchMovieViewModel>> Search(string search, int? year, string langaugeCode)
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
{ {
var result = await MovieApi.SearchMovie(search); langaugeCode = await DefaultLanguageCode(langaugeCode);
var result = await MovieApi.SearchMovie(search, year, langaugeCode);
if (result != null) if (result != null)
{ {
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
} }
return null; return null;
} }
public async Task<IEnumerable<SearchMovieViewModel>> SearchActor(string search, string langaugeCode)
{
langaugeCode = await DefaultLanguageCode(langaugeCode);
var people = await MovieApi.SearchByActor(search, langaugeCode);
var person = people?.results?.Count > 0 ? people.results.FirstOrDefault() : null;
var resultSet = new List<SearchMovieViewModel>();
if (person == null)
{
return resultSet;
}
// Get this person movie credits
var credits = await MovieApi.GetActorMovieCredits(person.id, langaugeCode);
// Grab results from both cast and crew, prefer items in cast. we can handle directors like this.
var movieResults = (from role in credits.cast select new { Id = role.id, Title = role.title, ReleaseDate = role.release_date }).ToList();
movieResults.AddRange((from job in credits.crew select new { Id = job.id, Title = job.title, ReleaseDate = job.release_date }).ToList());
movieResults = movieResults.Take(10).ToList();
foreach (var movieResult in movieResults)
{
resultSet.Add(await LookupImdbInformation(movieResult.Id, langaugeCode));
}
return resultSet;
}
/// <summary> /// <summary>
/// Get similar movies to the id passed in /// Get similar movies to the id passed in
/// </summary> /// </summary>
/// <param name="theMovieDbId"></param> /// <param name="theMovieDbId"></param>
/// <returns></returns> /// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId) public async Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode)
{ {
var result = await MovieApi.SimilarMovies(theMovieDbId); langCode = await DefaultLanguageCode(langCode);
var result = await MovieApi.SimilarMovies(theMovieDbId, langCode);
if (result != null) if (result != null)
{ {
Logger.LogDebug("Search Result: {result}", result); Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
} }
return null; return null;
} }
@ -87,10 +116,15 @@ namespace Ombi.Core.Engine
/// <returns></returns> /// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies() public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
{ {
var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () => await MovieApi.PopularMovies(), DateTime.Now.AddHours(12));
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) if (result != null)
{ {
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
} }
return null; return null;
} }
@ -101,10 +135,14 @@ namespace Ombi.Core.Engine
/// <returns></returns> /// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies() public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
{ {
var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () => await MovieApi.TopRated(), DateTime.Now.AddHours(12)); 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) if (result != null)
{ {
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
} }
return null; return null;
} }
@ -115,11 +153,15 @@ namespace Ombi.Core.Engine
/// <returns></returns> /// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies() public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
{ {
var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () => await MovieApi.Upcoming(), DateTime.Now.AddHours(12)); 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) if (result != null)
{ {
Logger.LogDebug("Search Result: {result}", result); Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
} }
return null; return null;
} }
@ -130,15 +172,19 @@ namespace Ombi.Core.Engine
/// <returns></returns> /// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies() public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
{ {
var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () => await MovieApi.NowPlaying(), DateTime.Now.AddHours(12)); 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) if (result != null)
{ {
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
} }
return null; return null;
} }
private async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse( protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
IEnumerable<MovieSearchResult> movies) IEnumerable<MovieSearchResult> movies)
{ {
var viewMovies = new List<SearchMovieViewModel>(); var viewMovies = new List<SearchMovieViewModel>();
@ -149,17 +195,18 @@ namespace Ombi.Core.Engine
return viewMovies; return viewMovies;
} }
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false) protected async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false)
{ {
if (lookupExtraInfo) if (lookupExtraInfo && viewMovie.ImdbId.IsNullOrEmpty())
{ {
var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id); var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id);
viewMovie.Id = showInfo.Id; // TheMovieDbId viewMovie.Id = showInfo.Id; // TheMovieDbId
viewMovie.ImdbId = showInfo.ImdbId; viewMovie.ImdbId = showInfo.ImdbId;
var usDates = viewMovie.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
viewMovie.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate;
} }
var usDates = viewMovie.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
viewMovie.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate;
viewMovie.TheMovieDbId = viewMovie.Id.ToString(); viewMovie.TheMovieDbId = viewMovie.Id.ToString();
await RunSearchRules(viewMovie); await RunSearchRules(viewMovie);
@ -174,9 +221,13 @@ namespace Ombi.Core.Engine
{ {
// Check if this user requested it // Check if this user requested it
var user = await GetUser(); var user = await GetUser();
if (user == null)
{
return;
}
var request = await RequestService.MovieRequestService.GetAll() var request = await RequestService.MovieRequestService.GetAll()
.AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id); .AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id);
if (request) if (request || viewModel.Available)
{ {
viewModel.ShowSubscribe = false; viewModel.ShowSubscribe = false;
} }

View file

@ -83,7 +83,8 @@ namespace Ombi.Core.Engine
Title = album.title, Title = album.title,
Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url, Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url, Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url,
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty,
RequestedByAlias = model.RequestedByAlias
}; };
if (requestModel.Cover.IsNullOrEmpty()) if (requestModel.Cover.IsNullOrEmpty())
{ {
@ -299,7 +300,7 @@ namespace Ombi.Core.Engine
return await ApproveAlbum(request); return await ApproveAlbum(request);
} }
public async Task<RequestEngineResult> DenyAlbumById(int modelId) public async Task<RequestEngineResult> DenyAlbumById(int modelId, string reason)
{ {
var request = await MusicRepository.Find(modelId); var request = await MusicRepository.Find(modelId);
if (request == null) if (request == null)
@ -311,6 +312,7 @@ namespace Ombi.Core.Engine
} }
request.Denied = true; request.Denied = true;
request.DeniedReason = reason;
// We are denying a request // We are denying a request
NotificationHelper.Notify(request, NotificationType.RequestDeclined); NotificationHelper.Notify(request, NotificationType.RequestDeclined);
await MusicRepository.Update(request); await MusicRepository.Update(request);
@ -495,7 +497,7 @@ namespace Ombi.Core.Engine
RequestType = RequestType.Album, RequestType = RequestType.Album,
}); });
return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!" }; return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!", RequestId = model.Id };
} }

View file

@ -60,6 +60,18 @@ namespace Ombi.Core.Engine
return vm; return vm;
} }
public async Task<SearchAlbumViewModel> GetAlbumInformation(string foreignAlbumId)
{
var settings = await GetSettings();
var result = await _lidarrApi.AlbumInformation(foreignAlbumId, settings.ApiKey, settings.FullUri);
var vm = await MapIntoAlbumVm(result, settings);
return vm;
}
/// <summary> /// <summary>
/// Searches the specified artist /// Searches the specified artist
/// </summary> /// </summary>
@ -143,6 +155,48 @@ namespace Ombi.Core.Engine
return vm; return vm;
} }
// TODO
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(AlbumResponse a, LidarrSettings settings)
{
var vm = new SearchAlbumViewModel
{
ForeignAlbumId = a.foreignAlbumId,
Monitored = a.monitored,
Rating = a.ratings?.value ?? 0m,
ReleaseDate = a.releaseDate,
Title = a.title,
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.Replace("http", "https"),
Genres = a.genres
};
if (a.artistId > 0)
{
//TODO THEY HAVE FIXED THIS IN DEV
// The JSON is different for some stupid reason
// Need to lookup the artist now and all the images -.-"
var artist = await _lidarrApi.GetArtist(a.artistId, settings.ApiKey, settings.FullUri);
vm.ArtistName = artist.artistName;
vm.ForeignArtistId = artist.foreignArtistId;
}
else
{
//vm.ForeignArtistId = a.artistId?.foreignArtistId;
//vm.ArtistName = a.artist?.artistName;
}
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.Replace("http", "https");
if (vm.Cover.IsNullOrEmpty())
{
//vm.Cover = a.remoteCover;
}
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
await RunSearchRules(vm);
return vm;
}
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(AlbumLookup a, LidarrSettings settings) private async Task<SearchAlbumViewModel> MapIntoAlbumVm(AlbumLookup a, LidarrSettings settings)
{ {
var vm = new SearchAlbumViewModel var vm = new SearchAlbumViewModel
@ -152,7 +206,8 @@ namespace Ombi.Core.Engine
Rating = a.ratings?.value ?? 0m, Rating = a.ratings?.value ?? 0m,
ReleaseDate = a.releaseDate, ReleaseDate = a.releaseDate,
Title = a.title, Title = a.title,
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.Replace("http", "https"),
Genres = a.genres
}; };
if (a.artistId > 0) if (a.artistId > 0)
{ {
@ -169,7 +224,7 @@ namespace Ombi.Core.Engine
vm.ArtistName = a.artist?.artistName; vm.ArtistName = a.artist?.artistName;
} }
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url; vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.Replace("http", "https");
if (vm.Cover.IsNullOrEmpty()) if (vm.Cover.IsNullOrEmpty())
{ {
vm.Cover = a.remoteCover; vm.Cover = a.remoteCover;

View file

@ -6,5 +6,6 @@
public string Message { get; set; } public string Message { get; set; }
public bool IsError => !string.IsNullOrEmpty(ErrorMessage); public bool IsError => !string.IsNullOrEmpty(ErrorMessage);
public string ErrorMessage { get; set; } public string ErrorMessage { get; set; }
public int RequestId { get; set; }
} }
} }

View file

@ -31,14 +31,13 @@ namespace Ombi.Core.Engine
{ {
public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user, public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, 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) IRepository<RequestSubscription> sub) : base(user, requestService, rule, manager, cache, settings, sub)
{ {
TvApi = tvApi; TvApi = tvApi;
MovieDbApi = movApi; MovieDbApi = movApi;
NotificationHelper = helper; NotificationHelper = helper;
TvSender = sender; TvSender = sender;
Audit = audit;
_requestLog = rl; _requestLog = rl;
} }
@ -46,7 +45,6 @@ namespace Ombi.Core.Engine
private ITvMazeApi TvApi { get; } private ITvMazeApi TvApi { get; }
private IMovieDbApi MovieDbApi { get; } private IMovieDbApi MovieDbApi { get; }
private ITvSender TvSender { get; } private ITvSender TvSender { get; }
private IAuditRepository Audit { get; }
private readonly IRepository<RequestLog> _requestLog; private readonly IRepository<RequestLog> _requestLog;
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv) public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
@ -84,8 +82,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); var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId);
if (existingRequest != null) if (existingRequest != null)
{ {
@ -116,6 +112,7 @@ namespace Ombi.Core.Engine
} }
// Remove the ID since this is a new child // Remove the ID since this is a new child
// This was a TVDBID for the request rules to run
tvBuilder.ChildRequest.Id = 0; tvBuilder.ChildRequest.Id = 0;
if (!tvBuilder.ChildRequest.SeasonRequests.Any()) if (!tvBuilder.ChildRequest.SeasonRequests.Any())
{ {
@ -350,7 +347,6 @@ namespace Ombi.Core.Engine
public async Task<TvRequests> UpdateTvRequest(TvRequests request) public async Task<TvRequests> UpdateTvRequest(TvRequests request)
{ {
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
var allRequests = TvRepository.Get(); var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id); var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id);
@ -384,6 +380,7 @@ namespace Ombi.Core.Engine
foreach (var ep in s.Episodes) foreach (var ep in s.Episodes)
{ {
ep.Approved = true; ep.Approved = true;
ep.Requested = true;
} }
} }
@ -392,7 +389,6 @@ namespace Ombi.Core.Engine
if (request.Approved) if (request.Approved)
{ {
NotificationHelper.Notify(request, NotificationType.RequestApproved); NotificationHelper.Notify(request, NotificationType.RequestApproved);
await Audit.Record(AuditType.Approved, AuditArea.TvRequest, $"Approved Request {request.Title}", Username);
// Autosend // Autosend
await TvSender.Send(request); await TvSender.Send(request);
} }
@ -402,7 +398,7 @@ namespace Ombi.Core.Engine
}; };
} }
public async Task<RequestEngineResult> DenyChildRequest(int requestId) public async Task<RequestEngineResult> DenyChildRequest(int requestId, string reason)
{ {
var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId); var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId);
if (request == null) if (request == null)
@ -413,6 +409,7 @@ namespace Ombi.Core.Engine
}; };
} }
request.Denied = true; request.Denied = true;
request.DeniedReason = reason;
await TvRepository.UpdateChild(request); await TvRepository.UpdateChild(request);
NotificationHelper.Notify(request, NotificationType.RequestDeclined); NotificationHelper.Notify(request, NotificationType.RequestDeclined);
return new RequestEngineResult return new RequestEngineResult
@ -423,9 +420,7 @@ namespace Ombi.Core.Engine
public async Task<ChildRequests> UpdateChildRequest(ChildRequests request) 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; return request;
} }
@ -443,7 +438,6 @@ namespace Ombi.Core.Engine
// Delete the parent // Delete the parent
TvRepository.Db.TvRequests.Remove(parent); TvRepository.Db.TvRequests.Remove(parent);
} }
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
await TvRepository.Db.SaveChangesAsync(); await TvRepository.Db.SaveChangesAsync();
} }
@ -451,8 +445,7 @@ namespace Ombi.Core.Engine
public async Task RemoveTvRequest(int requestId) public async Task RemoveTvRequest(int requestId)
{ {
var request = await TvRepository.Get().FirstOrDefaultAsync(x => x.Id == 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) public async Task<bool> UserHasRequest(string userId)
@ -604,15 +597,16 @@ namespace Ombi.Core.Engine
var result = await TvSender.Send(model); var result = await TvSender.Send(model);
if (result.Success) if (result.Success)
{ {
return new RequestEngineResult { Result = true }; return new RequestEngineResult { Result = true, RequestId = model.Id};
} }
return new RequestEngineResult return new RequestEngineResult
{ {
ErrorMessage = result.Message ErrorMessage = result.Message,
RequestId = model.Id
}; };
} }
return new RequestEngineResult { Result = true }; return new RequestEngineResult { Result = true, RequestId = model.Id };
} }
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user) public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)

View file

@ -40,8 +40,8 @@ namespace Ombi.Core.Engine
EmbyContentRepo = embyRepo; EmbyContentRepo = embyRepo;
} }
private ITvMazeApi TvMazeApi { get; } protected ITvMazeApi TvMazeApi { get; }
private IMapper Mapper { get; } protected IMapper Mapper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; } private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; } private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexContentRepository PlexContentRepo { get; } private IPlexContentRepository PlexContentRepo { get; }
@ -61,7 +61,7 @@ namespace Ombi.Core.Engine
{ {
continue; continue;
} }
retVal.Add(await ProcessResult(tvMazeSearch)); retVal.Add(ProcessResult(tvMazeSearch));
} }
return retVal; return retVal;
} }
@ -99,7 +99,7 @@ namespace Ombi.Core.Engine
{ {
Url = e.url, Url = e.url,
Title = e.name, Title = e.name,
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()), AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
EpisodeNumber = e.number, EpisodeNumber = e.number,
}); });
@ -112,7 +112,7 @@ namespace Ombi.Core.Engine
{ {
Url = e.url, Url = e.url,
Title = e.name, Title = e.name,
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()), AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
EpisodeNumber = e.number, EpisodeNumber = e.number,
}); });
} }
@ -123,7 +123,7 @@ namespace Ombi.Core.Engine
public async Task<IEnumerable<SearchTvShowViewModel>> Popular() public async Task<IEnumerable<SearchTvShowViewModel>> Popular()
{ {
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result); var processed = ProcessResults(result);
return processed; return processed;
} }
@ -131,35 +131,35 @@ namespace Ombi.Core.Engine
{ {
var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12)); var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result); var processed = ProcessResults(result);
return processed; return processed;
} }
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches() public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
{ {
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12)); var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result); var processed = ProcessResults(result);
return processed; return processed;
} }
public async Task<IEnumerable<SearchTvShowViewModel>> Trending() public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
{ {
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12)); var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result); var processed = ProcessResults(result);
return processed; return processed;
} }
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items) protected IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
{ {
var retVal = new List<SearchTvShowViewModel>(); var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items) foreach (var tvMazeSearch in items)
{ {
retVal.Add(await ProcessResult(tvMazeSearch)); retVal.Add(ProcessResult(tvMazeSearch));
} }
return retVal; return retVal;
} }
private async Task<SearchTvShowViewModel> ProcessResult<T>(T tvMazeSearch) protected SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
{ {
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch); return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
} }

View file

@ -0,0 +1,263 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models;
using Ombi.Core.Models.UI;
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
{
public class VoteEngine : BaseEngine, IVoteEngine
{
public VoteEngine(IRepository<Votes> votes, IPrincipal user, OmbiUserManager um, IRuleEvaluator r, ISettingsService<VoteSettings> voteSettings,
IMusicRequestEngine musicRequestEngine, ITvRequestEngine tvRequestEngine, IMovieRequestEngine movieRequestEngine) : base(user, um, r)
{
_voteRepository = votes;
_voteSettings = voteSettings;
_movieRequestEngine = movieRequestEngine;
_musicRequestEngine = musicRequestEngine;
_tvRequestEngine = tvRequestEngine;
}
private readonly IRepository<Votes> _voteRepository;
private readonly ISettingsService<VoteSettings> _voteSettings;
private readonly IMusicRequestEngine _musicRequestEngine;
private readonly ITvRequestEngine _tvRequestEngine;
private readonly IMovieRequestEngine _movieRequestEngine;
public async Task<List<VoteViewModel>> GetMovieViewModel()
{
var vm = new List<VoteViewModel>();
var movieRequests = await _movieRequestEngine.GetRequests();
var tvRequestsTask = _tvRequestEngine.GetRequests();
var musicRequestsTask = _musicRequestEngine.GetRequests();
var user = await GetUser();
foreach (var r in movieRequests)
{
if (r.Available || r.Approved || (r.Denied ?? false))
{
continue;
}
// Make model
var votes = GetVotes(r.Id, RequestType.Movie);
var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync();
var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted);
vm.Add(new VoteViewModel
{
Upvotes = upVotes,
Downvotes = downVotes,
RequestId = r.Id,
RequestType = RequestType.Movie,
Title = r.Title,
Image = $"https://image.tmdb.org/t/p/w500/{r.PosterPath}",
Background = $"https://image.tmdb.org/t/p/w1280{r.Background}",
Description = r.Overview,
AlreadyVoted = myVote != null,
MyVote = myVote?.VoteType ?? VoteType.Downvote
});
}
foreach (var r in await musicRequestsTask)
{
if (r.Available || r.Approved || (r.Denied ?? false))
{
continue;
}
// Make model
var votes = GetVotes(r.Id, RequestType.Album);
var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync();
var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted);
vm.Add(new VoteViewModel
{
Upvotes = upVotes,
Downvotes = downVotes,
RequestId = r.Id,
RequestType = RequestType.Album,
Title = r.Title,
Image = r.Cover,
Background = r.Cover,
Description = r.ArtistName,
AlreadyVoted = myVote != null,
MyVote = myVote?.VoteType ?? VoteType.Downvote
});
}
foreach (var r in await tvRequestsTask)
{
foreach (var childRequests in r.ChildRequests)
{
var finalsb = new StringBuilder();
if (childRequests.Available || childRequests.Approved || (childRequests.Denied ?? false))
{
continue;
}
var votes = GetVotes(childRequests.Id, RequestType.TvShow);
// Make model
var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync();
var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted);
foreach (var epInformation in childRequests.SeasonRequests.OrderBy(x => x.SeasonNumber))
{
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
var episodeString = StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
finalsb.Append("<br />");
}
vm.Add(new VoteViewModel
{
Upvotes = upVotes,
Downvotes = downVotes,
RequestId = childRequests.Id,
RequestType = RequestType.TvShow,
Title = r.Title,
Image = r.PosterPath,
Background = r.Background,
Description = finalsb.ToString(),
AlreadyVoted = myVote != null,
MyVote = myVote?.VoteType ?? VoteType.Downvote
});
}
}
return vm;
}
public IQueryable<Votes> GetVotes(int requestId, RequestType requestType)
{
return _voteRepository.GetAll().Where(x => x.RequestType == requestType && requestId == x.RequestId);
}
public Task<Votes> GetVoteForUser(int requestId, string userId)
{
return _voteRepository.GetAll().FirstOrDefaultAsync(x => x.RequestId == requestId && x.UserId == userId);
}
public async Task<VoteEngineResult> UpVote(int requestId, RequestType requestType)
{
var voteSettings = await _voteSettings.GetSettingsAsync();
if (!voteSettings.Enabled)
{
return new VoteEngineResult {Result = true};
}
// How many votes does this have?!
var currentVotes = GetVotes(requestId, requestType);
var user = await GetUser();
// Does this user have a downvote? If so we should revert it and make it an upvote
var currentVote = await GetVoteForUser(requestId, user.Id);
if (currentVote != null && currentVote.VoteType == VoteType.Upvote)
{
return new VoteEngineResult { ErrorMessage = "You have already voted!" };
}
await RemoveCurrentVote(currentVote);
await _movieRequestEngine.SubscribeToRequest(requestId, requestType);
await _voteRepository.Add(new Votes
{
Date = DateTime.UtcNow,
RequestId = requestId,
RequestType = requestType,
UserId = user.Id,
VoteType = VoteType.Upvote
});
var upVotes = await currentVotes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
var downVotes = -(await currentVotes.Where(x => x.VoteType == VoteType.Downvote).CountAsync());
var totalVotes = upVotes + downVotes;
RequestEngineResult result = null;
switch (requestType)
{
case RequestType.TvShow:
if (totalVotes >= voteSettings.TvShowVoteMax)
{
result = await _tvRequestEngine.ApproveChildRequest(requestId);
}
break;
case RequestType.Movie:
if (totalVotes >= voteSettings.MovieVoteMax)
{
result = await _movieRequestEngine.ApproveMovieById(requestId);
}
break;
case RequestType.Album:
if (totalVotes >= voteSettings.MusicVoteMax)
{
result = await _musicRequestEngine.ApproveAlbumById(requestId);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(requestType), requestType, null);
}
if (result != null && !result.Result)
{
return new VoteEngineResult
{
ErrorMessage = "Voted succesfully but could not approve!"
};
}
return new VoteEngineResult
{
Result = true
};
}
public async Task<VoteEngineResult> DownVote(int requestId, RequestType requestType)
{
var voteSettings = await _voteSettings.GetSettingsAsync();
if (!voteSettings.Enabled)
{
return new VoteEngineResult { Result = true };
}
var user = await GetUser();
var currentVote = await GetVoteForUser(requestId, user.Id);
if (currentVote != null && currentVote.VoteType == VoteType.Downvote)
{
return new VoteEngineResult { ErrorMessage = "You have already voted!" };
}
await RemoveCurrentVote(currentVote);
await _movieRequestEngine.UnSubscribeRequest(requestId, requestType);
await _voteRepository.Add(new Votes
{
Date = DateTime.UtcNow,
RequestId = requestId,
RequestType = requestType,
UserId = user.Id,
VoteType = VoteType.Downvote
});
return new VoteEngineResult
{
Result = true
};
}
public async Task RemoveCurrentVote(Votes currentVote)
{
if (currentVote != null)
{
await _voteRepository.Delete(currentVote);
}
}
}
}

View file

@ -41,7 +41,7 @@ namespace Ombi.Core.Helpers
ShowInfo = await TvApi.ShowLookupByTheTvDbId(id); ShowInfo = await TvApi.ShowLookupByTheTvDbId(id);
Results = await MovieDbApi.SearchTv(ShowInfo.name); Results = await MovieDbApi.SearchTv(ShowInfo.name);
foreach (TvSearchResult result in Results) { foreach (TvSearchResult result in Results) {
if (result.Name == ShowInfo.name) if (result.Name.Equals(ShowInfo.name, StringComparison.InvariantCultureIgnoreCase))
{ {
var showIds = await MovieDbApi.GetTvExternals(result.Id); var showIds = await MovieDbApi.GetTvExternals(result.Id);
ShowInfo.externals.imdb = showIds.imdb_id; ShowInfo.externals.imdb = showIds.imdb_id;
@ -64,14 +64,16 @@ namespace Ombi.Core.Helpers
{ {
ChildRequest = new ChildRequests ChildRequest = new ChildRequests
{ {
Id = model.TvDbId, Id = model.TvDbId, // This is set to 0 after the request rules have run, the request rules needs it to identify the request
RequestType = RequestType.TvShow, RequestType = RequestType.TvShow,
RequestedDate = DateTime.UtcNow, RequestedDate = DateTime.UtcNow,
Approved = false, Approved = false,
RequestedUserId = userId, RequestedUserId = userId,
SeasonRequests = new List<SeasonRequests>(), SeasonRequests = new List<SeasonRequests>(),
Title = ShowInfo.name, Title = ShowInfo.name,
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.OrdinalIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard ReleaseYear = FirstAir,
RequestedByAlias = model.RequestedByAlias,
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.InvariantCultureIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard
}; };
return this; return this;

View file

@ -24,10 +24,20 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using Newtonsoft.Json;
namespace Ombi.Core.Models.Requests namespace Ombi.Core.Models.Requests
{ {
public class MovieRequestViewModel public class MovieRequestViewModel
{ {
public int TheMovieDbId { get; set; } public int TheMovieDbId { get; set; }
public string LanguageCode { get; set; } = "en";
/// <summary>
/// This is only set from a HTTP Header
/// </summary>
[JsonIgnore]
public string RequestedByAlias { get; set; }
} }
} }

View file

@ -3,5 +3,6 @@
public class MusicAlbumRequestViewModel public class MusicAlbumRequestViewModel
{ {
public string ForeignAlbumId { get; set; } public string ForeignAlbumId { get; set; }
public string RequestedByAlias { get; set; }
} }
} }

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace Ombi.Core.Models.Requests namespace Ombi.Core.Models.Requests
{ {
@ -9,6 +10,8 @@ namespace Ombi.Core.Models.Requests
public bool FirstSeason { get; set; } public bool FirstSeason { get; set; }
public int TvDbId { get; set; } public int TvDbId { get; set; }
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>(); public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
[JsonIgnore]
public string RequestedByAlias { get; set; }
} }
public class SeasonsViewModel public class SeasonsViewModel

View file

@ -16,8 +16,13 @@ namespace Ombi.Core.Models.Search
public string Cover { get; set; } public string Cover { get; set; }
public string Disk { get; set; } public string Disk { get; set; }
public decimal PercentOfTracks { get; set; } public decimal PercentOfTracks { get; set; }
public object[] Genres { get; set; }
public override RequestType Type => RequestType.Album; public override RequestType Type => RequestType.Album;
public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0;
public bool FullyAvailable => PercentOfTracks == 100; public bool FullyAvailable => PercentOfTracks == 100;
// Below is from the INFO call NEED A SEPERATE VM FOR THIS IN V4 TODO
// TODO ADD TRACK COUNT
} }
} }

View file

@ -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; }
}
}

View file

@ -0,0 +1,18 @@
using Ombi.Store.Entities;
namespace Ombi.Core.Models.UI
{
public class VoteViewModel
{
public int RequestId { get; set; }
public RequestType RequestType { get; set; }
public string Image { get; set; }
public string Background { get; set; }
public int Upvotes { get; set; }
public int Downvotes { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool AlreadyVoted { get; set; }
public VoteType MyVote { get; set; }
}
}

View file

@ -0,0 +1,10 @@
namespace Ombi.Core.Models
{
public class VoteEngineResult
{
public bool Result { get; set; }
public string Message { get; set; }
public bool IsError => !string.IsNullOrEmpty(ErrorMessage);
public string ErrorMessage { get; set; }
}
}

View file

@ -11,25 +11,27 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper" Version="6.1.1" /> <PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
<PackageReference Include="Hangfire" Version="1.6.19" /> <PackageReference Include="Hangfire" Version="1.6.22" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.2" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" /> <PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" /> <PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ombi.Api.CouchPotato\Ombi.Api.CouchPotato.csproj" />
<ProjectReference Include="..\Ombi.Api.DogNzb\Ombi.Api.DogNzb.csproj" /> <ProjectReference Include="..\Ombi.Api.DogNzb\Ombi.Api.DogNzb.csproj" />
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" /> <ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" /> <ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
<ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" />
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" /> <ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" /> <ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
<ProjectReference Include="..\Ombi.Api.Trakt\Ombi.Api.Trakt.csproj" /> <ProjectReference Include="..\Ombi.Api.Trakt\Ombi.Api.Trakt.csproj" />
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" /> <ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" /> <ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" /> <ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Schedule\Ombi.Schedule.csproj" />
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" /> <ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" /> <ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" /> <ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />

View file

@ -1,5 +1,7 @@
using System.Security.Principal; using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests; using Ombi.Core.Models.Requests;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers; using Ombi.Helpers;
@ -10,28 +12,31 @@ namespace Ombi.Core.Rule.Rules.Request
{ {
public class AutoApproveRule : BaseRequestRule, IRules<BaseRequest> public class AutoApproveRule : BaseRequestRule, IRules<BaseRequest>
{ {
public AutoApproveRule(IPrincipal principal) public AutoApproveRule(IPrincipal principal, OmbiUserManager um)
{ {
User = principal; User = principal;
_manager = um;
} }
private IPrincipal User { get; } 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; 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; 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; 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; 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
} }
} }
} }

View file

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

View file

@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
namespace Ombi.Core.Rule.Rules.Request
{
public class ExistingPlexRequestRule : BaseRequestRule, IRules<BaseRequest>
{
public ExistingPlexRequestRule(IPlexContentRepository rv)
{
_plexContent = rv;
}
private readonly IPlexContentRepository _plexContent;
/// <summary>
/// We check if the request exists, if it does then we don't want to re-request it.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns></returns>
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (obj.RequestType == RequestType.TvShow)
{
var tvRequest = (ChildRequests) obj;
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
if (anyTvDbMatches == null)
{
// So we do not have a TVDB Id, that really sucks.
// Let's try and match on the title and year of the show
var titleAndYearMatch = await tvContent.Include(x=> x.Episodes).FirstOrDefaultAsync(x =>
x.Title.Equals(tvRequest.Title, StringComparison.InvariantCultureIgnoreCase)
&& x.ReleaseYear == tvRequest.ReleaseYear.Year.ToString());
if (titleAndYearMatch != null)
{
// We have a match! Suprise Motherfucker
return CheckExistingContent(tvRequest, titleAndYearMatch);
}
// We do not have this
return Success();
}
// looks like we have a match on the TVDbID
return CheckExistingContent(tvRequest, anyTvDbMatches);
}
return Success();
}
private RuleResult CheckExistingContent(ChildRequests child, PlexServerContent content)
{
foreach (var season in child.SeasonRequests)
{
var currentSeasonRequest =
content.Episodes.Where(x => x.SeasonNumber == season.SeasonNumber).ToList();
if (!currentSeasonRequest.Any())
{
continue;
}
foreach (var e in season.Episodes)
{
var hasEpisode = currentSeasonRequest.Any(x => x.EpisodeNumber == e.EpisodeNumber);
if (hasEpisode)
{
return Fail($"We already have episodes requested from series {child.Title}");
}
}
}
return Success();
}
}
}

View file

@ -0,0 +1,57 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Rule.Rules.Request
{
public class ExistingTvRequestRule : BaseRequestRule, IRules<BaseRequest>
{
public ExistingTvRequestRule(ITvRequestRepository rv)
{
Tv = rv;
}
private ITvRequestRepository Tv { get; }
/// <summary>
/// We check if the request exists, if it does then we don't want to re-request it.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns></returns>
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (obj.RequestType == RequestType.TvShow)
{
var tv = (ChildRequests) obj;
var tvRequests = Tv.GetChild();
var currentRequest = await tvRequests.FirstOrDefaultAsync(x => x.ParentRequest.TvDbId == tv.Id); // the Id on the child is the tvdbid at this point
if (currentRequest == null)
{
return Success();
}
foreach (var season in tv.SeasonRequests)
{
var currentSeasonRequest =
currentRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber);
if (currentSeasonRequest == null)
{
continue;
}
foreach (var e in season.Episodes)
{
var hasEpisode = currentSeasonRequest.Episodes.Any(x => x.EpisodeNumber == e.EpisodeNumber);
if (hasEpisode)
{
return Fail($"We already have episodes requested from series {tv.Title}");
}
}
}
}
return Success();
}
}
}

View file

@ -7,12 +7,12 @@ namespace Ombi.Core.Rule.Rules.Request
{ {
public class SonarrCacheRequestRule : BaseRequestRule, IRules<BaseRequest> public class SonarrCacheRequestRule : BaseRequestRule, IRules<BaseRequest>
{ {
public SonarrCacheRequestRule(IOmbiContext ctx) public SonarrCacheRequestRule(IExternalContext ctx)
{ {
_ctx = ctx; _ctx = ctx;
} }
private readonly IOmbiContext _ctx; private readonly IExternalContext _ctx;
public Task<RuleResult> Execute(BaseRequest obj) public Task<RuleResult> Execute(BaseRequest obj)
{ {

View file

@ -0,0 +1,105 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Rule.Rules.Search
{
public static class AvailabilityRuleHelper
{
public static void CheckForUnairedEpisodes(SearchTvShowViewModel search)
{
if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available)))
{
search.FullyAvailable = true;
}
else
{
var airedButNotAvailable = search.SeasonRequests.Any(x =>
x.Episodes.Any(c => !c.Available && c.AirDate <= DateTime.Now.Date && c.AirDate != DateTime.MinValue));
if (!airedButNotAvailable)
{
var unairedEpisodes = search.SeasonRequests.Any(x =>
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date || c.AirDate != DateTime.MinValue));
if (unairedEpisodes)
{
search.FullyAvailable = true;
}
}
}
}
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<PlexEpisode> allEpisodes, EpisodeRequests episode,
SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb, ILogger log)
{
PlexEpisode epExists = null;
try
{
if (useImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
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);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId);
}
}
catch (Exception e)
{
log.LogError(e, "Exception thrown when attempting to check if something is available");
}
if (epExists != null)
{
episode.Available = true;
}
}
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<EmbyEpisode> allEpisodes, EpisodeRequests episode,
SeasonRequests season, EmbyContent item, bool useTheMovieDb, bool useTvDb)
{
EmbyEpisode epExists = null;
if (useImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
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);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId);
}
if (epExists != null)
{
episode.Available = true;
}
}
}
}

View file

@ -11,12 +11,12 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
public class CouchPotatoCacheRule : BaseSearchRule, IRules<SearchViewModel> public class CouchPotatoCacheRule : BaseSearchRule, IRules<SearchViewModel>
{ {
public CouchPotatoCacheRule(IRepository<CouchPotatoCache> ctx) public CouchPotatoCacheRule(IExternalRepository<CouchPotatoCache> ctx)
{ {
_ctx = ctx; _ctx = ctx;
} }
private readonly IRepository<CouchPotatoCache> _ctx; private readonly IExternalRepository<CouchPotatoCache> _ctx;
public async Task<RuleResult> Execute(SearchViewModel obj) public async Task<RuleResult> Execute(SearchViewModel obj)
{ {

View file

@ -1,10 +1,10 @@
using System; using System.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Store.Entities; using Ombi.Store.Entities;
using Ombi.Store.Repository; using Ombi.Store.Repository;
@ -13,25 +13,39 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
public class EmbyAvailabilityRule : BaseSearchRule, IRules<SearchViewModel> public class EmbyAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{ {
public EmbyAvailabilityRule(IEmbyContentRepository repo) public EmbyAvailabilityRule(IEmbyContentRepository repo, ISettingsService<EmbySettings> s)
{ {
EmbyContentRepository = repo; EmbyContentRepository = repo;
EmbySettings = s;
} }
private IEmbyContentRepository EmbyContentRepository { get; } private IEmbyContentRepository EmbyContentRepository { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
public async Task<RuleResult> Execute(SearchViewModel obj) public async Task<RuleResult> Execute(SearchViewModel obj)
{ {
EmbyContent item = null; EmbyContent item = null;
var useImdb = false;
var useTheMovieDb = false;
var useTvDb = false;
if (obj.ImdbId.HasValue()) if (obj.ImdbId.HasValue())
{ {
item = await EmbyContentRepository.GetByImdbId(obj.ImdbId); item = await EmbyContentRepository.GetByImdbId(obj.ImdbId);
if (item != null)
{
useImdb = true;
}
} }
if (item == null) if (item == null)
{ {
if (obj.TheMovieDbId.HasValue()) if (obj.TheMovieDbId.HasValue())
{ {
item = await EmbyContentRepository.GetByTheMovieDbId(obj.TheMovieDbId); item = await EmbyContentRepository.GetByTheMovieDbId(obj.TheMovieDbId);
if (item != null)
{
useTheMovieDb = true;
}
} }
if (item == null) if (item == null)
@ -39,6 +53,10 @@ namespace Ombi.Core.Rule.Rules.Search
if (obj.TheTvDbId.HasValue()) if (obj.TheTvDbId.HasValue())
{ {
item = await EmbyContentRepository.GetByTvDbId(obj.TheTvDbId); item = await EmbyContentRepository.GetByTvDbId(obj.TheTvDbId);
if (item != null)
{
useTvDb = true;
}
} }
} }
} }
@ -46,7 +64,16 @@ namespace Ombi.Core.Rule.Rules.Search
if (item != null) if (item != null)
{ {
obj.Available = true; obj.Available = true;
obj.EmbyUrl = item.Url; var s = await EmbySettings.GetSettingsAsync();
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
if ((server?.ServerHostname ?? string.Empty).HasValue())
{
obj.EmbyUrl = $"{server.ServerHostname}#!/itemdetails.html?id={item.EmbyId}";
}
else
{
obj.EmbyUrl = $"https://app.emby.media/#!/itemdetails.html?id={item.EmbyId}";
}
if (obj.Type == RequestType.TvShow) if (obj.Type == RequestType.TvShow)
{ {
@ -59,29 +86,12 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
foreach (var episode in season.Episodes) foreach (var episode in season.Episodes)
{ {
EmbyEpisode epExists = null; await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb);
if (item.HasImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.ImdbId == item.ImdbId);
} if (item.HasTvDb && epExists == null)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.Series.TvDbId == item.TvDbId);
} if (item.HasTheMovieDb && epExists == null)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.TheMovieDbId == item.TheMovieDbId);
}
if (epExists != null)
{
episode.Available = true;
}
} }
} }
} }
AvailabilityRuleHelper.CheckForUnairedEpisodes(search);
} }
} }
return Success(); return Success();

View file

@ -1,4 +1,5 @@
using System.Linq; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
@ -87,11 +88,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; 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; request.PartlyAvailable = true;
} }

View file

@ -10,12 +10,12 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
public class LidarrAlbumCacheRule : SpecificRule, ISpecificRule<object> public class LidarrAlbumCacheRule : SpecificRule, ISpecificRule<object>
{ {
public LidarrAlbumCacheRule(IRepository<LidarrAlbumCache> db) public LidarrAlbumCacheRule(IExternalRepository<LidarrAlbumCache> db)
{ {
_db = db; _db = db;
} }
private readonly IRepository<LidarrAlbumCache> _db; private readonly IExternalRepository<LidarrAlbumCache> _db;
public Task<RuleResult> Execute(object objec) public Task<RuleResult> Execute(object objec)
{ {

View file

@ -10,12 +10,12 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
public class LidarrArtistCacheRule : SpecificRule, ISpecificRule<object> public class LidarrArtistCacheRule : SpecificRule, ISpecificRule<object>
{ {
public LidarrArtistCacheRule(IRepository<LidarrArtistCache> db) public LidarrArtistCacheRule(IExternalRepository<LidarrArtistCache> db)
{ {
_db = db; _db = db;
} }
private readonly IRepository<LidarrArtistCache> _db; private readonly IExternalRepository<LidarrArtistCache> _db;
public Task<RuleResult> Execute(object objec) public Task<RuleResult> Execute(object objec)
{ {

View file

@ -1,6 +1,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers; using Ombi.Helpers;
@ -11,12 +11,14 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
public class PlexAvailabilityRule : BaseSearchRule, IRules<SearchViewModel> public class PlexAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{ {
public PlexAvailabilityRule(IPlexContentRepository repo) public PlexAvailabilityRule(IPlexContentRepository repo, ILogger<PlexAvailabilityRule> log)
{ {
PlexContentRepository = repo; PlexContentRepository = repo;
Log = log;
} }
private IPlexContentRepository PlexContentRepository { get; } private IPlexContentRepository PlexContentRepository { get; }
private ILogger Log { get; }
public async Task<RuleResult> Execute(SearchViewModel obj) public async Task<RuleResult> Execute(SearchViewModel obj)
{ {
@ -73,41 +75,17 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
foreach (var episode in season.Episodes) foreach (var episode in season.Episodes)
{ {
PlexEpisode epExists = null; await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb, Log);
if (useImdb)
{
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 (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId.ToString());
}
if (epExists != null)
{
episode.Available = true;
}
} }
} }
if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available))) AvailabilityRuleHelper.CheckForUnairedEpisodes(search);
{
search.FullyAvailable = true;
}
} }
} }
} }
return Success(); return Success();
} }
} }
} }

View file

@ -9,12 +9,12 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
public class RadarrCacheRule : BaseSearchRule, IRules<SearchViewModel> public class RadarrCacheRule : BaseSearchRule, IRules<SearchViewModel>
{ {
public RadarrCacheRule(IRepository<RadarrCache> db) public RadarrCacheRule(IExternalRepository<RadarrCache> db)
{ {
_db = db; _db = db;
} }
private readonly IRepository<RadarrCache> _db; private readonly IExternalRepository<RadarrCache> _db;
public Task<RuleResult> Execute(SearchViewModel obj) public Task<RuleResult> Execute(SearchViewModel obj)
{ {

View file

@ -34,12 +34,12 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
public class SonarrCacheSearchRule : BaseSearchRule, IRules<SearchViewModel> public class SonarrCacheSearchRule : BaseSearchRule, IRules<SearchViewModel>
{ {
public SonarrCacheSearchRule(IOmbiContext ctx) public SonarrCacheSearchRule(IExternalContext ctx)
{ {
_ctx = ctx; _ctx = ctx;
} }
private readonly IOmbiContext _ctx; private readonly IExternalContext _ctx;
public Task<RuleResult> Execute(SearchViewModel obj) public Task<RuleResult> Execute(SearchViewModel obj)
{ {

View file

@ -10,12 +10,12 @@ namespace Ombi.Core.Rule.Rules
{ {
public class SonarrCacheRule public class SonarrCacheRule
{ {
public SonarrCacheRule(IOmbiContext ctx) public SonarrCacheRule(IExternalContext ctx)
{ {
_ctx = ctx; _ctx = ctx;
} }
private readonly IOmbiContext _ctx; private readonly IExternalContext _ctx;
public async Task<RuleResult> Execute(BaseRequest obj) public async Task<RuleResult> Execute(BaseRequest obj)
{ {

View file

@ -1,4 +1,5 @@
using System.Linq; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -19,7 +20,7 @@ namespace Ombi.Core.Senders
{ {
public MovieSender(ISettingsService<RadarrSettings> radarrSettings, IRadarrApi api, ILogger<MovieSender> log, public MovieSender(ISettingsService<RadarrSettings> radarrSettings, IRadarrApi api, ILogger<MovieSender> log,
ISettingsService<DogNzbSettings> dogSettings, IDogNzbApi dogApi, ISettingsService<CouchPotatoSettings> cpSettings, ISettingsService<DogNzbSettings> dogSettings, IDogNzbApi dogApi, ISettingsService<CouchPotatoSettings> cpSettings,
ICouchPotatoApi cpApi, IRepository<UserQualityProfiles> userProfiles) ICouchPotatoApi cpApi, IRepository<UserQualityProfiles> userProfiles, IRepository<RequestQueue> requestQueue, INotificationHelper notify)
{ {
RadarrSettings = radarrSettings; RadarrSettings = radarrSettings;
RadarrApi = api; RadarrApi = api;
@ -29,6 +30,8 @@ namespace Ombi.Core.Senders
CouchPotatoSettings = cpSettings; CouchPotatoSettings = cpSettings;
CouchPotatoApi = cpApi; CouchPotatoApi = cpApi;
_userProfiles = userProfiles; _userProfiles = userProfiles;
_requestQueuRepository = requestQueue;
_notificationHelper = notify;
} }
private ISettingsService<RadarrSettings> RadarrSettings { get; } private ISettingsService<RadarrSettings> RadarrSettings { get; }
@ -39,39 +42,63 @@ namespace Ombi.Core.Senders
private ISettingsService<CouchPotatoSettings> CouchPotatoSettings { get; } private ISettingsService<CouchPotatoSettings> CouchPotatoSettings { get; }
private ICouchPotatoApi CouchPotatoApi { get; } private ICouchPotatoApi CouchPotatoApi { get; }
private readonly IRepository<UserQualityProfiles> _userProfiles; private readonly IRepository<UserQualityProfiles> _userProfiles;
private readonly IRepository<RequestQueue> _requestQueuRepository;
private readonly INotificationHelper _notificationHelper;
public async Task<SenderResult> Send(MovieRequests model) public async Task<SenderResult> Send(MovieRequests model)
{ {
var cpSettings = await CouchPotatoSettings.GetSettingsAsync(); try
//var watcherSettings = await WatcherSettings.GetSettingsAsync();
var radarrSettings = await RadarrSettings.GetSettingsAsync();
if (radarrSettings.Enabled)
{ {
return await SendToRadarr(model, radarrSettings); var cpSettings = await CouchPotatoSettings.GetSettingsAsync();
} //var watcherSettings = await WatcherSettings.GetSettingsAsync();
var radarrSettings = await RadarrSettings.GetSettingsAsync();
var dogSettings = await DogNzbSettings.GetSettingsAsync(); if (radarrSettings.Enabled)
if (dogSettings.Enabled)
{
await SendToDogNzb(model, dogSettings);
return new SenderResult
{ {
Success = true, return await SendToRadarr(model, radarrSettings);
Sent = true, }
};
}
if (cpSettings.Enabled) var dogSettings = await DogNzbSettings.GetSettingsAsync();
if (dogSettings.Enabled)
{
await SendToDogNzb(model, dogSettings);
return new SenderResult
{
Success = true,
Sent = true,
};
}
if (cpSettings.Enabled)
{
return await SendToCp(model, cpSettings, cpSettings.DefaultProfileId);
}
}
catch (Exception e)
{ {
return await SendToCp(model, cpSettings, cpSettings.DefaultProfileId); 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);
if (existingQueue != null)
{
existingQueue.RetryCount++;
existingQueue.Error = e.Message;
await _requestQueuRepository.SaveChangesAsync();
}
else
{
await _requestQueuRepository.Add(new RequestQueue
{
Dts = DateTime.UtcNow,
Error = e.Message,
RequestId = model.Id,
Type = RequestType.Movie,
RetryCount = 0
});
_notificationHelper.Notify(model, NotificationType.ItemAddedToFaultQueue);
}
} }
//if (watcherSettings.Enabled)
//{
// return SendToWatcher(model, watcherSettings);
//}
return new SenderResult return new SenderResult
{ {
Success = true, Success = true,
@ -101,13 +128,17 @@ namespace Ombi.Core.Senders
var profiles = await _userProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId); var profiles = await _userProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId);
if (profiles != null) if (profiles != null)
{ {
if (profiles.SonarrRootPathAnime > 0) if (profiles.RadarrRootPath > 0)
{ {
rootFolderPath = await RadarrRootPath(profiles.SonarrRootPathAnime, settings); var tempPath = await RadarrRootPath(profiles.RadarrRootPath, settings);
if (tempPath.HasValue())
{
rootFolderPath = tempPath;
}
} }
if (profiles.SonarrQualityProfileAnime > 0) if (profiles.RadarrQualityProfile > 0)
{ {
qualityToUse = profiles.SonarrQualityProfileAnime; qualityToUse = profiles.RadarrQualityProfile;
} }
} }
@ -150,7 +181,7 @@ namespace Ombi.Core.Senders
// Search for it // Search for it
if (!settings.AddOnly) if (!settings.AddOnly)
{ {
await RadarrApi.MovieSearch(new[] {existingMovie.id}, settings.ApiKey, settings.FullUri); await RadarrApi.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri);
} }
return new SenderResult { Success = true, Sent = true }; return new SenderResult { Success = true, Sent = true };
@ -163,7 +194,7 @@ namespace Ombi.Core.Senders
{ {
var paths = await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); var paths = await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId); var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
return selectedPath.path; return selectedPath?.path ?? String.Empty;
} }
} }
} }

View file

@ -1,37 +1,76 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EnsureThat;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr; using Ombi.Api.Lidarr;
using Ombi.Api.Lidarr.Models; using Ombi.Api.Lidarr.Models;
using Ombi.Api.Radarr;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External; using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using Serilog; using Ombi.Store.Repository;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Ombi.Core.Senders namespace Ombi.Core.Senders
{ {
public class MusicSender : IMusicSender public class MusicSender : IMusicSender
{ {
public MusicSender(ISettingsService<LidarrSettings> lidarr, ILidarrApi lidarrApi) public MusicSender(ISettingsService<LidarrSettings> lidarr, ILidarrApi lidarrApi, ILogger<MusicSender> log,
IRepository<RequestQueue> requestQueue, INotificationHelper notify)
{ {
_lidarrSettings = lidarr; _lidarrSettings = lidarr;
_lidarrApi = lidarrApi; _lidarrApi = lidarrApi;
_log = log;
_requestQueueRepository = requestQueue;
_notificationHelper = notify;
} }
private readonly ISettingsService<LidarrSettings> _lidarrSettings; private readonly ISettingsService<LidarrSettings> _lidarrSettings;
private readonly ILidarrApi _lidarrApi; private readonly ILidarrApi _lidarrApi;
private readonly ILogger _log;
private readonly IRepository<RequestQueue> _requestQueueRepository;
private readonly INotificationHelper _notificationHelper;
public async Task<SenderResult> Send(AlbumRequest model) public async Task<SenderResult> Send(AlbumRequest model)
{ {
var settings = await _lidarrSettings.GetSettingsAsync(); try
if (settings.Enabled)
{ {
return await SendToLidarr(model, settings); var settings = await _lidarrSettings.GetSettingsAsync();
if (settings.Enabled)
{
return await SendToLidarr(model, settings);
}
return new SenderResult { Success = false, Sent = false, Message = "Lidarr is not enabled" };
}
catch (Exception e)
{
_log.LogError(e, "Exception thrown when sending a music to DVR app, added to the request queue");
var existingQueue = await _requestQueueRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);
if (existingQueue != null)
{
existingQueue.RetryCount++;
existingQueue.Error = e.Message;
await _requestQueueRepository.SaveChangesAsync();
}
else
{
await _requestQueueRepository.Add(new RequestQueue
{
Dts = DateTime.UtcNow,
Error = e.Message,
RequestId = model.Id,
Type = RequestType.Album,
RetryCount = 0
});
_notificationHelper.Notify(model, NotificationType.ItemAddedToFaultQueue);
}
} }
return new SenderResult { Success = false, Sent = false, Message = "Lidarr is not enabled" };
return new SenderResult { Success = false, Sent = false, Message = "Something went wrong!" };
} }
private async Task<SenderResult> SendToLidarr(AlbumRequest model, LidarrSettings settings) private async Task<SenderResult> SendToLidarr(AlbumRequest model, LidarrSettings settings)
@ -49,6 +88,11 @@ namespace Ombi.Core.Senders
if (artist == null || artist.id <= 0) if (artist == null || artist.id <= 0)
{ {
EnsureArg.IsNotNullOrEmpty(model.ForeignArtistId, nameof(model.ForeignArtistId));
EnsureArg.IsNotNullOrEmpty(model.ForeignAlbumId, nameof(model.ForeignAlbumId));
EnsureArg.IsNotNullOrEmpty(model.ArtistName, nameof(model.ArtistName));
EnsureArg.IsNotNullOrEmpty(rootFolderPath, nameof(rootFolderPath));
// Create artist // Create artist
var newArtist = new ArtistAdd var newArtist = new ArtistAdd
{ {

View file

@ -16,16 +16,18 @@ using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities; using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using Remotion.Linq.Parsing.Structure.IntermediateModel;
namespace Ombi.Core.Senders namespace Ombi.Core.Senders
{ {
public class TvSender : ITvSender public class TvSender : ITvSender
{ {
public TvSender(ISonarrApi sonarrApi, ILogger<TvSender> log, ISettingsService<SonarrSettings> sonarrSettings, public TvSender(ISonarrApi sonarrApi, ISonarrV3Api sonarrV3Api, ILogger<TvSender> log, ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<DogNzbSettings> dog, IDogNzbApi dogApi, ISettingsService<SickRageSettings> srSettings, ISettingsService<DogNzbSettings> dog, IDogNzbApi dogApi, ISettingsService<SickRageSettings> srSettings,
ISickRageApi srApi, IRepository<UserQualityProfiles> userProfiles) ISickRageApi srApi, IRepository<UserQualityProfiles> userProfiles, IRepository<RequestQueue> requestQueue, INotificationHelper notify)
{ {
SonarrApi = sonarrApi; SonarrApi = sonarrApi;
SonarrV3Api = sonarrV3Api;
Logger = log; Logger = log;
SonarrSettings = sonarrSettings; SonarrSettings = sonarrSettings;
DogNzbSettings = dog; DogNzbSettings = dog;
@ -33,9 +35,12 @@ namespace Ombi.Core.Senders
SickRageSettings = srSettings; SickRageSettings = srSettings;
SickRageApi = srApi; SickRageApi = srApi;
UserQualityProfiles = userProfiles; UserQualityProfiles = userProfiles;
_requestQueueRepository = requestQueue;
_notificationHelper = notify;
} }
private ISonarrApi SonarrApi { get; } private ISonarrApi SonarrApi { get; }
private ISonarrV3Api SonarrV3Api { get; }
private IDogNzbApi DogNzbApi { get; } private IDogNzbApi DogNzbApi { get; }
private ISickRageApi SickRageApi { get; } private ISickRageApi SickRageApi { get; }
private ILogger<TvSender> Logger { get; } private ILogger<TvSender> Logger { get; }
@ -43,59 +48,94 @@ namespace Ombi.Core.Senders
private ISettingsService<DogNzbSettings> DogNzbSettings { get; } private ISettingsService<DogNzbSettings> DogNzbSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; } private ISettingsService<SickRageSettings> SickRageSettings { get; }
private IRepository<UserQualityProfiles> UserQualityProfiles { get; } private IRepository<UserQualityProfiles> UserQualityProfiles { get; }
private readonly IRepository<RequestQueue> _requestQueueRepository;
private readonly INotificationHelper _notificationHelper;
public async Task<SenderResult> Send(ChildRequests model) public async Task<SenderResult> Send(ChildRequests model)
{ {
var sonarr = await SonarrSettings.GetSettingsAsync(); try
if (sonarr.Enabled)
{ {
var result = await SendToSonarr(model); var sonarr = await SonarrSettings.GetSettingsAsync();
if (result != null) if (sonarr.Enabled)
{ {
var result = await SendToSonarr(model, sonarr);
if (result != null)
{
return new SenderResult
{
Sent = true,
Success = true
};
}
}
var dog = await DogNzbSettings.GetSettingsAsync();
if (dog.Enabled)
{
var result = await SendToDogNzb(model, dog);
if (!result.Failure)
{
return new SenderResult
{
Sent = true,
Success = true
};
}
return new SenderResult return new SenderResult
{ {
Sent = true, Message = result.ErrorMessage
Success = true
}; };
} }
} var sr = await SickRageSettings.GetSettingsAsync();
var dog = await DogNzbSettings.GetSettingsAsync(); if (sr.Enabled)
if (dog.Enabled)
{
var result = await SendToDogNzb(model, dog);
if (!result.Failure)
{ {
var result = await SendToSickRage(model, sr);
if (result)
{
return new SenderResult
{
Sent = true,
Success = true
};
}
return new SenderResult return new SenderResult
{ {
Sent = true, Message = "Could not send to SickRage!"
Success = true
}; };
} }
return new SenderResult return new SenderResult
{ {
Message = result.ErrorMessage Success = true
}; };
} }
var sr = await SickRageSettings.GetSettingsAsync(); catch (Exception e)
if (sr.Enabled)
{ {
var result = await SendToSickRage(model, sr); Logger.LogError(e, "Exception thrown when sending a movie to DVR app, added to the request queue");
if (result) // Check if already in request queue
var existingQueue = await _requestQueueRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);
if (existingQueue != null)
{ {
return new SenderResult existingQueue.RetryCount++;
{ existingQueue.Error = e.Message;
Sent = true, await _requestQueueRepository.SaveChangesAsync();
Success = true
};
} }
return new SenderResult else
{ {
Message = "Could not send to SickRage!" await _requestQueueRepository.Add(new RequestQueue
}; {
Dts = DateTime.UtcNow,
Error = e.Message,
RequestId = model.Id,
Type = RequestType.TvShow,
RetryCount = 0
});
_notificationHelper.Notify(model, NotificationType.ItemAddedToFaultQueue);
}
} }
return new SenderResult return new SenderResult
{ {
Success = true Success = false,
Message = "Something went wrong!"
}; };
} }
@ -111,13 +151,8 @@ namespace Ombi.Core.Senders
/// <param name="s"></param> /// <param name="s"></param>
/// <param name="model"></param> /// <param name="model"></param>
/// <returns></returns> /// <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)) if (string.IsNullOrEmpty(s.ApiKey))
{ {
return null; return null;
@ -143,7 +178,7 @@ namespace Ombi.Core.Senders
} }
if (profiles.SonarrQualityProfileAnime > 0) if (profiles.SonarrQualityProfileAnime > 0)
{ {
qualityToUse = profiles.SonarrQualityProfileAnime; qualityToUse = profiles.SonarrQualityProfileAnime;
} }
} }
seriesType = "anime"; seriesType = "anime";
@ -163,7 +198,7 @@ namespace Ombi.Core.Senders
} }
if (profiles.SonarrQualityProfile > 0) if (profiles.SonarrQualityProfile > 0)
{ {
qualityToUse = profiles.SonarrQualityProfile; qualityToUse = profiles.SonarrQualityProfile;
} }
} }
seriesType = "standard"; seriesType = "standard";
@ -175,6 +210,10 @@ namespace Ombi.Core.Senders
qualityToUse = model.ParentRequest.QualityOverride.Value; qualityToUse = model.ParentRequest.QualityOverride.Value;
} }
// Are we using v3 sonarr?
var sonarrV3 = s.V3;
var languageProfileId = s.LanguageProfile;
try try
{ {
// Does the series actually exist? // Does the series actually exist?
@ -204,6 +243,11 @@ namespace Ombi.Core.Senders
} }
}; };
if (sonarrV3)
{
newSeries.languageProfileId = languageProfileId;
}
// Montitor the correct seasons, // Montitor the correct seasons,
// If we have that season in the model then it's monitored! // If we have that season in the model then it's monitored!
var seasonsToAdd = GetSeasonsToCreate(model); var seasonsToAdd = GetSeasonsToCreate(model);
@ -271,10 +315,19 @@ namespace Ombi.Core.Senders
foreach (var season in model.SeasonRequests) foreach (var season in model.SeasonRequests)
{ {
var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber); var sonarrEpisodeList = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber).ToList();
var sonarrEpCount = sonarrSeason.Count(); var sonarrEpCount = sonarrEpisodeList.Count;
var ourRequestCount = season.Episodes.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 = var existingSeason =
result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber); result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
if (existingSeason == null) if (existingSeason == null)
@ -284,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. // We have the same amount of requests as all of the episodes in the season.
@ -300,16 +353,16 @@ namespace Ombi.Core.Senders
if (!existingSeason.monitored) if (!existingSeason.monitored)
{ {
// We need to monitor it, problem being is all episodes will now be monitored // We need to monitor it, problem being is all episodes will now be monitored
// So we need to monior the series but unmonitor every episode // So we need to monitor the series but unmonitor every episode
// Except the episodes that are already monitored before we update the series (we do not want to unmonitor episodes that are monitored beforehand) // Except the episodes that are already monitored before we update the series (we do not want to unmonitored episodes that are monitored beforehand)
existingSeason.monitored = true; existingSeason.monitored = true;
var sea = result.seasons.FirstOrDefault(x => x.seasonNumber == existingSeason.seasonNumber); var sea = result.seasons.FirstOrDefault(x => x.seasonNumber == existingSeason.seasonNumber);
sea.monitored = true; sea.monitored = true;
//var previouslyMonitoredEpisodes = sonarrEpList.Where(x => //var previouslyMonitoredEpisodes = sonarrEpList.Where(x =>
// x.seasonNumber == existingSeason.seasonNumber && x.monitored).Select(x => x.episodeNumber).ToList(); // We probably don't actually care about this // x.seasonNumber == existingSeason.seasonNumber && x.monitored).Select(x => x.episodeNumber).ToList(); // We probably don't actually care about this
result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri);
var epToUnmonitor = new List<Episode>(); var epToUnmonitored = new List<Episode>();
var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the orignal member var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the original member
foreach (var ep in newEpList.Where(x => x.seasonNumber == existingSeason.seasonNumber).ToList()) foreach (var ep in newEpList.Where(x => x.seasonNumber == existingSeason.seasonNumber).ToList())
{ {
//if (previouslyMonitoredEpisodes.Contains(ep.episodeNumber)) //if (previouslyMonitoredEpisodes.Contains(ep.episodeNumber))
@ -318,10 +371,10 @@ namespace Ombi.Core.Senders
// continue; // continue;
//} //}
ep.monitored = false; ep.monitored = false;
epToUnmonitor.Add(ep); epToUnmonitored.Add(ep);
} }
foreach (var epToUpdate in epToUnmonitor) foreach (var epToUpdate in epToUnmonitored)
{ {
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri); await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri);
} }
@ -355,7 +408,7 @@ namespace Ombi.Core.Senders
var sea = new Season var sea = new Season
{ {
seasonNumber = i, seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0) monitored = false
}; };
seasonsToUpdate.Add(sea); seasonsToUpdate.Add(sea);
} }

View file

@ -32,6 +32,7 @@ using Ombi.Api.CouchPotato;
using Ombi.Api.DogNzb; using Ombi.Api.DogNzb;
using Ombi.Api.FanartTv; using Ombi.Api.FanartTv;
using Ombi.Api.Github; using Ombi.Api.Github;
using Ombi.Api.Gotify;
using Ombi.Api.Lidarr; using Ombi.Api.Lidarr;
using Ombi.Api.Mattermost; using Ombi.Api.Mattermost;
using Ombi.Api.Notifications; using Ombi.Api.Notifications;
@ -53,6 +54,7 @@ using Ombi.Updater;
using PlexContentCacher = Ombi.Schedule.Jobs.Plex; using PlexContentCacher = Ombi.Schedule.Jobs.Plex;
using Ombi.Api.Telegram; using Ombi.Api.Telegram;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Core.Engine.Demo;
using Ombi.Core.Processor; using Ombi.Core.Processor;
using Ombi.Schedule.Jobs.Lidarr; using Ombi.Schedule.Jobs.Lidarr;
using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Schedule.Jobs.Plex.Interfaces;
@ -92,6 +94,9 @@ namespace Ombi.DependencyInjection
services.AddTransient<IMusicSender, MusicSender>(); services.AddTransient<IMusicSender, MusicSender>();
services.AddTransient<IMassEmailSender, MassEmailSender>(); services.AddTransient<IMassEmailSender, MassEmailSender>();
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>(); services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
services.AddTransient<IVoteEngine, VoteEngine>();
services.AddTransient<IDemoMovieSearchEngine, DemoMovieSearchEngine>();
services.AddTransient<IDemoTvSearchEngine, DemoTvSearchEngine>();
} }
public static void RegisterHttp(this IServiceCollection services) public static void RegisterHttp(this IServiceCollection services)
{ {
@ -107,6 +112,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IPlexApi, PlexApi>(); services.AddTransient<IPlexApi, PlexApi>();
services.AddTransient<IEmbyApi, EmbyApi>(); services.AddTransient<IEmbyApi, EmbyApi>();
services.AddTransient<ISonarrApi, SonarrApi>(); services.AddTransient<ISonarrApi, SonarrApi>();
services.AddTransient<ISonarrV3Api, SonarrV3Api>();
services.AddTransient<ISlackApi, SlackApi>(); services.AddTransient<ISlackApi, SlackApi>();
services.AddTransient<ITvMazeApi, TvMazeApi>(); services.AddTransient<ITvMazeApi, TvMazeApi>();
services.AddTransient<ITraktApi, TraktApi>(); services.AddTransient<ITraktApi, TraktApi>();
@ -116,6 +122,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IOmbiService, OmbiService>(); services.AddTransient<IOmbiService, OmbiService>();
services.AddTransient<IFanartTvApi, FanartTvApi>(); services.AddTransient<IFanartTvApi, FanartTvApi>();
services.AddTransient<IPushoverApi, PushoverApi>(); services.AddTransient<IPushoverApi, PushoverApi>();
services.AddTransient<IGotifyApi, GotifyApi>();
services.AddTransient<IMattermostApi, MattermostApi>(); services.AddTransient<IMattermostApi, MattermostApi>();
services.AddTransient<ICouchPotatoApi, CouchPotatoApi>(); services.AddTransient<ICouchPotatoApi, CouchPotatoApi>();
services.AddTransient<IDogNzbApi, DogNzbApi>(); services.AddTransient<IDogNzbApi, DogNzbApi>();
@ -128,23 +135,28 @@ namespace Ombi.DependencyInjection
} }
public static void RegisterStore(this IServiceCollection services) { public static void RegisterStore(this IServiceCollection services) {
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>(); 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<IOmbiContext, OmbiContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddTransient<ISettingsRepository, SettingsJsonRepository>(); services.AddScoped<ISettingsContext, SettingsContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddTransient<ISettingsResolver, SettingsResolver>(); services.AddScoped<IExternalContext, ExternalContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddTransient<IPlexContentRepository, PlexServerContentRepository>(); services.AddScoped<ISettingsRepository, SettingsJsonRepository>();
services.AddTransient<IEmbyContentRepository, EmbyContentRepository>(); services.AddScoped<ISettingsResolver, SettingsResolver>();
services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>(); services.AddScoped<IPlexContentRepository, PlexServerContentRepository>();
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddTransient<ITvRequestRepository, TvRequestRepository>(); services.AddScoped<ITvRequestRepository, TvRequestRepository>();
services.AddTransient<IMovieRequestRepository, MovieRequestRepository>(); services.AddScoped<IMovieRequestRepository, MovieRequestRepository>();
services.AddTransient<IMusicRequestRepository, MusicRequestRepository>(); services.AddScoped<IMusicRequestRepository, MusicRequestRepository>();
services.AddTransient<IAuditRepository, AuditRepository>(); services.AddScoped<IAuditRepository, AuditRepository>();
services.AddTransient<IApplicationConfigRepository, ApplicationConfigRepository>(); services.AddScoped<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddTransient<ITokenRepository, TokenRepository>(); services.AddScoped<ITokenRepository, TokenRepository>();
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>)); services.AddScoped(typeof(ISettingsService<>), typeof(SettingsService<>));
services.AddTransient(typeof(IRepository<>), typeof(Repository<>)); services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
} }
public static void RegisterServices(this IServiceCollection services) public static void RegisterServices(this IServiceCollection services)
{ {
@ -152,7 +164,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<INotificationService, NotificationService>(); services.AddTransient<INotificationService, NotificationService>();
services.AddTransient<IEmailProvider, GenericEmailProvider>(); services.AddTransient<IEmailProvider, GenericEmailProvider>();
services.AddTransient<INotificationHelper, NotificationHelper>(); services.AddTransient<INotificationHelper, NotificationHelper>();
services.AddTransient<ICacheService, CacheService>(); services.AddSingleton<ICacheService, CacheService>();
services.AddTransient<IDiscordNotification, DiscordNotification>(); services.AddTransient<IDiscordNotification, DiscordNotification>();
services.AddTransient<IEmailNotification, EmailNotification>(); services.AddTransient<IEmailNotification, EmailNotification>();
@ -161,6 +173,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ISlackNotification, SlackNotification>(); services.AddTransient<ISlackNotification, SlackNotification>();
services.AddTransient<IMattermostNotification, MattermostNotification>(); services.AddTransient<IMattermostNotification, MattermostNotification>();
services.AddTransient<IPushoverNotification, PushoverNotification>(); services.AddTransient<IPushoverNotification, PushoverNotification>();
services.AddTransient<IGotifyNotification, GotifyNotification>();
services.AddTransient<ITelegramNotification, TelegramNotification>(); services.AddTransient<ITelegramNotification, TelegramNotification>();
services.AddTransient<IMobileNotification, MobileNotification>(); services.AddTransient<IMobileNotification, MobileNotification>();
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>(); services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();
@ -194,6 +207,8 @@ namespace Ombi.DependencyInjection
services.AddTransient<ILidarrArtistSync, LidarrArtistSync>(); services.AddTransient<ILidarrArtistSync, LidarrArtistSync>();
services.AddTransient<ILidarrAvailabilityChecker, LidarrAvailabilityChecker>(); services.AddTransient<ILidarrAvailabilityChecker, LidarrAvailabilityChecker>();
services.AddTransient<IIssuesPurge, IssuesPurge>(); services.AddTransient<IIssuesPurge, IssuesPurge>();
services.AddTransient<IResendFailedRequests, ResendFailedRequests>();
services.AddTransient<IMediaDatabaseRefresh, MediaDatabaseRefresh>();
} }
} }
} }

View file

@ -9,9 +9,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,52 @@
using System;
using NUnit.Framework;
using System.Collections.Generic;
namespace Ombi.Helpers.Tests
{
[TestFixture]
public class PlexHelperTests
{
[TestCaseSource(nameof(ProviderIdGuidData))]
public string GetProviderIdFromPlexGuidTests(string guidInput, ProviderIdType type)
{
var result = PlexHelper.GetProviderIdFromPlexGuid(guidInput);
switch (type)
{
case ProviderIdType.Imdb:
Assert.That(result.ImdbId, Is.Not.Null);
return result.ImdbId;
case ProviderIdType.TvDb:
Assert.That(result.TheTvDb, Is.Not.Null);
return result.TheTvDb;
case ProviderIdType.MovieDb:
Assert.That(result.TheMovieDb, Is.Not.Null);
return result.TheMovieDb;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
public static IEnumerable<TestCaseData> ProviderIdGuidData
{
get
{
yield return new TestCaseData("com.plexapp.agents.thetvdb://269586/2/8?lang=en", ProviderIdType.TvDb).Returns("269586").SetName("Regular TvDb Id");
yield return new TestCaseData("com.plexapp.agents.themoviedb://390043?lang=en", ProviderIdType.MovieDb).Returns("390043").SetName("Regular MovieDb Id");
yield return new TestCaseData("com.plexapp.agents.imdb://tt2543164?lang=en", ProviderIdType.Imdb).Returns("tt2543164").SetName("Regular Imdb Id");
yield return new TestCaseData("com.plexapp.agents.agent47://tt2543456?lang=en", ProviderIdType.Imdb).Returns("tt2543456").SetName("Unknown IMDB agent");
yield return new TestCaseData("com.plexapp.agents.agent47://456822/1/1?lang=en", ProviderIdType.TvDb).Returns("456822").SetName("Unknown TvDb agent");
yield return new TestCaseData("com.plexapp.agents.agent47://456822/999/999?lang=en", ProviderIdType.TvDb).Returns("456822").SetName("Unknown TvDb agent, large episode and season");
}
}
public enum ProviderIdType
{
Imdb,
TvDb,
MovieDb
}
}
}

View file

@ -28,18 +28,15 @@ namespace Ombi.Helpers
return result; return result;
} }
using (await _mutex.LockAsync()) if (_memoryCache.TryGetValue(cacheKey, out result))
{ {
if (_memoryCache.TryGetValue(cacheKey, out result))
{
return result;
}
result = await factory();
_memoryCache.Set(cacheKey, result, absoluteExpiration);
return result; return result;
} }
result = await factory();
_memoryCache.Set(cacheKey, result, absoluteExpiration);
return result;
} }
public void Remove(string key) public void Remove(string key)
@ -49,32 +46,32 @@ namespace Ombi.Helpers
public T GetOrAdd<T>(string cacheKey, Func<T> factory, DateTime absoluteExpiration) public T GetOrAdd<T>(string cacheKey, Func<T> factory, DateTime absoluteExpiration)
{
// locks get and set internally
if (_memoryCache.TryGetValue<T>(cacheKey, out var result))
{ {
// locks get and set internally return result;
if (_memoryCache.TryGetValue<T>(cacheKey, out var result)) }
lock (TypeLock<T>.Lock)
{
if (_memoryCache.TryGetValue(cacheKey, out result))
{ {
return result; return result;
} }
lock (TypeLock<T>.Lock) result = factory();
{ _memoryCache.Set(cacheKey, result, absoluteExpiration);
if (_memoryCache.TryGetValue(cacheKey, out result))
{
return result;
}
result = factory(); return result;
_memoryCache.Set(cacheKey, result, absoluteExpiration);
return result;
}
} }
}
private static class TypeLock<T> private static class TypeLock<T>
{ {
public static object Lock { get; } = new object(); public static object Lock { get; } = new object();
} }
} }
} }

View file

@ -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; }
}
}

View file

@ -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; }
}
}

View file

@ -7,11 +7,16 @@ namespace Ombi.Helpers
{ {
public class EmbyHelper public class EmbyHelper
{ {
public static string GetEmbyMediaUrl(string mediaId) public static string GetEmbyMediaUrl(string mediaId, string customerServerUrl = null)
{ {
var url = if (customerServerUrl.HasValue())
$"http://app.emby.media/#!/itemdetails.html?id={mediaId}"; {
return url; return $"{customerServerUrl}#!/itemdetails.html?id={mediaId}";
}
else
{
return $"https://app.emby.media/#!/itemdetails.html?id={mediaId}";
}
} }
} }
} }

View file

@ -21,6 +21,7 @@ namespace Ombi.Helpers
public static EventId PlexContentCacher => new EventId(2008); public static EventId PlexContentCacher => new EventId(2008);
public static EventId SickRageCacher => new EventId(2009); public static EventId SickRageCacher => new EventId(2009);
public static EventId LidarrArtistCache => new EventId(2010); public static EventId LidarrArtistCache => new EventId(2010);
public static EventId MediaReferesh => new EventId(2011);
public static EventId MovieSender => new EventId(3000); public static EventId MovieSender => new EventId(3000);
@ -31,6 +32,7 @@ namespace Ombi.Helpers
public static EventId MattermostNotification => new EventId(4004); public static EventId MattermostNotification => new EventId(4004);
public static EventId PushoverNotification => new EventId(4005); public static EventId PushoverNotification => new EventId(4005);
public static EventId TelegramNotifcation => new EventId(4006); public static EventId TelegramNotifcation => new EventId(4006);
public static EventId GotifyNotification => new EventId(4007);
public static EventId TvSender => new EventId(5000); public static EventId TvSender => new EventId(5000);
public static EventId SonarrSender => new EventId(5001); public static EventId SonarrSender => new EventId(5001);

View file

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

View file

@ -10,9 +10,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="EasyCrypto" Version="3.3.2" /> <PackageReference Include="EasyCrypto" Version="3.3.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.1.2" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" /> <PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
<PackageReference Include="System.Security.Claims" Version="4.3.0" /> <PackageReference Include="System.Security.Claims" Version="4.3.0" />
</ItemGroup> </ItemGroup>

View file

@ -14,5 +14,7 @@
public const string RequestMusic = nameof(RequestMusic); public const string RequestMusic = nameof(RequestMusic);
public const string Disabled = nameof(Disabled); public const string Disabled = nameof(Disabled);
public const string ReceivesNewsletter = nameof(ReceivesNewsletter); public const string ReceivesNewsletter = nameof(ReceivesNewsletter);
public const string ManageOwnRequests = nameof(ManageOwnRequests);
public const string EditCustomPage = nameof(EditCustomPage);
} }
} }

View file

@ -27,11 +27,14 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions;
namespace Ombi.Helpers namespace Ombi.Helpers
{ {
public class PlexHelper public class PlexHelper
{ {
private const string ImdbMatchExpression = "tt([0-9]{1,10})";
private const string TvDbIdMatchExpression = "//[0-9]+/([0-9]{1,3})/([0-9]{1,3})";
public static ProviderId GetProviderIdFromPlexGuid(string guid) public static ProviderId GetProviderIdFromPlexGuid(string guid)
{ {
@ -52,7 +55,7 @@ namespace Ombi.Helpers
{ {
TheTvDb = guidSplit[1] TheTvDb = guidSplit[1]
}; };
} } else
if (guid.Contains("themoviedb", CompareOptions.IgnoreCase)) if (guid.Contains("themoviedb", CompareOptions.IgnoreCase))
{ {
return new ProviderId return new ProviderId
@ -60,6 +63,7 @@ namespace Ombi.Helpers
TheMovieDb = guidSplit[1] TheMovieDb = guidSplit[1]
}; };
} }
else
if (guid.Contains("imdb", CompareOptions.IgnoreCase)) if (guid.Contains("imdb", CompareOptions.IgnoreCase))
{ {
return new ProviderId return new ProviderId
@ -67,6 +71,31 @@ namespace Ombi.Helpers
ImdbId = guidSplit[1] ImdbId = guidSplit[1]
}; };
} }
else
{
var imdbRegex = new Regex(ImdbMatchExpression, RegexOptions.Compiled);
var tvdbRegex = new Regex(TvDbIdMatchExpression, RegexOptions.Compiled);
var imdbMatch = imdbRegex.IsMatch(guid);
if (imdbMatch)
{
return new ProviderId
{
ImdbId = guidSplit[1]
};
}
else
{
// Check if it matches the TvDb pattern
var tvdbMatch = tvdbRegex.IsMatch(guid);
if (tvdbMatch)
{
return new ProviderId
{
TheTvDb = guidSplit[1]
};
}
}
}
} }
return new ProviderId(); return new ProviderId();
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Security; using System.Security;
@ -76,6 +77,49 @@ namespace Ombi.Helpers
return -1; return -1;
} }
public static string BuildEpisodeList(IEnumerable<int> orderedEpisodes)
{
var epSb = new StringBuilder();
var previousEpisodes = new List<int>();
var previousEpisode = -1;
foreach (var ep in orderedEpisodes)
{
if (ep - 1 == previousEpisode)
{
// This is the next one
previousEpisodes.Add(ep);
}
else
{
if (previousEpisodes.Count > 1)
{
// End it
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}, ");
}
else if (previousEpisodes.Count == 1)
{
epSb.Append($"{previousEpisodes.FirstOrDefault()}, ");
}
// New one
previousEpisodes.Clear();
previousEpisodes.Add(ep);
}
previousEpisode = ep;
}
if (previousEpisodes.Count > 1)
{
// Got some left over
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}");
}
else if (previousEpisodes.Count == 1)
{
epSb.Append(previousEpisodes.FirstOrDefault());
}
return epSb.ToString();
}
public static string RemoveSpaces(this string str) public static string RemoveSpaces(this string str)
{ {
return str.Replace(" ", ""); return str.Replace(" ", "");

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