mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-22 14:13:36 -07:00
Merge branch 'Ombi-app:develop' into develop
This commit is contained in:
commit
fc7d087a8d
159 changed files with 9605 additions and 4384 deletions
2
.github/workflows/chromatic.yml
vendored
2
.github/workflows/chromatic.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
||||||
exitOnceUploaded: true
|
exitOnceUploaded: true
|
||||||
|
|
||||||
- name: Publish to Chromatic and auto accept changes
|
- name: Publish to Chromatic and auto accept changes
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/develop'
|
||||||
uses: chromaui/action@v1
|
uses: chromaui/action@v1
|
||||||
with:
|
with:
|
||||||
projectToken: 7c47e1a1a4bd
|
projectToken: 7c47e1a1a4bd
|
||||||
|
|
32
.github/workflows/cypress.yml
vendored
32
.github/workflows/cypress.yml
vendored
|
@ -34,16 +34,28 @@ jobs:
|
||||||
- name: Install Frontend Deps
|
- name: Install Frontend Deps
|
||||||
run: yarn --cwd ./src/Ombi/ClientApp install
|
run: yarn --cwd ./src/Ombi/ClientApp install
|
||||||
|
|
||||||
- name: Start Frontend
|
- name: Build Frontend
|
||||||
run: |
|
run: yarn --cwd ./src/Ombi/ClientApp build
|
||||||
nohup yarn --cwd ./src/Ombi/ClientApp start &
|
|
||||||
|
|
||||||
- name: Install Automation Deps
|
- name: Build Docker Image
|
||||||
run: yarn --cwd ./tests install
|
run: docker build -t ombi src/
|
||||||
|
|
||||||
- name: Start Backend
|
- name: Run Docker Image
|
||||||
run: |
|
run: nohup docker run --rm -p 5000:5000 ombi &
|
||||||
nohup dotnet run --project ./src/Ombi -- --host http://*:3577 &
|
|
||||||
|
- name: Sleep for server to start
|
||||||
|
run: sleep 20
|
||||||
|
|
||||||
|
# - name: Start Frontend
|
||||||
|
# run: |
|
||||||
|
# nohup yarn --cwd ./src/Ombi/ClientApp start &
|
||||||
|
|
||||||
|
# - name: Install Automation Deps
|
||||||
|
# run: yarn --cwd ./tests install
|
||||||
|
|
||||||
|
# - name: Start Backend
|
||||||
|
# run: |
|
||||||
|
# nohup dotnet run --project ./src/Ombi -- --host http://*:3577 &
|
||||||
|
|
||||||
- name: Cypress Tests
|
- name: Cypress Tests
|
||||||
uses: cypress-io/github-action@v2.8.2
|
uses: cypress-io/github-action@v2.8.2
|
||||||
|
@ -52,9 +64,9 @@ jobs:
|
||||||
browser: chrome
|
browser: chrome
|
||||||
headless: true
|
headless: true
|
||||||
working-directory: tests
|
working-directory: tests
|
||||||
wait-on: http://localhost:3577/
|
wait-on: http://localhost:5000/
|
||||||
# 10 minutes
|
# 10 minutes
|
||||||
wait-on-timeout: 600
|
wait-on-timeout: 600
|
||||||
env:
|
env:
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
237
CHANGELOG.md
237
CHANGELOG.md
|
@ -1,3 +1,145 @@
|
||||||
|
## [4.27.6](https://github.com/Ombi-app/Ombi/compare/v4.27.5...v4.27.6) (2022-10-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **notifications:** Fixed the error when sending multiple test notifications. Added more logging when Discord complains the message is invalid ([fc14780](https://github.com/Ombi-app/Ombi/commit/fc14780bd354483119ddcbb55a8c382e1890a783))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.27.5](https://github.com/Ombi-app/Ombi/compare/v4.27.4...v4.27.5) (2022-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **importer:** 🐛 Allow you to only import Plex Admins without the Plex Users ([8c9ad9b](https://github.com/Ombi-app/Ombi/commit/8c9ad9b414fdc6c88bdb911d6057ae5d38783b98))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.27.4](https://github.com/Ombi-app/Ombi/compare/v4.27.3...v4.27.4) (2022-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.27.3](https://github.com/Ombi-app/Ombi/compare/v4.27.2...v4.27.3) (2022-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **availability:** 🐛 Fixed a issue with the availability checker after the previous update. Added full test coverage around that area ([28e2480](https://github.com/Ombi-app/Ombi/commit/28e248046ad56390595f84172bbd5f5961325b4d))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.27.2](https://github.com/Ombi-app/Ombi/compare/v4.27.1...v4.27.2) (2022-09-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **sonarr:** :bug: Cleaned up and removed Sonarr v3 option, sonarr v3 is now the default. This allows us to get ready for the upcoming Sonarr v4 ([#4764](https://github.com/Ombi-app/Ombi/issues/4764)) ([2cddec7](https://github.com/Ombi-app/Ombi/commit/2cddec759004b6490f686ff74cb092238e3dc946))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.27.1](https://github.com/Ombi-app/Ombi/compare/v4.27.0...v4.27.1) (2022-09-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **plex:** stop the plex sync from deleting episodes when we can't find the plex key ([66b05e5](https://github.com/Ombi-app/Ombi/commit/66b05e5a85dbfe1fec5f9366e80987f2cfa1f4fe))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [4.27.0](https://github.com/Ombi-app/Ombi/compare/v4.26.0...v4.27.0) (2022-09-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Recently requested improvements ([#4755](https://github.com/Ombi-app/Ombi/issues/4755)) ([ff04d87](https://github.com/Ombi-app/Ombi/commit/ff04d875343604c77c391bf55d0968977e480281))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [4.26.0](https://github.com/Ombi-app/Ombi/compare/v4.25.1...v4.26.0) (2022-09-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **notifications:** Add more curly variables for partially available notification ([66aa101](https://github.com/Ombi-app/Ombi/commit/66aa101019c4c4b34e186db9d303049d02b9c781))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.25.1](https://github.com/Ombi-app/Ombi/compare/v4.25.0...v4.25.1) (2022-09-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **webhook:** Remove added trailing slash from webhook URL [#4710](https://github.com/Ombi-app/Ombi/issues/4710) ([369eb33](https://github.com/Ombi-app/Ombi/commit/369eb339171671101be219486e2aab27a20f3d74))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [4.25.0](https://github.com/Ombi-app/Ombi/compare/v4.24.0...v4.25.0) (2022-08-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fixed stats controller ([#4742](https://github.com/Ombi-app/Ombi/issues/4742)) ([47ea64b](https://github.com/Ombi-app/Ombi/commit/47ea64b5a401770f1943b575ca40f84d515e96b3))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Watchlist history errors([#4741](https://github.com/Ombi-app/Ombi/issues/4741)) ([c222f1a](https://github.com/Ombi-app/Ombi/commit/c222f1a945e944ef34e68cad2b61f40e57cab823))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [4.24.0](https://github.com/Ombi-app/Ombi/compare/v4.23.2...v4.24.0) (2022-08-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add crew on movie page ([#4722](https://github.com/Ombi-app/Ombi/issues/4722)) ([1d53261](https://github.com/Ombi-app/Ombi/commit/1d532613823804b25984bd1d223d081a54ad143d))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.23.2](https://github.com/Ombi-app/Ombi/compare/v4.23.1...v4.23.2) (2022-08-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix conflicting property name for Swagger ([#4733](https://github.com/Ombi-app/Ombi/issues/4733)) ([d661f32](https://github.com/Ombi-app/Ombi/commit/d661f32e8a9e105faab6380b4b7b642896b98163))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.23.1](https://github.com/Ombi-app/Ombi/compare/v4.23.0...v4.23.1) (2022-08-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Localize recently requested on discover page ([#4729](https://github.com/Ombi-app/Ombi/issues/4729)) ([bf65c76](https://github.com/Ombi-app/Ombi/commit/bf65c76ff9ce38f65a9e5feb872734e8d8e35eb6))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [4.23.0](https://github.com/Ombi-app/Ombi/compare/v4.22.4...v4.23.0) (2022-08-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Log Microsoft warnings to log file ([#4723](https://github.com/Ombi-app/Ombi/issues/4723)) ([26ac75f](https://github.com/Ombi-app/Ombi/commit/26ac75f0c223c2a91e3471797ae46ede3fde89cc))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Recently Requested on Discover Page ([#4387](https://github.com/Ombi-app/Ombi/issues/4387)) ([44d38fb](https://github.com/Ombi-app/Ombi/commit/44d38fbaae521dbb467b61c7471b2384015ac52e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.22.4](https://github.com/Ombi-app/Ombi/compare/v4.22.3...v4.22.4) (2022-08-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* :bug: Fixed missing externals ([#4712](https://github.com/Ombi-app/Ombi/issues/4712)) ([fcc1eaa](https://github.com/Ombi-app/Ombi/commit/fcc1eaaa377683dcdc81d62a2a688fb0c4490c7b))
|
||||||
|
* fixed trakt image not loading when base url present ([#4711](https://github.com/Ombi-app/Ombi/issues/4711)) ([f102dcf](https://github.com/Ombi-app/Ombi/commit/f102dcf751c2eb62ebfe30f9f8e4b2ad863c3b0d))
|
||||||
|
* **translations:** 🌐 New translations from Crowdin [skip ci] ([#4713](https://github.com/Ombi-app/Ombi/issues/4713)) ([ff142b0](https://github.com/Ombi-app/Ombi/commit/ff142b09abbb2f9540387284222552e6e12639fe))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.22.3](https://github.com/Ombi-app/Ombi/compare/v4.22.2...v4.22.3) (2022-07-28)
|
## [4.22.3](https://github.com/Ombi-app/Ombi/compare/v4.22.2...v4.22.3) (2022-07-28)
|
||||||
|
|
||||||
|
|
||||||
|
@ -264,98 +406,3 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.16.8](https://github.com/Ombi-app/Ombi/compare/v4.16.7...v4.16.8) (2022-04-13)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **availability:** Fixed an issue where we wouldn't mark a available 4k movie as available (when 4K request feature is disabled) ([b492699](https://github.com/Ombi-app/Ombi/commit/b49269961d4830a530e3054976a47f519524948b))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.16.7](https://github.com/Ombi-app/Ombi/compare/v4.16.6...v4.16.7) (2022-04-12)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.16.6](https://github.com/Ombi-app/Ombi/compare/v4.16.5...v4.16.6) (2022-04-11)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.16.5](https://github.com/Ombi-app/Ombi/compare/v4.16.4...v4.16.5) (2022-04-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **watchlist:** actually fixed it this time... ([d962a32](https://github.com/Ombi-app/Ombi/commit/d962a3211eca29520662ddce962676e3aea17ec5))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.16.4](https://github.com/Ombi-app/Ombi/compare/v4.16.3...v4.16.4) (2022-04-08)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.16.3](https://github.com/Ombi-app/Ombi/compare/v4.16.2...v4.16.3) (2022-04-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **plex-watchlist:** :bug: Fixed the issue where the watchlist didn't work for users logging in via OAuth ([6398f6a](https://github.com/Ombi-app/Ombi/commit/6398f6a4f7755281ebeac537e3ff623df5cfa0f3))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.16.2](https://github.com/Ombi-app/Ombi/compare/v4.16.1...v4.16.2) (2022-04-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **wizard:** Fixed an issue when using Plex OAuth it could fail setting up ([b743cf4](https://github.com/Ombi-app/Ombi/commit/b743cf4fafa7341ad1b163276f006d7ab0e9dcff))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.16.1](https://github.com/Ombi-app/Ombi/compare/v4.16.0...v4.16.1) (2022-04-07)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [4.16.0](https://github.com/Ombi-app/Ombi/compare/v4.15.6...v4.16.0) (2022-04-07)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.15.6](https://github.com/Ombi-app/Ombi/compare/v4.15.5...v4.15.6) (2022-04-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **radarr:** Fixed an issue where we couldn't sync radarr content [#4577](https://github.com/Ombi-app/Ombi/issues/4577) ([a5355a3](https://github.com/Ombi-app/Ombi/commit/a5355a3023e6900c4dd1b0da4722d7596c03907f))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.15.5](https://github.com/Ombi-app/Ombi/compare/v4.15.4...v4.15.5) (2022-04-06)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.15.4](https://github.com/Ombi-app/Ombi/compare/v4.15.3...v4.15.4) (2022-03-29)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.15.3](https://github.com/Ombi-app/Ombi/compare/v4.15.2...v4.15.3) (2022-03-24)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.15.2](https://github.com/Ombi-app/Ombi/compare/v4.15.1...v4.15.2) (2022-03-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **metadata:** improved the metadata job to also lookup the media in Plex to see if it has any more uptodate metadata ([83d1a15](https://github.com/Ombi-app/Ombi/commit/83d1a15cc9d0ee91be73bd91c4672cf1bcf2728a))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.15.1](https://github.com/Ombi-app/Ombi/compare/v4.15.0...v4.15.1) (2022-03-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **mediaserver:** fixed an issue where we were not detecting available content correctly [#4542](https://github.com/Ombi-app/Ombi/issues/4542) ([9cdd6f4](https://github.com/Ombi-app/Ombi/commit/9cdd6f41cdab8825a984905c089611409c53c753))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
66
README.md
66
README.md
|
@ -515,6 +515,13 @@ Here are some of the features Ombi has:
|
||||||
<sub><b>Fish2</b></sub>
|
<sub><b>Fish2</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/ketsapiwiq">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/26697460?v=4" width="50;" alt="ketsapiwiq"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Hadrien</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/hariesramdhani">
|
<a href="https://github.com/hariesramdhani">
|
||||||
<img src="https://avatars.githubusercontent.com/u/24251244?v=4" width="50;" alt="hariesramdhani"/>
|
<img src="https://avatars.githubusercontent.com/u/24251244?v=4" width="50;" alt="hariesramdhani"/>
|
||||||
|
@ -535,15 +542,15 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Imgbot</b></sub>
|
<sub><b>Imgbot</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/JPyke3">
|
<a href="https://github.com/JPyke3">
|
||||||
<img src="https://avatars.githubusercontent.com/u/13283054?v=4" width="50;" alt="JPyke3"/>
|
<img src="https://avatars.githubusercontent.com/u/13283054?v=4" width="50;" alt="JPyke3"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Jacob Pyke</b></sub>
|
<sub><b>Jacob Pyke</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/jamesmacwhite">
|
<a href="https://github.com/jamesmacwhite">
|
||||||
<img src="https://avatars.githubusercontent.com/u/8067792?v=4" width="50;" alt="jamesmacwhite"/>
|
<img src="https://avatars.githubusercontent.com/u/8067792?v=4" width="50;" alt="jamesmacwhite"/>
|
||||||
|
@ -578,15 +585,15 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Jono Cairns</b></sub>
|
<sub><b>Jono Cairns</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/krisklosterman">
|
<a href="https://github.com/krisklosterman">
|
||||||
<img src="https://avatars.githubusercontent.com/u/7139579?v=4" width="50;" alt="krisklosterman"/>
|
<img src="https://avatars.githubusercontent.com/u/7139579?v=4" width="50;" alt="krisklosterman"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Kris Klosterman</b></sub>
|
<sub><b>Kris Klosterman</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/kmlucy">
|
<a href="https://github.com/kmlucy">
|
||||||
<img src="https://avatars.githubusercontent.com/u/13952475?v=4" width="50;" alt="kmlucy"/>
|
<img src="https://avatars.githubusercontent.com/u/13952475?v=4" width="50;" alt="kmlucy"/>
|
||||||
|
@ -621,15 +628,15 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Marley</b></sub>
|
<sub><b>Marley</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/mattmattmatt">
|
<a href="https://github.com/mattmattmatt">
|
||||||
<img src="https://avatars.githubusercontent.com/u/927830?v=4" width="50;" alt="mattmattmatt"/>
|
<img src="https://avatars.githubusercontent.com/u/927830?v=4" width="50;" alt="mattmattmatt"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Matt</b></sub>
|
<sub><b>Matt</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/LMaxence">
|
<a href="https://github.com/LMaxence">
|
||||||
<img src="https://avatars.githubusercontent.com/u/29194680?v=4" width="50;" alt="LMaxence"/>
|
<img src="https://avatars.githubusercontent.com/u/29194680?v=4" width="50;" alt="LMaxence"/>
|
||||||
|
@ -644,6 +651,13 @@ Here are some of the features Ombi has:
|
||||||
<sub><b>Micky</b></sub>
|
<sub><b>Micky</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/mvicomoya">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/24613599?v=4" width="50;" alt="mvicomoya"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Miguel A Vico Moya</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/beast3334">
|
<a href="https://github.com/beast3334">
|
||||||
<img src="https://avatars.githubusercontent.com/u/20631046?v=4" width="50;" alt="beast3334"/>
|
<img src="https://avatars.githubusercontent.com/u/20631046?v=4" width="50;" alt="beast3334"/>
|
||||||
|
@ -657,7 +671,8 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Qiming Chen</b></sub>
|
<sub><b>Qiming Chen</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/randallbruder">
|
<a href="https://github.com/randallbruder">
|
||||||
<img src="https://avatars.githubusercontent.com/u/6447487?v=4" width="50;" alt="randallbruder"/>
|
<img src="https://avatars.githubusercontent.com/u/6447487?v=4" width="50;" alt="randallbruder"/>
|
||||||
|
@ -671,8 +686,7 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Rob Gökemeijer</b></sub>
|
<sub><b>Rob Gökemeijer</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/sambartik">
|
<a href="https://github.com/sambartik">
|
||||||
<img src="https://avatars.githubusercontent.com/u/63553146?v=4" width="50;" alt="sambartik"/>
|
<img src="https://avatars.githubusercontent.com/u/63553146?v=4" width="50;" alt="sambartik"/>
|
||||||
|
@ -700,7 +714,8 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Teifun2</b></sub>
|
<sub><b>Teifun2</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/thomasvt1">
|
<a href="https://github.com/thomasvt1">
|
||||||
<img src="https://avatars.githubusercontent.com/u/2271011?v=4" width="50;" alt="thomasvt1"/>
|
<img src="https://avatars.githubusercontent.com/u/2271011?v=4" width="50;" alt="thomasvt1"/>
|
||||||
|
@ -714,8 +729,7 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Tim Trott</b></sub>
|
<sub><b>Tim Trott</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/tombomb">
|
<a href="https://github.com/tombomb">
|
||||||
<img src="https://avatars.githubusercontent.com/u/544509?v=4" width="50;" alt="tombomb"/>
|
<img src="https://avatars.githubusercontent.com/u/544509?v=4" width="50;" alt="tombomb"/>
|
||||||
|
@ -743,7 +757,8 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Xirg</b></sub>
|
<sub><b>Xirg</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/bazhip">
|
<a href="https://github.com/bazhip">
|
||||||
<img src="https://avatars.githubusercontent.com/u/10350445?v=4" width="50;" alt="bazhip"/>
|
<img src="https://avatars.githubusercontent.com/u/10350445?v=4" width="50;" alt="bazhip"/>
|
||||||
|
@ -757,8 +772,7 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Blake Drumm</b></sub>
|
<sub><b>Blake Drumm</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/camjac251">
|
<a href="https://github.com/camjac251">
|
||||||
<img src="https://avatars.githubusercontent.com/u/6313132?v=4" width="50;" alt="camjac251"/>
|
<img src="https://avatars.githubusercontent.com/u/6313132?v=4" width="50;" alt="camjac251"/>
|
||||||
|
@ -786,7 +800,8 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Echel0n</b></sub>
|
<sub><b>Echel0n</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/m4tta">
|
<a href="https://github.com/m4tta">
|
||||||
<img src="https://avatars.githubusercontent.com/u/427218?v=4" width="50;" alt="m4tta"/>
|
<img src="https://avatars.githubusercontent.com/u/427218?v=4" width="50;" alt="m4tta"/>
|
||||||
|
@ -800,8 +815,7 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Maartenheebink</b></sub>
|
<sub><b>Maartenheebink</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/masterhuck">
|
<a href="https://github.com/masterhuck">
|
||||||
<img src="https://avatars.githubusercontent.com/u/4671442?v=4" width="50;" alt="masterhuck"/>
|
<img src="https://avatars.githubusercontent.com/u/4671442?v=4" width="50;" alt="masterhuck"/>
|
||||||
|
@ -809,6 +823,13 @@ Here are some of the features Ombi has:
|
||||||
<sub><b>Patrick Weber</b></sub>
|
<sub><b>Patrick Weber</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/mkgeeky">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/68811367?v=4" width="50;" alt="mkgeeky"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Mkgeeky</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/sir-marv">
|
<a href="https://github.com/sir-marv">
|
||||||
<img src="https://avatars.githubusercontent.com/u/3598205?v=4" width="50;" alt="sir-marv"/>
|
<img src="https://avatars.githubusercontent.com/u/3598205?v=4" width="50;" alt="sir-marv"/>
|
||||||
|
@ -822,7 +843,8 @@ Here are some of the features Ombi has:
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Tdorsey</b></sub>
|
<sub><b>Tdorsey</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/thegame3202">
|
<a href="https://github.com/thegame3202">
|
||||||
<img src="https://avatars.githubusercontent.com/u/22148848?v=4" width="50;" alt="thegame3202"/>
|
<img src="https://avatars.githubusercontent.com/u/22148848?v=4" width="50;" alt="thegame3202"/>
|
||||||
|
|
285
src/.dockerignore
Normal file
285
src/.dockerignore
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
**/bin/
|
||||||
|
**/obj/
|
||||||
|
**/.angular/
|
||||||
|
**/node_modules/
|
||||||
|
.gitignore
|
||||||
|
.git/
|
||||||
|
|
||||||
|
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
|
||||||
|
# Visual Studio 2015 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# DNX
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
Properties/launchSettings.json
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding add-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/packages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/packages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/packages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignoreable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
node_modules/
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# CodeRush
|
||||||
|
.cr/
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/
|
||||||
|
tools/Cake.CoreCLR
|
||||||
|
.vscode
|
||||||
|
tools
|
||||||
|
.dotnet
|
||||||
|
Dockerfile
|
||||||
|
|
||||||
|
# .env file contains default environment variables for docker
|
||||||
|
.env
|
||||||
|
.git/
|
6
src/.idea/.idea.Ombi/.idea/contentModel.xml
generated
6
src/.idea/.idea.Ombi/.idea/contentModel.xml
generated
|
@ -268,6 +268,12 @@
|
||||||
<e p="cast-carousel.component.scss" t="Include" />
|
<e p="cast-carousel.component.scss" t="Include" />
|
||||||
<e p="cast-carousel.component.ts" t="Include" />
|
<e p="cast-carousel.component.ts" t="Include" />
|
||||||
</e>
|
</e>
|
||||||
|
<e p="crew-carousel" t="Include">
|
||||||
|
<e p="crew-carousel.component.html" t="Include" />
|
||||||
|
<e p="crew-carousel.component.scss" t="Include" />
|
||||||
|
<e p="crew-carousel.component.ts" t="Include" />
|
||||||
|
</e>
|
||||||
|
|
||||||
<e p="deny-dialog" t="Include">
|
<e p="deny-dialog" t="Include">
|
||||||
<e p="deny-dialog.component.html" t="Include" />
|
<e p="deny-dialog.component.html" t="Include" />
|
||||||
<e p="deny-dialog.component.ts" t="Include" />
|
<e p="deny-dialog.component.ts" t="Include" />
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System.Net.Http;
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Ombi.Api.Discord.Models;
|
using Ombi.Api.Discord.Models;
|
||||||
|
@ -23,7 +25,20 @@ namespace Ombi.Api.Discord
|
||||||
|
|
||||||
request.ApplicationJsonContentType();
|
request.ApplicationJsonContentType();
|
||||||
|
|
||||||
await Api.Request(request);
|
var response = await Api.Request(request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
throw new DiscordException(content, response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DiscordException : Exception
|
||||||
|
{
|
||||||
|
public DiscordException(string content, HttpStatusCode code) : base($"Exception when calling Discord with status code {code} and message: {content}")
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,27 +5,16 @@ namespace Ombi.Api.Lidarr.Models
|
||||||
{
|
{
|
||||||
public class ArtistAdd
|
public class ArtistAdd
|
||||||
{
|
{
|
||||||
public string status { get; set; }
|
|
||||||
public bool ended { get; set; }
|
|
||||||
public string artistName { get; set; }
|
public string artistName { get; set; }
|
||||||
public string foreignArtistId { get; set; }
|
public string foreignArtistId { get; set; }
|
||||||
public int tadbId { get; set; }
|
|
||||||
public int discogsId { get; set; }
|
|
||||||
public string overview { get; set; }
|
|
||||||
public string disambiguation { get; set; }
|
|
||||||
public Link[] links { get; set; }
|
public Link[] links { get; set; }
|
||||||
public Image[] images { get; set; }
|
public Image[] images { get; set; }
|
||||||
public string remotePoster { get; set; }
|
|
||||||
public int qualityProfileId { get; set; }
|
public int qualityProfileId { get; set; }
|
||||||
public int metadataProfileId { get; set; }
|
public int metadataProfileId { get; set; }
|
||||||
public bool albumFolder { get; set; }
|
public bool albumFolder { get; set; }
|
||||||
public bool monitored { get; set; }
|
public bool monitored { get; set; }
|
||||||
public string cleanName { get; set; }
|
public string cleanName { get; set; }
|
||||||
public string sortName { get; set; }
|
|
||||||
public object[] tags { get; set; }
|
|
||||||
public DateTime added { get; set; }
|
public DateTime added { get; set; }
|
||||||
public Ratings ratings { get; set; }
|
|
||||||
public Statistics statistics { get; set; }
|
|
||||||
public Addoptions addOptions { get; set; }
|
public Addoptions addOptions { get; set; }
|
||||||
public string rootFolderPath { get; set; }
|
public string rootFolderPath { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -49,4 +38,4 @@ namespace Ombi.Api.Lidarr.Models
|
||||||
None,
|
None,
|
||||||
Unknown
|
Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ namespace Ombi.Api.Plex.Models
|
||||||
public string grandparentTheme { get; set; }
|
public string grandparentTheme { get; set; }
|
||||||
public string chapterSource { get; set; }
|
public string chapterSource { get; set; }
|
||||||
public Medium[] Media { get; set; }
|
public Medium[] Media { get; set; }
|
||||||
|
[JsonProperty("Guid")] // force uppercase to solve conflict with lowercase guid
|
||||||
public List<PlexGuids> Guid { get; set; } = new List<PlexGuids>();
|
public List<PlexGuids> Guid { get; set; } = new List<PlexGuids>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ombi.Api.Plex.Models
|
namespace Ombi.Api.Plex.Models
|
||||||
{
|
{
|
||||||
public class PlexWatchlistContainer
|
public class PlexWatchlistContainer
|
||||||
{
|
{
|
||||||
public PlexWatchlist MediaContainer { get; set; }
|
public PlexWatchlist MediaContainer { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool AuthError { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -295,9 +296,18 @@ namespace Ombi.Api.Plex
|
||||||
var request = new Request("library/sections/watchlist/all", WatchlistUri, HttpMethod.Get);
|
var request = new Request("library/sections/watchlist/all", WatchlistUri, HttpMethod.Get);
|
||||||
await AddHeaders(request, plexToken);
|
await AddHeaders(request, plexToken);
|
||||||
|
|
||||||
var result = await Api.Request<PlexWatchlistContainer>(request, cancellationToken);
|
var result = await Api.Request(request, cancellationToken);
|
||||||
|
|
||||||
return result;
|
if (result.StatusCode.Equals(HttpStatusCode.Unauthorized))
|
||||||
|
{
|
||||||
|
return new PlexWatchlistContainer
|
||||||
|
{
|
||||||
|
AuthError = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var receivedString = await result.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
return JsonConvert.DeserializeObject<PlexWatchlistContainer>(receivedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PlexWatchlistMetadataContainer> GetWatchlistMetadata(string ratingKey, string plexToken, CancellationToken cancellationToken)
|
public async Task<PlexWatchlistMetadataContainer> GetWatchlistMetadata(string ratingKey, string plexToken, CancellationToken cancellationToken)
|
||||||
|
|
|
@ -5,8 +5,6 @@ namespace Ombi.Api.Sonarr.Models
|
||||||
public class SonarrProfile
|
public class SonarrProfile
|
||||||
{
|
{
|
||||||
public string name { get; set; }
|
public string name { get; set; }
|
||||||
public Cutoff cutoff { get; set; }
|
|
||||||
public List<Item> items { get; set; }
|
|
||||||
public int id { get; set; }
|
public int id { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,8 +19,8 @@ namespace Ombi.Api.Webhook
|
||||||
|
|
||||||
public async Task PushAsync(string baseUrl, string accessToken, IDictionary<string, string> parameters)
|
public async Task PushAsync(string baseUrl, string accessToken, IDictionary<string, string> parameters)
|
||||||
{
|
{
|
||||||
var request = new Request("", baseUrl, HttpMethod.Post);
|
var request = new Request("", baseUrl, HttpMethod.Post) {IgnoreBaseUrlAppend = true};
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(accessToken))
|
if (!string.IsNullOrWhiteSpace(accessToken))
|
||||||
{
|
{
|
||||||
request.AddHeader("Access-Token", accessToken);
|
request.AddHeader("Access-Token", accessToken);
|
||||||
|
|
146
src/Ombi.Core.Tests/Services/PlexServiceTests.cs
Normal file
146
src/Ombi.Core.Tests/Services/PlexServiceTests.cs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
using MockQueryable.Moq;
|
||||||
|
using Moq.AutoMock;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
using Ombi.Core.Services;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
using Ombi.Test.Common;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UserType = Ombi.Store.Entities.UserType;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Tests.Services
|
||||||
|
{
|
||||||
|
public class PlexServiceTests
|
||||||
|
{
|
||||||
|
|
||||||
|
private PlexService _subject;
|
||||||
|
private AutoMocker _mocker;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_mocker = new AutoMocker();
|
||||||
|
_subject = _mocker.CreateInstance<PlexService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetWatchListUsers_AllUsersSynced()
|
||||||
|
{
|
||||||
|
var userMock = MockHelper.MockUserManager(new List<OmbiUser>
|
||||||
|
{
|
||||||
|
new OmbiUser
|
||||||
|
{
|
||||||
|
MediaServerToken = "token",
|
||||||
|
Id = "1",
|
||||||
|
UserName = "user1",
|
||||||
|
UserType = UserType.PlexUser,
|
||||||
|
},
|
||||||
|
new OmbiUser
|
||||||
|
{
|
||||||
|
MediaServerToken = "token",
|
||||||
|
Id = "2",
|
||||||
|
UserName = "user2",
|
||||||
|
UserType = UserType.PlexUser,
|
||||||
|
},
|
||||||
|
new OmbiUser
|
||||||
|
{
|
||||||
|
MediaServerToken = "token",
|
||||||
|
Id = "2",
|
||||||
|
UserName = "user2",
|
||||||
|
UserType = UserType.LocalUser,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_mocker.Use(userMock.Object);
|
||||||
|
_subject = _mocker.CreateInstance<PlexService>();
|
||||||
|
|
||||||
|
_mocker.Setup<IRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll())
|
||||||
|
.Returns(new List<PlexWatchlistUserError>().AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
var result = await _subject.GetWatchlistUsers(CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(result.All(x => x.SyncStatus == WatchlistSyncStatus.Successful));
|
||||||
|
Assert.That(result.Count, Is.EqualTo(2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetWatchListUsers_NotEnabled()
|
||||||
|
{
|
||||||
|
var userMock = MockHelper.MockUserManager(new List<OmbiUser>
|
||||||
|
{
|
||||||
|
new OmbiUser
|
||||||
|
{
|
||||||
|
MediaServerToken = "",
|
||||||
|
Id = "1",
|
||||||
|
UserName = "user1",
|
||||||
|
UserType = UserType.PlexUser,
|
||||||
|
},
|
||||||
|
new OmbiUser
|
||||||
|
{
|
||||||
|
MediaServerToken = null,
|
||||||
|
Id = "2",
|
||||||
|
UserName = "user2",
|
||||||
|
UserType = UserType.PlexUser,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
_mocker.Use(userMock.Object);
|
||||||
|
_subject = _mocker.CreateInstance<PlexService>();
|
||||||
|
|
||||||
|
_mocker.Setup<IRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll())
|
||||||
|
.Returns(new List<PlexWatchlistUserError>().AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
var result = await _subject.GetWatchlistUsers(CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(result.All(x => x.SyncStatus == WatchlistSyncStatus.NotEnabled));
|
||||||
|
Assert.That(result.Count, Is.EqualTo(2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetWatchListUsers_Failed()
|
||||||
|
{
|
||||||
|
var userMock = MockHelper.MockUserManager(new List<OmbiUser>
|
||||||
|
{
|
||||||
|
new OmbiUser
|
||||||
|
{
|
||||||
|
MediaServerToken = "test",
|
||||||
|
Id = "1",
|
||||||
|
UserName = "user1",
|
||||||
|
UserType = UserType.PlexUser,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
_mocker.Use(userMock.Object);
|
||||||
|
_subject = _mocker.CreateInstance<PlexService>();
|
||||||
|
|
||||||
|
_mocker.Setup<IRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll())
|
||||||
|
.Returns(new List<PlexWatchlistUserError>
|
||||||
|
{
|
||||||
|
new PlexWatchlistUserError
|
||||||
|
{
|
||||||
|
UserId = "1",
|
||||||
|
MediaServerToken = "test",
|
||||||
|
}
|
||||||
|
}.AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
var result = await _subject.GetWatchlistUsers(CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(result.All(x => x.SyncStatus == WatchlistSyncStatus.Failed));
|
||||||
|
Assert.That(result.Count, Is.EqualTo(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
202
src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs
Normal file
202
src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
using AutoFixture;
|
||||||
|
using MockQueryable.Moq;
|
||||||
|
using Moq;
|
||||||
|
using Moq.AutoMock;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Helpers;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using Ombi.Core.Services;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Entities.Requests;
|
||||||
|
using Ombi.Store.Repository.Requests;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Tests.Services
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class RecentlyRequestedServiceTests
|
||||||
|
{
|
||||||
|
private AutoMocker _mocker;
|
||||||
|
private RecentlyRequestedService _subject;
|
||||||
|
private Fixture _fixture;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_fixture = new Fixture();
|
||||||
|
|
||||||
|
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
|
||||||
|
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||||
|
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||||
|
_mocker = new AutoMocker();
|
||||||
|
|
||||||
|
_mocker.Setup<ICurrentUser, Task<OmbiUser>>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias", Language = "en" });
|
||||||
|
_mocker.Setup<ICurrentUser, string>(x => x.Username).Returns("test");
|
||||||
|
_subject = _mocker.CreateInstance<RecentlyRequestedService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetRecentlyRequested_Movies()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<CustomizationSettings>, Task<CustomizationSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new CustomizationSettings());
|
||||||
|
var releaseDate = new DateTime(2019, 01, 01);
|
||||||
|
var requestDate = DateTime.Now;
|
||||||
|
var movies = new List<MovieRequests>
|
||||||
|
{
|
||||||
|
new MovieRequests
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Approved = true,
|
||||||
|
Available = true,
|
||||||
|
ReleaseDate = releaseDate,
|
||||||
|
Title = "title",
|
||||||
|
Overview = "overview",
|
||||||
|
RequestedDate = requestDate,
|
||||||
|
RequestedUser = new Store.Entities.OmbiUser
|
||||||
|
{
|
||||||
|
UserName = "a"
|
||||||
|
},
|
||||||
|
RequestedUserId = "b",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var albums = new List<AlbumRequest>();
|
||||||
|
var chilRequests = new List<ChildRequests>();
|
||||||
|
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<IMusicRequestRepository, IQueryable<AlbumRequest>>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<ITvRequestRepository, IQueryable<ChildRequests>>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
var result = await _subject.GetRecentlyRequested(CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.That(result.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(result.First(), Is.InstanceOf<RecentlyRequestedModel>()
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.RequestId)).EqualTo(1)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Approved)).EqualTo(true)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Available)).EqualTo(true)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Title)).EqualTo("title")
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Overview)).EqualTo("overview")
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.RequestDate)).EqualTo(requestDate)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.ReleaseDate)).EqualTo(releaseDate)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Type)).EqualTo(RequestType.Movie)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetRecentlyRequested_Movies_HideAvailable()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<CustomizationSettings>, Task<CustomizationSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new CustomizationSettings() { HideAvailableRecentlyRequested = true });
|
||||||
|
var releaseDate = new DateTime(2019, 01, 01);
|
||||||
|
var requestDate = DateTime.Now;
|
||||||
|
var movies = new List<MovieRequests>
|
||||||
|
{
|
||||||
|
new MovieRequests
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Approved = true,
|
||||||
|
Available = true,
|
||||||
|
ReleaseDate = releaseDate,
|
||||||
|
Title = "title",
|
||||||
|
Overview = "overview",
|
||||||
|
RequestedDate = requestDate,
|
||||||
|
RequestedUser = new Store.Entities.OmbiUser
|
||||||
|
{
|
||||||
|
UserName = "a"
|
||||||
|
},
|
||||||
|
RequestedUserId = "b",
|
||||||
|
},
|
||||||
|
|
||||||
|
new MovieRequests
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Approved = true,
|
||||||
|
Available = false,
|
||||||
|
ReleaseDate = releaseDate,
|
||||||
|
Title = "title2",
|
||||||
|
Overview = "overview2",
|
||||||
|
RequestedDate = requestDate,
|
||||||
|
RequestedUser = new Store.Entities.OmbiUser
|
||||||
|
{
|
||||||
|
UserName = "a"
|
||||||
|
},
|
||||||
|
RequestedUserId = "b",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var albums = new List<AlbumRequest>();
|
||||||
|
var chilRequests = new List<ChildRequests>();
|
||||||
|
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<IMusicRequestRepository, IQueryable<AlbumRequest>>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<ITvRequestRepository, IQueryable<ChildRequests>>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
var result = await _subject.GetRecentlyRequested(CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.That(result.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(result.First(), Is.InstanceOf<RecentlyRequestedModel>()
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.RequestId)).EqualTo(1)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Approved)).EqualTo(true)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Available)).EqualTo(false)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Title)).EqualTo("title2")
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Overview)).EqualTo("overview2")
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.RequestDate)).EqualTo(requestDate)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.ReleaseDate)).EqualTo(releaseDate)
|
||||||
|
.With.Property(nameof(RecentlyRequestedModel.Type)).EqualTo(RequestType.Movie)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetRecentlyRequested()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<CustomizationSettings>, Task<CustomizationSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new CustomizationSettings());
|
||||||
|
var releaseDate = new DateTime(2019, 01, 01);
|
||||||
|
var requestDate = DateTime.Now;
|
||||||
|
|
||||||
|
var movies = _fixture.CreateMany<MovieRequests>(10);
|
||||||
|
var albums = _fixture.CreateMany<AlbumRequest>(10);
|
||||||
|
var chilRequests = _fixture.CreateMany<ChildRequests>(10);
|
||||||
|
|
||||||
|
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<IMusicRequestRepository, IQueryable<AlbumRequest>>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<ITvRequestRepository, IQueryable<ChildRequests>>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
var result = await _subject.GetRecentlyRequested(CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.That(result.Count, Is.EqualTo(21));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetRecentlyRequested_HideUsernames()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<CustomizationSettings>, Task<CustomizationSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new CustomizationSettings());
|
||||||
|
_mocker.Setup<ISettingsService<OmbiSettings>, Task<OmbiSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new OmbiSettings { HideRequestsUsers = true });
|
||||||
|
var releaseDate = new DateTime(2019, 01, 01);
|
||||||
|
var requestDate = DateTime.Now;
|
||||||
|
|
||||||
|
var movies = _fixture.CreateMany<MovieRequests>(10);
|
||||||
|
var albums = _fixture.CreateMany<AlbumRequest>(10);
|
||||||
|
var chilRequests = _fixture.CreateMany<ChildRequests>(10);
|
||||||
|
|
||||||
|
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<IMusicRequestRepository, IQueryable<AlbumRequest>>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<ITvRequestRepository, IQueryable<ChildRequests>>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<ICurrentUser, Task<OmbiUser>>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias", UserType = UserType.LocalUser });
|
||||||
|
_mocker.Setup<ICurrentUser, string>(x => x.Username).Returns("test");
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<bool>>(x => x.IsInRoleAsync(It.IsAny<OmbiUser>(), It.IsAny<string>())).ReturnsAsync(false);
|
||||||
|
|
||||||
|
var result = await _subject.GetRecentlyRequested(CancellationToken.None);
|
||||||
|
|
||||||
|
CollectionAssert.IsEmpty(result.Where(x => !string.IsNullOrEmpty(x.Username) && !string.IsNullOrEmpty(x.UserId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -111,11 +111,11 @@ namespace Ombi.Core.Engine
|
||||||
if (model.Is4kRequest)
|
if (model.Is4kRequest)
|
||||||
{
|
{
|
||||||
existingRequest.Is4kRequest = true;
|
existingRequest.Is4kRequest = true;
|
||||||
existingRequest.RequestedDate4k = DateTime.Now;
|
existingRequest.RequestedDate4k = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
existingRequest.RequestedDate = DateTime.Now;
|
existingRequest.RequestedDate = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
isExisting = true;
|
isExisting = true;
|
||||||
requestModel = existingRequest;
|
requestModel = existingRequest;
|
||||||
|
@ -134,7 +134,7 @@ namespace Ombi.Core.Engine
|
||||||
? DateTime.Parse(movieInfo.ReleaseDate)
|
? DateTime.Parse(movieInfo.ReleaseDate)
|
||||||
: DateTime.MinValue,
|
: DateTime.MinValue,
|
||||||
Status = movieInfo.Status,
|
Status = movieInfo.Status,
|
||||||
RequestedDate = model.Is4kRequest ? DateTime.MinValue : DateTime.Now,
|
RequestedDate = model.Is4kRequest ? DateTime.MinValue : DateTime.UtcNow,
|
||||||
Approved = false,
|
Approved = false,
|
||||||
Approved4K = false,
|
Approved4K = false,
|
||||||
RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id,
|
RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id,
|
||||||
|
@ -143,7 +143,7 @@ namespace Ombi.Core.Engine
|
||||||
RequestedByAlias = model.RequestedByAlias,
|
RequestedByAlias = model.RequestedByAlias,
|
||||||
RootPathOverride = model.RootFolderOverride.GetValueOrDefault(),
|
RootPathOverride = model.RootFolderOverride.GetValueOrDefault(),
|
||||||
QualityOverride = model.QualityPathOverride.GetValueOrDefault(),
|
QualityOverride = model.QualityPathOverride.GetValueOrDefault(),
|
||||||
RequestedDate4k = model.Is4kRequest ? DateTime.Now : DateTime.MinValue,
|
RequestedDate4k = model.Is4kRequest ? DateTime.UtcNow : DateTime.MinValue,
|
||||||
Is4kRequest = model.Is4kRequest,
|
Is4kRequest = model.Is4kRequest,
|
||||||
Source = model.Source
|
Source = model.Source
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace Ombi.Core.Engine
|
||||||
private readonly IMusicRequestRepository _musicRepository;
|
private readonly IMusicRequestRepository _musicRepository;
|
||||||
private readonly IRepository<Votes> _voteRepository;
|
private readonly IRepository<Votes> _voteRepository;
|
||||||
private readonly IRepository<MobileDevices> _mobileDevicesRepository;
|
private readonly IRepository<MobileDevices> _mobileDevicesRepository;
|
||||||
|
private readonly IRepository<PlexWatchlistUserError> _watchlistUserError;
|
||||||
|
|
||||||
public UserDeletionEngine(IMovieRequestRepository movieRepository,
|
public UserDeletionEngine(IMovieRequestRepository movieRepository,
|
||||||
OmbiUserManager userManager,
|
OmbiUserManager userManager,
|
||||||
|
@ -39,7 +40,8 @@ namespace Ombi.Core.Engine
|
||||||
IRepository<UserNotificationPreferences> notificationPreferencesRepo,
|
IRepository<UserNotificationPreferences> notificationPreferencesRepo,
|
||||||
IRepository<UserQualityProfiles> qualityProfilesRepo,
|
IRepository<UserQualityProfiles> qualityProfilesRepo,
|
||||||
IRepository<Votes> voteRepository,
|
IRepository<Votes> voteRepository,
|
||||||
IRepository<MobileDevices> mobileDevicesRepository
|
IRepository<MobileDevices> mobileDevicesRepository,
|
||||||
|
IRepository<PlexWatchlistUserError> watchlistUserError
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_movieRepository = movieRepository;
|
_movieRepository = movieRepository;
|
||||||
|
@ -56,6 +58,7 @@ namespace Ombi.Core.Engine
|
||||||
_userQualityProfiles = qualityProfilesRepo;
|
_userQualityProfiles = qualityProfilesRepo;
|
||||||
_voteRepository = voteRepository;
|
_voteRepository = voteRepository;
|
||||||
_mobileDevicesRepository = mobileDevicesRepository;
|
_mobileDevicesRepository = mobileDevicesRepository;
|
||||||
|
_watchlistUserError = watchlistUserError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,6 +71,7 @@ namespace Ombi.Core.Engine
|
||||||
var musicRequested = _musicRepository.GetAll().Where(x => x.RequestedUserId == userId);
|
var musicRequested = _musicRepository.GetAll().Where(x => x.RequestedUserId == userId);
|
||||||
var notificationPreferences = _userNotificationPreferences.GetAll().Where(x => x.UserId == userId);
|
var notificationPreferences = _userNotificationPreferences.GetAll().Where(x => x.UserId == userId);
|
||||||
var userQuality = await _userQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == userId);
|
var userQuality = await _userQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == userId);
|
||||||
|
var watchlistError = await _watchlistUserError.GetAll().FirstOrDefaultAsync(x => x.UserId == userId);
|
||||||
|
|
||||||
if (moviesUserRequested.Any())
|
if (moviesUserRequested.Any())
|
||||||
{
|
{
|
||||||
|
@ -89,6 +93,10 @@ namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
await _userQualityProfiles.Delete(userQuality);
|
await _userQualityProfiles.Delete(userQuality);
|
||||||
}
|
}
|
||||||
|
if (watchlistError != null)
|
||||||
|
{
|
||||||
|
await _watchlistUserError.Delete(watchlistError);
|
||||||
|
}
|
||||||
|
|
||||||
// Delete any issues and request logs
|
// Delete any issues and request logs
|
||||||
var issues = _issuesRepository.GetAll().Where(x => x.UserReportedId == userId);
|
var issues = _issuesRepository.GetAll().Where(x => x.UserReportedId == userId);
|
||||||
|
|
|
@ -25,28 +25,29 @@ namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
// get all movie requests
|
// get all movie requests
|
||||||
var movies = _movieRequest.GetWithUser();
|
var movies = _movieRequest.GetWithUser();
|
||||||
var filteredMovies = movies.Where(x => x.RequestedDate >= request.From && x.RequestedDate <= request.To);
|
var filteredMovies = await movies.Where(x => x.RequestedDate >= request.From && x.RequestedDate <= request.To).ToListAsync();
|
||||||
var tv = _tvRequest.GetLite();
|
var tv = _tvRequest.GetLite();
|
||||||
var children = tv.SelectMany(x =>
|
var children = await tv.SelectMany(x =>
|
||||||
x.ChildRequests.Where(c => c.RequestedDate >= request.From && c.RequestedDate <= request.To));
|
x.ChildRequests.Where(c => c.RequestedDate >= request.From && c.RequestedDate <= request.To)).ToListAsync();
|
||||||
|
|
||||||
|
var userMovie = filteredMovies.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefault();
|
||||||
|
var userTv = children.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefault();
|
||||||
|
|
||||||
var userMovie = filteredMovies.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
|
|
||||||
var userTv = children.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
|
|
||||||
|
|
||||||
var moviesCount = filteredMovies.CountAsync();
|
var moviesCount = filteredMovies.Count;
|
||||||
var childrenCount = children.CountAsync();
|
var childrenCount = children.Count;
|
||||||
var availableMovies =
|
var availableMovies =
|
||||||
filteredMovies.Select(x => x.MarkedAsAvailable >= request.From && x.MarkedAsAvailable <= request.To).CountAsync();
|
filteredMovies.Select(x => x.MarkedAsAvailable >= request.From && x.MarkedAsAvailable <= request.To).Count();
|
||||||
var availableChildren = children.Where(c => c.MarkedAsAvailable >= request.From && c.MarkedAsAvailable <= request.To).CountAsync();
|
var availableChildren = children.Where(c => c.MarkedAsAvailable >= request.From && c.MarkedAsAvailable <= request.To).Count();
|
||||||
|
|
||||||
return new UserStatsSummary
|
return new UserStatsSummary
|
||||||
{
|
{
|
||||||
TotalMovieRequests = await moviesCount,
|
TotalMovieRequests = moviesCount,
|
||||||
TotalTvRequests = await childrenCount,
|
TotalTvRequests = childrenCount,
|
||||||
CompletedRequestsTv = await availableChildren,
|
CompletedRequestsTv = availableChildren,
|
||||||
CompletedRequestsMovies = await availableMovies,
|
CompletedRequestsMovies = availableMovies,
|
||||||
MostRequestedUserMovie = (await userMovie).FirstOrDefault()?.RequestedUser ?? new OmbiUser(),
|
MostRequestedUserMovie = userMovie.FirstOrDefault()?.RequestedUser ?? new OmbiUser(),
|
||||||
MostRequestedUserTv = (await userTv).FirstOrDefault()?.RequestedUser ?? new OmbiUser(),
|
MostRequestedUserTv = userTv.FirstOrDefault()?.RequestedUser ?? new OmbiUser(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ombi.Core
|
namespace Ombi.Core
|
||||||
{
|
{
|
||||||
public interface IImageService
|
public interface IImageService
|
||||||
{
|
{
|
||||||
Task<string> GetTvBackground(string tvdbId);
|
Task<string> GetTvBackground(string tvdbId);
|
||||||
|
Task<string> GetTmdbTvBackground(string id, CancellationToken token);
|
||||||
|
Task<string> GetTmdbTvPoster(string tmdbId, CancellationToken token);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ombi.Api.FanartTv;
|
using Ombi.Api.FanartTv;
|
||||||
|
using Ombi.Api.TheMovieDb;
|
||||||
|
using Ombi.Core.Helpers;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
|
||||||
namespace Ombi.Core
|
namespace Ombi.Core
|
||||||
|
@ -12,13 +17,19 @@ namespace Ombi.Core
|
||||||
private readonly IApplicationConfigRepository _configRepository;
|
private readonly IApplicationConfigRepository _configRepository;
|
||||||
private readonly IFanartTvApi _fanartTvApi;
|
private readonly IFanartTvApi _fanartTvApi;
|
||||||
private readonly ICacheService _cache;
|
private readonly ICacheService _cache;
|
||||||
|
private readonly IMovieDbApi _movieDbApi;
|
||||||
|
private readonly ICurrentUser _user;
|
||||||
|
private readonly ISettingsService<OmbiSettings> _ombiSettings;
|
||||||
|
|
||||||
public ImageService(IApplicationConfigRepository configRepository, IFanartTvApi fanartTvApi,
|
public ImageService(IApplicationConfigRepository configRepository, IFanartTvApi fanartTvApi,
|
||||||
ICacheService cache)
|
ICacheService cache, IMovieDbApi movieDbApi, ICurrentUser user, ISettingsService<OmbiSettings> ombiSettings)
|
||||||
{
|
{
|
||||||
_configRepository = configRepository;
|
_configRepository = configRepository;
|
||||||
_fanartTvApi = fanartTvApi;
|
_fanartTvApi = fanartTvApi;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
|
_movieDbApi = movieDbApi;
|
||||||
|
_user = user;
|
||||||
|
_ombiSettings = ombiSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetTvBackground(string tvdbId)
|
public async Task<string> GetTvBackground(string tvdbId)
|
||||||
|
@ -43,5 +54,69 @@ namespace Ombi.Core
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetTmdbTvBackground(string id, CancellationToken token)
|
||||||
|
{
|
||||||
|
var images = await _cache.GetOrAddAsync($"{CacheKeys.TmdbImages}tv{id}", () => _movieDbApi.GetTvImages(id, token), DateTimeOffset.Now.AddDays(1));
|
||||||
|
|
||||||
|
if (images?.backdrops?.Any() ?? false)
|
||||||
|
{
|
||||||
|
return images.backdrops.Select(x => x.file_path).FirstOrDefault();
|
||||||
|
}
|
||||||
|
if (images?.posters?.Any() ?? false)
|
||||||
|
{
|
||||||
|
return images.posters.Select(x => x.file_path).FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetTmdbTvPoster(string tmdbId, CancellationToken token)
|
||||||
|
{
|
||||||
|
var images = await _cache.GetOrAddAsync($"{CacheKeys.TmdbImages}tv{tmdbId}", () => _movieDbApi.GetTvImages(tmdbId, token), DateTimeOffset.Now.AddDays(1));
|
||||||
|
|
||||||
|
if (images?.posters?.Any() ?? false)
|
||||||
|
{
|
||||||
|
var lang = await DefaultLanguageCode();
|
||||||
|
var langImage = images.posters.Where(x => lang.Equals(x.iso_639_1, StringComparison.InvariantCultureIgnoreCase)).OrderByDescending(x => x.vote_count);
|
||||||
|
if (langImage.Any())
|
||||||
|
{
|
||||||
|
return langImage.Select(x => x.file_path).First();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return images.posters.Select(x => x.file_path).First();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (images?.backdrops?.Any() ?? false)
|
||||||
|
{
|
||||||
|
return images.backdrops.Select(x => x.file_path).FirstOrDefault();
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<string> DefaultLanguageCode()
|
||||||
|
{
|
||||||
|
var user = await _user.GetUser();
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(user.Language))
|
||||||
|
{
|
||||||
|
var s = await GetOmbiSettings();
|
||||||
|
return s.DefaultLanguageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.Language;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OmbiSettings ombiSettings;
|
||||||
|
protected async Task<OmbiSettings> GetOmbiSettings()
|
||||||
|
{
|
||||||
|
return ombiSettings ?? (ombiSettings = await _ombiSettings.GetSettingsAsync());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
16
src/Ombi.Core/Models/PlexUserWatchlistModel.cs
Normal file
16
src/Ombi.Core/Models/PlexUserWatchlistModel.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace Ombi.Core.Models
|
||||||
|
{
|
||||||
|
public class PlexUserWatchlistModel
|
||||||
|
{
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public WatchlistSyncStatus SyncStatus { get; set; }
|
||||||
|
public string UserName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WatchlistSyncStatus
|
||||||
|
{
|
||||||
|
Successful,
|
||||||
|
Failed,
|
||||||
|
NotEnabled
|
||||||
|
}
|
||||||
|
}
|
24
src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs
Normal file
24
src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Models.Requests
|
||||||
|
{
|
||||||
|
public class RecentlyRequestedModel
|
||||||
|
{
|
||||||
|
public int RequestId { get; set; }
|
||||||
|
public RequestType Type { get; set; }
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
public bool Available { get; set; }
|
||||||
|
public bool TvPartiallyAvailable { get; set; }
|
||||||
|
public DateTime RequestDate { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Overview { get; set; }
|
||||||
|
public DateTime ReleaseDate { get; set; }
|
||||||
|
public bool Approved { get; set; }
|
||||||
|
public string MediaId { get; set; }
|
||||||
|
|
||||||
|
public string PosterPath { get; set; }
|
||||||
|
public string Background { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -157,7 +157,6 @@ namespace Ombi.Core.Senders
|
||||||
}
|
}
|
||||||
|
|
||||||
int qualityToUse;
|
int qualityToUse;
|
||||||
var sonarrV3 = s.V3;
|
|
||||||
var languageProfileId = s.LanguageProfile;
|
var languageProfileId = s.LanguageProfile;
|
||||||
string rootFolderPath;
|
string rootFolderPath;
|
||||||
string seriesType;
|
string seriesType;
|
||||||
|
@ -265,13 +264,11 @@ namespace Ombi.Core.Senders
|
||||||
ignoreEpisodesWithFiles = false, // There shouldn't be any episodes with files, this is a new season
|
ignoreEpisodesWithFiles = false, // There shouldn't be any episodes with files, this is a new season
|
||||||
ignoreEpisodesWithoutFiles = false, // We want all missing
|
ignoreEpisodesWithoutFiles = false, // We want all missing
|
||||||
searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly.
|
searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly.
|
||||||
}
|
},
|
||||||
};
|
languageProfileId = languageProfileId
|
||||||
|
};
|
||||||
|
|
||||||
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!
|
||||||
|
|
12
src/Ombi.Core/Services/IPlexService.cs
Normal file
12
src/Ombi.Core/Services/IPlexService.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Services
|
||||||
|
{
|
||||||
|
public interface IPlexService
|
||||||
|
{
|
||||||
|
Task<List<PlexUserWatchlistModel>> GetWatchlistUsers(CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
12
src/Ombi.Core/Services/IRecentlyRequestedService.cs
Normal file
12
src/Ombi.Core/Services/IRecentlyRequestedService.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Services
|
||||||
|
{
|
||||||
|
public interface IRecentlyRequestedService
|
||||||
|
{
|
||||||
|
Task<IEnumerable<RecentlyRequestedModel>> GetRecentlyRequested(CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
55
src/Ombi.Core/Services/PlexService.cs
Normal file
55
src/Ombi.Core/Services/PlexService.cs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UserType = Ombi.Store.Entities.UserType;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Services
|
||||||
|
{
|
||||||
|
public class PlexService : IPlexService
|
||||||
|
{
|
||||||
|
private readonly IRepository<PlexWatchlistUserError> _watchlistUserErrors;
|
||||||
|
private readonly OmbiUserManager _userManager;
|
||||||
|
|
||||||
|
public PlexService(IRepository<PlexWatchlistUserError> watchlistUserErrors, OmbiUserManager userManager)
|
||||||
|
{
|
||||||
|
_watchlistUserErrors = watchlistUserErrors;
|
||||||
|
_userManager = userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<PlexUserWatchlistModel>> GetWatchlistUsers(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var plexUsers = _userManager.Users.Where(x => x.UserType == UserType.PlexUser);
|
||||||
|
var userErrors = await _watchlistUserErrors.GetAll().ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var model = new List<PlexUserWatchlistModel>();
|
||||||
|
|
||||||
|
|
||||||
|
foreach(var plexUser in plexUsers)
|
||||||
|
{
|
||||||
|
model.Add(new PlexUserWatchlistModel
|
||||||
|
{
|
||||||
|
UserId = plexUser.Id,
|
||||||
|
UserName = plexUser.UserName,
|
||||||
|
SyncStatus = GetWatchlistSyncStatus(plexUser, userErrors)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WatchlistSyncStatus GetWatchlistSyncStatus(OmbiUser user, List<PlexWatchlistUserError> userErrors)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(user.MediaServerToken))
|
||||||
|
{
|
||||||
|
return WatchlistSyncStatus.NotEnabled;
|
||||||
|
}
|
||||||
|
return userErrors.Any(x => x.UserId == user.Id) ? WatchlistSyncStatus.Failed : WatchlistSyncStatus.Successful;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
183
src/Ombi.Core/Services/RecentlyRequestedService.cs
Normal file
183
src/Ombi.Core/Services/RecentlyRequestedService.cs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Api.TheMovieDb;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Engine.Interfaces;
|
||||||
|
using Ombi.Core.Helpers;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
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.Requests;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static Ombi.Core.Engine.BaseMediaEngine;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Services
|
||||||
|
{
|
||||||
|
public class RecentlyRequestedService : BaseEngine, IRecentlyRequestedService
|
||||||
|
{
|
||||||
|
private readonly IMovieRequestRepository _movieRequestRepository;
|
||||||
|
private readonly ITvRequestRepository _tvRequestRepository;
|
||||||
|
private readonly IMusicRequestRepository _musicRequestRepository;
|
||||||
|
private readonly ISettingsService<CustomizationSettings> _customizationSettings;
|
||||||
|
private readonly ISettingsService<OmbiSettings> _ombiSettings;
|
||||||
|
private readonly IMovieDbApi _movieDbApi;
|
||||||
|
private readonly ICacheService _cache;
|
||||||
|
private const int AmountToTake = 7;
|
||||||
|
|
||||||
|
public RecentlyRequestedService(
|
||||||
|
IMovieRequestRepository movieRequestRepository,
|
||||||
|
ITvRequestRepository tvRequestRepository,
|
||||||
|
IMusicRequestRepository musicRequestRepository,
|
||||||
|
ISettingsService<CustomizationSettings> customizationSettings,
|
||||||
|
ISettingsService<OmbiSettings> ombiSettings,
|
||||||
|
ICurrentUser user,
|
||||||
|
OmbiUserManager um,
|
||||||
|
IRuleEvaluator rules,
|
||||||
|
IMovieDbApi movieDbApi,
|
||||||
|
ICacheService cache) : base(user, um, rules)
|
||||||
|
{
|
||||||
|
_movieRequestRepository = movieRequestRepository;
|
||||||
|
_tvRequestRepository = tvRequestRepository;
|
||||||
|
_musicRequestRepository = musicRequestRepository;
|
||||||
|
_customizationSettings = customizationSettings;
|
||||||
|
_ombiSettings = ombiSettings;
|
||||||
|
_movieDbApi = movieDbApi;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<RecentlyRequestedModel>> GetRecentlyRequested(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var customizationSettingsTask = _customizationSettings.GetSettingsAsync();
|
||||||
|
|
||||||
|
var recentMovieRequests = _movieRequestRepository.GetAll().Include(x => x.RequestedUser).OrderByDescending(x => x.RequestedDate).Take(AmountToTake);
|
||||||
|
var recentTvRequests = _tvRequestRepository.GetChild().Include(x => x.RequestedUser).Include(x => x.ParentRequest).OrderByDescending(x => x.RequestedDate).Take(AmountToTake);
|
||||||
|
var recentMusicRequests = _musicRequestRepository.GetAll().Include(x => x.RequestedUser).OrderByDescending(x => x.RequestedDate).Take(AmountToTake);
|
||||||
|
|
||||||
|
var settings = await customizationSettingsTask;
|
||||||
|
if (settings.HideAvailableRecentlyRequested)
|
||||||
|
{
|
||||||
|
recentMovieRequests = recentMovieRequests.Where(x => !x.Available);
|
||||||
|
recentTvRequests = recentTvRequests.Where(x => !x.Available);
|
||||||
|
recentMusicRequests = recentMusicRequests.Where(x => !x.Available);
|
||||||
|
}
|
||||||
|
var hideUsers = await HideFromOtherUsers();
|
||||||
|
|
||||||
|
var model = new List<RecentlyRequestedModel>();
|
||||||
|
|
||||||
|
var lang = await DefaultLanguageCode();
|
||||||
|
foreach (var item in await recentMovieRequests.ToListAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
var images = await _cache.GetOrAddAsync($"{CacheKeys.TmdbImages}movie{item.TheMovieDbId}", () => _movieDbApi.GetMovieImages(item.TheMovieDbId.ToString(), cancellationToken), DateTimeOffset.Now.AddDays(1));
|
||||||
|
model.Add(new RecentlyRequestedModel
|
||||||
|
{
|
||||||
|
RequestId = item.Id,
|
||||||
|
Available = item.Available,
|
||||||
|
Overview = item.Overview,
|
||||||
|
ReleaseDate = item.ReleaseDate,
|
||||||
|
RequestDate = item.RequestedDate,
|
||||||
|
Title = item.Title,
|
||||||
|
Type = RequestType.Movie,
|
||||||
|
Approved = item.Approved,
|
||||||
|
UserId = hideUsers.Hide ? string.Empty : item.RequestedUserId,
|
||||||
|
Username = hideUsers.Hide ? string.Empty : item.RequestedUser.UserAlias,
|
||||||
|
MediaId = item.TheMovieDbId.ToString(),
|
||||||
|
PosterPath = images?.posters?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(),
|
||||||
|
Background = images?.backdrops?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in await recentMusicRequests.ToListAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
model.Add(new RecentlyRequestedModel
|
||||||
|
{
|
||||||
|
RequestId = item.Id,
|
||||||
|
Available = item.Available,
|
||||||
|
Overview = item.ArtistName,
|
||||||
|
Approved = item.Approved,
|
||||||
|
ReleaseDate = item.ReleaseDate,
|
||||||
|
RequestDate = item.RequestedDate,
|
||||||
|
Title = item.Title,
|
||||||
|
Type = RequestType.Album,
|
||||||
|
UserId = hideUsers.Hide ? string.Empty : item.RequestedUserId,
|
||||||
|
Username = hideUsers.Hide ? string.Empty : item.RequestedUser.UserAlias,
|
||||||
|
MediaId = item.ForeignAlbumId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in await recentTvRequests.ToListAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
var providerId = item.ParentRequest.ExternalProviderId.ToString();
|
||||||
|
var images = await _cache.GetOrAddAsync($"{CacheKeys.TmdbImages}tv{providerId}", () => _movieDbApi.GetTvImages(providerId.ToString(), cancellationToken), DateTimeOffset.Now.AddDays(1));
|
||||||
|
|
||||||
|
var partialAvailability = item.SeasonRequests.SelectMany(x => x.Episodes).Any(e => e.Available);
|
||||||
|
model.Add(new RecentlyRequestedModel
|
||||||
|
{
|
||||||
|
RequestId = item.Id,
|
||||||
|
Available = item.Available,
|
||||||
|
Overview = item.ParentRequest.Overview,
|
||||||
|
ReleaseDate = item.ParentRequest.ReleaseDate,
|
||||||
|
Approved = item.Approved,
|
||||||
|
RequestDate = item.RequestedDate,
|
||||||
|
TvPartiallyAvailable = partialAvailability,
|
||||||
|
Title = item.ParentRequest.Title,
|
||||||
|
Type = RequestType.TvShow,
|
||||||
|
UserId = hideUsers.Hide ? string.Empty : item.RequestedUserId,
|
||||||
|
Username = hideUsers.Hide ? string.Empty : item.RequestedUser.UserAlias,
|
||||||
|
MediaId = providerId.ToString(),
|
||||||
|
PosterPath = images?.posters?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(),
|
||||||
|
Background = images?.backdrops?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.OrderByDescending(x => x.RequestDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<HideResult> HideFromOtherUsers()
|
||||||
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
if (await IsInRole(OmbiRoles.Admin) || await IsInRole(OmbiRoles.PowerUser) || user.IsSystemUser)
|
||||||
|
{
|
||||||
|
return new HideResult
|
||||||
|
{
|
||||||
|
UserId = user.Id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var settings = await GetOmbiSettings();
|
||||||
|
var result = new HideResult
|
||||||
|
{
|
||||||
|
Hide = settings.HideRequestsUsers,
|
||||||
|
UserId = user.Id
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
protected async Task<string> DefaultLanguageCode()
|
||||||
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(user.Language))
|
||||||
|
{
|
||||||
|
var s = await GetOmbiSettings();
|
||||||
|
return s.DefaultLanguageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.Language;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private OmbiSettings ombiSettings;
|
||||||
|
protected async Task<OmbiSettings> GetOmbiSettings()
|
||||||
|
{
|
||||||
|
return ombiSettings ??= await _ombiSettings.GetSettingsAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -228,6 +228,8 @@ namespace Ombi.DependencyInjection
|
||||||
services.AddTransient<ILegacyMobileNotification, LegacyMobileNotification>();
|
services.AddTransient<ILegacyMobileNotification, LegacyMobileNotification>();
|
||||||
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();
|
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();
|
||||||
services.AddScoped<IFeatureService, FeatureService>();
|
services.AddScoped<IFeatureService, FeatureService>();
|
||||||
|
services.AddTransient<IRecentlyRequestedService, RecentlyRequestedService>();
|
||||||
|
services.AddTransient<IPlexService, PlexService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RegisterJobs(this IServiceCollection services)
|
public static void RegisterJobs(this IServiceCollection services)
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace Ombi.Helpers
|
||||||
public const string LidarrRootFolders = nameof(LidarrRootFolders);
|
public const string LidarrRootFolders = nameof(LidarrRootFolders);
|
||||||
public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles);
|
public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles);
|
||||||
public const string FanartTv = nameof(FanartTv);
|
public const string FanartTv = nameof(FanartTv);
|
||||||
|
public const string TmdbImages = nameof(TmdbImages);
|
||||||
public const string UsersDropdown = nameof(UsersDropdown);
|
public const string UsersDropdown = nameof(UsersDropdown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace Ombi.Helpers
|
||||||
}
|
}
|
||||||
public class MediaCacheService : CacheService, IMediaCacheService
|
public class MediaCacheService : CacheService, IMediaCacheService
|
||||||
{
|
{
|
||||||
private const string CacheKey = "MediaCacheServiceKeys";
|
private const string _cacheKey = "MediaCacheServiceKeys";
|
||||||
|
|
||||||
public MediaCacheService(IMemoryCache memoryCache) : base(memoryCache)
|
public MediaCacheService(IMemoryCache memoryCache) : base(memoryCache)
|
||||||
{
|
{
|
||||||
|
@ -43,19 +43,19 @@ namespace Ombi.Helpers
|
||||||
|
|
||||||
private void UpdateLocalCache(string cacheKey)
|
private void UpdateLocalCache(string cacheKey)
|
||||||
{
|
{
|
||||||
var mediaServiceCache = _memoryCache.Get<List<string>>(CacheKey);
|
var mediaServiceCache = _memoryCache.Get<List<string>>(_cacheKey);
|
||||||
if (mediaServiceCache == null)
|
if (mediaServiceCache == null)
|
||||||
{
|
{
|
||||||
mediaServiceCache = new List<string>();
|
mediaServiceCache = new List<string>();
|
||||||
}
|
}
|
||||||
mediaServiceCache.Add(cacheKey);
|
mediaServiceCache.Add(cacheKey);
|
||||||
_memoryCache.Remove(CacheKey);
|
_memoryCache.Remove(_cacheKey);
|
||||||
_memoryCache.Set(CacheKey, mediaServiceCache);
|
_memoryCache.Set(_cacheKey, mediaServiceCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Purge()
|
public Task Purge()
|
||||||
{
|
{
|
||||||
var keys = _memoryCache.Get<List<string>>(CacheKey);
|
var keys = _memoryCache.Get<List<string>>(_cacheKey);
|
||||||
if (keys == null)
|
if (keys == null)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
|
@ -24,7 +24,11 @@ namespace Ombi.Hubs
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return UsersOnline.Where(x => x.Value.Roles.Contains(OmbiRoles.Admin)).Select(x => x.Key).ToList();
|
if (UsersOnline.Any())
|
||||||
|
{
|
||||||
|
return UsersOnline.Where(x => x.Value.Roles.Contains(OmbiRoles.Admin)).Select(x => x.Key).ToList();
|
||||||
|
}
|
||||||
|
return Enumerable.Empty<string>().ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,13 +118,13 @@
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="NewAlbums" xml:space="preserve">
|
<data name="NewAlbums" xml:space="preserve">
|
||||||
<value>New Albums</value>
|
<value>Nový album</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NewMovies" xml:space="preserve">
|
<data name="NewMovies" xml:space="preserve">
|
||||||
<value>New Movies</value>
|
<value>Nové filmy</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NewTV" xml:space="preserve">
|
<data name="NewTV" xml:space="preserve">
|
||||||
<value>New TV</value>
|
<value>Nové seriály</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GenresLabel" xml:space="preserve">
|
<data name="GenresLabel" xml:space="preserve">
|
||||||
<value>Žánre:</value>
|
<value>Žánre:</value>
|
||||||
|
@ -139,18 +139,18 @@
|
||||||
<value>Epizódy:</value>
|
<value>Epizódy:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PoweredBy" xml:space="preserve">
|
<data name="PoweredBy" xml:space="preserve">
|
||||||
<value>Powered by</value>
|
<value>Beží na</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Unsubscribe" xml:space="preserve">
|
<data name="Unsubscribe" xml:space="preserve">
|
||||||
<value>Unsubscribe</value>
|
<value>Zrušiť odber</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Album" xml:space="preserve">
|
<data name="Album" xml:space="preserve">
|
||||||
<value>Album</value>
|
<value>Album</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Movie" xml:space="preserve">
|
<data name="Movie" xml:space="preserve">
|
||||||
<value>Movie</value>
|
<value>Film</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TvShow" xml:space="preserve">
|
<data name="TvShow" xml:space="preserve">
|
||||||
<value>TV Show</value>
|
<value>Seriál</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -292,6 +292,8 @@ namespace Ombi.Notifications.Tests
|
||||||
|
|
||||||
notificationOptions.Substitutes.Add("Season", "1");
|
notificationOptions.Substitutes.Add("Season", "1");
|
||||||
notificationOptions.Substitutes.Add("Episodes", "1, 2");
|
notificationOptions.Substitutes.Add("Episodes", "1, 2");
|
||||||
|
notificationOptions.Substitutes.Add("EpisodesCount", "2");
|
||||||
|
notificationOptions.Substitutes.Add("SeasonEpisodes", "1x1, 1x2");
|
||||||
var req = F.Build<ChildRequests>()
|
var req = F.Build<ChildRequests>()
|
||||||
.With(x => x.RequestType, RequestType.TvShow)
|
.With(x => x.RequestType, RequestType.TvShow)
|
||||||
.With(x => x.Available, true)
|
.With(x => x.Available, true)
|
||||||
|
@ -324,6 +326,8 @@ namespace Ombi.Notifications.Tests
|
||||||
Assert.That("name", Is.EqualTo(sut.ApplicationName));
|
Assert.That("name", Is.EqualTo(sut.ApplicationName));
|
||||||
Assert.That(sut.PartiallyAvailableEpisodeNumbers, Is.EqualTo("1, 2"));
|
Assert.That(sut.PartiallyAvailableEpisodeNumbers, Is.EqualTo("1, 2"));
|
||||||
Assert.That(sut.PartiallyAvailableSeasonNumber, Is.EqualTo("1"));
|
Assert.That(sut.PartiallyAvailableSeasonNumber, Is.EqualTo("1"));
|
||||||
|
Assert.That(sut.PartiallyAvailableEpisodeCount, Is.EqualTo("2"));
|
||||||
|
Assert.That(sut.PartiallyAvailableEpisodesList, Is.EqualTo("1x1, 1x2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
@ -186,6 +186,14 @@ namespace Ombi.Notifications
|
||||||
{
|
{
|
||||||
PartiallyAvailableEpisodeNumbers = epNumber;
|
PartiallyAvailableEpisodeNumbers = epNumber;
|
||||||
}
|
}
|
||||||
|
if (opts.Substitutes.TryGetValue("EpisodesCount", out var epCount))
|
||||||
|
{
|
||||||
|
PartiallyAvailableEpisodeCount = epCount;
|
||||||
|
}
|
||||||
|
if (opts.Substitutes.TryGetValue("SeasonEpisodes", out var sEpisodes))
|
||||||
|
{
|
||||||
|
PartiallyAvailableEpisodesList = sEpisodes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,6 +303,8 @@ namespace Ombi.Notifications
|
||||||
public string ProviderId { get; set; }
|
public string ProviderId { get; set; }
|
||||||
public string PartiallyAvailableEpisodeNumbers { get; set; }
|
public string PartiallyAvailableEpisodeNumbers { get; set; }
|
||||||
public string PartiallyAvailableSeasonNumber { get; set; }
|
public string PartiallyAvailableSeasonNumber { get; set; }
|
||||||
|
public string PartiallyAvailableEpisodeCount { get; set; }
|
||||||
|
public string PartiallyAvailableEpisodesList { get; set; }
|
||||||
|
|
||||||
// System Defined
|
// System Defined
|
||||||
private string LongDate => DateTime.Now.ToString("D");
|
private string LongDate => DateTime.Now.ToString("D");
|
||||||
|
@ -336,6 +346,8 @@ namespace Ombi.Notifications
|
||||||
{ nameof(ProviderId), ProviderId },
|
{ nameof(ProviderId), ProviderId },
|
||||||
{ nameof(PartiallyAvailableEpisodeNumbers), PartiallyAvailableEpisodeNumbers },
|
{ nameof(PartiallyAvailableEpisodeNumbers), PartiallyAvailableEpisodeNumbers },
|
||||||
{ nameof(PartiallyAvailableSeasonNumber), PartiallyAvailableSeasonNumber },
|
{ nameof(PartiallyAvailableSeasonNumber), PartiallyAvailableSeasonNumber },
|
||||||
|
{ nameof(PartiallyAvailableEpisodesList), PartiallyAvailableEpisodesList },
|
||||||
|
{ nameof(PartiallyAvailableEpisodeCount), PartiallyAvailableEpisodeCount },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
192
src/Ombi.Schedule.Tests/AvailabilityCheckerTests.cs
Normal file
192
src/Ombi.Schedule.Tests/AvailabilityCheckerTests.cs
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MockQueryable.Moq;
|
||||||
|
using Moq;
|
||||||
|
using Moq.AutoMock;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Ombi.Core;
|
||||||
|
using Ombi.Hubs;
|
||||||
|
using Ombi.Notifications.Models;
|
||||||
|
using Ombi.Schedule.Jobs;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Entities.Requests;
|
||||||
|
using Ombi.Store.Repository.Requests;
|
||||||
|
using Ombi.Tests;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Schedule.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class AvailabilityCheckerTests
|
||||||
|
{
|
||||||
|
private AutoMocker _mocker;
|
||||||
|
private TestAvailabilityChecker _subject;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
_mocker = new AutoMocker();
|
||||||
|
var hub = SignalRHelper.MockHub<NotificationHub>();
|
||||||
|
_mocker.Use(hub);
|
||||||
|
|
||||||
|
_subject = _mocker.CreateInstance<TestAvailabilityChecker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task All_Episodes_Are_Available_In_Request()
|
||||||
|
{
|
||||||
|
var request = new ChildRequests
|
||||||
|
{
|
||||||
|
Title = "Test",
|
||||||
|
Id = 1,
|
||||||
|
RequestedUser = new OmbiUser { Email = "" },
|
||||||
|
SeasonRequests = new List<SeasonRequests>
|
||||||
|
{
|
||||||
|
new SeasonRequests
|
||||||
|
{
|
||||||
|
Episodes = new List<EpisodeRequests>
|
||||||
|
{
|
||||||
|
new EpisodeRequests
|
||||||
|
{
|
||||||
|
Available = false,
|
||||||
|
EpisodeNumber = 1,
|
||||||
|
Season = new SeasonRequests
|
||||||
|
{
|
||||||
|
SeasonNumber = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new EpisodeRequests
|
||||||
|
{
|
||||||
|
Available = false,
|
||||||
|
EpisodeNumber = 2,
|
||||||
|
Season = new SeasonRequests
|
||||||
|
{
|
||||||
|
SeasonNumber = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var databaseEpisodes = new List<IBaseMediaServerEpisode>
|
||||||
|
{
|
||||||
|
new PlexEpisode
|
||||||
|
{
|
||||||
|
EpisodeNumber = 1,
|
||||||
|
SeasonNumber = 1,
|
||||||
|
},
|
||||||
|
new PlexEpisode
|
||||||
|
{
|
||||||
|
EpisodeNumber = 2,
|
||||||
|
SeasonNumber = 1,
|
||||||
|
},
|
||||||
|
}.AsQueryable().BuildMock().Object;
|
||||||
|
|
||||||
|
await _subject.ProcessTvShow(databaseEpisodes, request);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(request.Available, Is.True);
|
||||||
|
Assert.That(request.MarkedAsAvailable, Is.Not.Null);
|
||||||
|
Assert.That(request.SeasonRequests[0].Episodes[0].Available, Is.True);
|
||||||
|
Assert.That(request.SeasonRequests[0].Episodes[1].Available, Is.True);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
_mocker.Verify<ITvRequestRepository>(x => x.Save(), Times.Exactly(2));
|
||||||
|
_mocker.Verify<INotificationHelper>(x => x.Notify(It.Is<NotificationOptions>(x => x.NotificationType == Helpers.NotificationType.RequestAvailable && x.RequestId == 1)), Times.Once);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task All_One_Episode_Is_Available_In_Request()
|
||||||
|
{
|
||||||
|
var request = new ChildRequests
|
||||||
|
{
|
||||||
|
Title = "Test",
|
||||||
|
Id = 1,
|
||||||
|
RequestedUser = new OmbiUser { Email = "" },
|
||||||
|
SeasonRequests = new List<SeasonRequests>
|
||||||
|
{
|
||||||
|
new SeasonRequests
|
||||||
|
{
|
||||||
|
Episodes = new List<EpisodeRequests>
|
||||||
|
{
|
||||||
|
new EpisodeRequests
|
||||||
|
{
|
||||||
|
Available = false,
|
||||||
|
EpisodeNumber = 1,
|
||||||
|
Season = new SeasonRequests
|
||||||
|
{
|
||||||
|
SeasonNumber = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new EpisodeRequests
|
||||||
|
{
|
||||||
|
Available = false,
|
||||||
|
EpisodeNumber = 2,
|
||||||
|
Season = new SeasonRequests
|
||||||
|
{
|
||||||
|
SeasonNumber = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new EpisodeRequests
|
||||||
|
{
|
||||||
|
Available = true,
|
||||||
|
EpisodeNumber = 3,
|
||||||
|
Season = new SeasonRequests
|
||||||
|
{
|
||||||
|
SeasonNumber = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var databaseEpisodes = new List<IBaseMediaServerEpisode>
|
||||||
|
{
|
||||||
|
new PlexEpisode
|
||||||
|
{
|
||||||
|
EpisodeNumber = 1,
|
||||||
|
SeasonNumber = 1,
|
||||||
|
},
|
||||||
|
new PlexEpisode
|
||||||
|
{
|
||||||
|
EpisodeNumber = 3,
|
||||||
|
SeasonNumber = 1,
|
||||||
|
},
|
||||||
|
}.AsQueryable().BuildMock().Object;
|
||||||
|
|
||||||
|
await _subject.ProcessTvShow(databaseEpisodes, request);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(request.Available, Is.False);
|
||||||
|
Assert.That(request.MarkedAsAvailable, Is.Null);
|
||||||
|
Assert.That(request.SeasonRequests[0].Episodes[0].Available, Is.True);
|
||||||
|
Assert.That(request.SeasonRequests[0].Episodes[1].Available, Is.False);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
_mocker.Verify<ITvRequestRepository>(x => x.Save(), Times.Once);
|
||||||
|
_mocker.Verify<INotificationHelper>(x => x.Notify(It.Is<NotificationOptions>(x => x.NotificationType == Helpers.NotificationType.PartiallyAvailable && x.RequestId == 1)), Times.Once);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class TestAvailabilityChecker : AvailabilityChecker
|
||||||
|
{
|
||||||
|
public TestAvailabilityChecker(ITvRequestRepository tvRequest, INotificationHelper notification, ILogger log, IHubContext<NotificationHub> hub) : base(tvRequest, notification, log, hub)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public new Task ProcessTvShow(IQueryable<IBaseMediaServerEpisode> seriesEpisodes, ChildRequests child) => base.ProcessTvShow(seriesEpisodes, child);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ombi.Schedule\Ombi.Schedule.csproj" />
|
<ProjectReference Include="..\Ombi.Schedule\Ombi.Schedule.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Test.Common\Ombi.Test.Common.csproj" />
|
<ProjectReference Include="..\Ombi.Test.Common\Ombi.Test.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Ombi.Tests\Ombi.Tests.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -19,49 +19,140 @@ using Ombi.Store.Repository;
|
||||||
using Ombi.Store.Repository.Requests;
|
using Ombi.Store.Repository.Requests;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Core.Services;
|
using Ombi.Core.Services;
|
||||||
|
using Ombi.Tests;
|
||||||
|
using Moq.AutoMock;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Notifications.Models;
|
||||||
|
|
||||||
namespace Ombi.Schedule.Tests
|
namespace Ombi.Schedule.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
[Ignore("Need to work out how to mockout the hub context")]
|
|
||||||
public class PlexAvailabilityCheckerTests
|
public class PlexAvailabilityCheckerTests
|
||||||
{
|
{
|
||||||
|
private AutoMocker _mocker;
|
||||||
|
private PlexAvailabilityChecker _subject;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_repo = new Mock<IPlexContentRepository>();
|
_mocker = new AutoMocker();
|
||||||
_tv = new Mock<ITvRequestRepository>();
|
|
||||||
_movie = new Mock<IMovieRequestRepository>();
|
var hub = SignalRHelper.MockHub<NotificationHub>();
|
||||||
_notify = new Mock<INotificationHelper>();
|
_mocker.Use(hub);
|
||||||
var hub = new Mock<IHubContext<NotificationHub>>();
|
|
||||||
hub.Setup(x =>
|
_subject = _mocker.CreateInstance<PlexAvailabilityChecker>();
|
||||||
x.Clients.Clients(It.IsAny<IReadOnlyList<string>>()).SendCoreAsync(It.IsAny<string>(), It.IsAny<object[]>(), It.IsAny<CancellationToken>()));
|
|
||||||
NotificationHub.UsersOnline.TryAdd("A", new HubUsers());
|
|
||||||
Checker = new PlexAvailabilityChecker(_repo.Object, _tv.Object, _movie.Object, _notify.Object, null, hub.Object, Mock.Of<IFeatureService>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Mock<IPlexContentRepository> _repo;
|
|
||||||
private Mock<ITvRequestRepository> _tv;
|
|
||||||
private Mock<IMovieRequestRepository> _movie;
|
|
||||||
private Mock<INotificationHelper> _notify;
|
|
||||||
private PlexAvailabilityChecker Checker;
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task ProcessMovies_ShouldMarkAvailable_WhenInPlex()
|
public async Task ProcessMovies_ShouldMarkAvailable_WhenInPlex_WithImdbId()
|
||||||
{
|
{
|
||||||
var request = new MovieRequests
|
var request = new MovieRequests
|
||||||
{
|
{
|
||||||
ImdbId = "test"
|
ImdbId = "test"
|
||||||
};
|
};
|
||||||
_movie.Setup(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
|
_mocker.Setup<IMovieRequestRepository>(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
|
||||||
_repo.Setup(x => x.Get("test", ProviderType.ImdbId)).ReturnsAsync(new PlexServerContent());
|
_mocker.Setup<IPlexContentRepository, Task<PlexServerContent>>(x => x.Get("test", ProviderType.ImdbId)).ReturnsAsync(new PlexServerContent());
|
||||||
|
|
||||||
await Checker.Execute(null);
|
await _subject.Execute(null);
|
||||||
|
|
||||||
_movie.Verify(x => x.Save(), Times.Once);
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(request.Available, Is.True);
|
||||||
|
Assert.That(request.MarkedAsAvailable, Is.Not.Null);
|
||||||
|
Assert.That(request.Available4K, Is.False);
|
||||||
|
Assert.That(request.MarkedAsAvailable4K, Is.Null);
|
||||||
|
});
|
||||||
|
|
||||||
Assert.True(request.Available);
|
_mocker.Verify<IMovieRequestRepository>(x => x.SaveChangesAsync(), Times.Once);
|
||||||
|
_mocker.Verify<IPlexContentRepository>(x => x.Get("test", ProviderType.ImdbId), Times.Once);
|
||||||
|
_mocker.Verify<IPlexContentRepository>(x => x.Get(It.IsAny<string>(), ProviderType.TheMovieDbId), Times.Never);
|
||||||
|
_mocker.Verify<INotificationHelper>(x => x.Notify(It.Is<NotificationOptions>(x => x.NotificationType == NotificationType.RequestAvailable)), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ProcessMovies_ShouldMarkAvailable_WhenInPlex_WithTheMovieDbId()
|
||||||
|
{
|
||||||
|
var request = new MovieRequests
|
||||||
|
{
|
||||||
|
ImdbId = null,
|
||||||
|
TheMovieDbId = 33
|
||||||
|
};
|
||||||
|
_mocker.Setup<IMovieRequestRepository>(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
|
||||||
|
_mocker.Setup<IPlexContentRepository, Task<PlexServerContent>>(x => x.Get(It.IsAny<string>(), ProviderType.ImdbId)).ReturnsAsync((PlexServerContent)null);
|
||||||
|
_mocker.Setup<IPlexContentRepository, Task<PlexServerContent>>(x => x.Get("33", ProviderType.TheMovieDbId)).ReturnsAsync(new PlexServerContent());
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(request.Available, Is.True);
|
||||||
|
Assert.That(request.MarkedAsAvailable, Is.Not.Null);
|
||||||
|
Assert.That(request.Available4K, Is.False);
|
||||||
|
Assert.That(request.MarkedAsAvailable4K, Is.Null);
|
||||||
|
});
|
||||||
|
|
||||||
|
_mocker.Verify<IMovieRequestRepository>(x => x.SaveChangesAsync(), Times.Once);
|
||||||
|
_mocker.Verify<IPlexContentRepository>(x => x.Get(It.IsAny<string>(), ProviderType.ImdbId), Times.Never);
|
||||||
|
_mocker.Verify<IPlexContentRepository>(x => x.Get(It.IsAny<string>(), ProviderType.TheMovieDbId), Times.Once);
|
||||||
|
_mocker.Verify<INotificationHelper>(x => x.Notify(It.Is<NotificationOptions>(x => x.NotificationType == NotificationType.RequestAvailable)), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ProcessMovies_ShouldMarkAvailable_WhenInPlex_WithTheMovieDbId_4K_Enabled ()
|
||||||
|
{
|
||||||
|
_mocker.Setup<IFeatureService, Task<bool>>(x => x.FeatureEnabled(FeatureNames.Movie4KRequests)).ReturnsAsync(true);
|
||||||
|
var request = new MovieRequests
|
||||||
|
{
|
||||||
|
ImdbId = "test"
|
||||||
|
};
|
||||||
|
_mocker.Setup<IMovieRequestRepository>(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
|
||||||
|
_mocker.Setup<IPlexContentRepository, Task<PlexServerContent>>(x => x.Get("test", ProviderType.ImdbId)).ReturnsAsync(new PlexServerContent { Quality = "1080p" });
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(request.Available, Is.True);
|
||||||
|
Assert.That(request.MarkedAsAvailable, Is.Not.Null);
|
||||||
|
Assert.That(request.Available4K, Is.False);
|
||||||
|
Assert.That(request.MarkedAsAvailable4K, Is.Null);
|
||||||
|
});
|
||||||
|
|
||||||
|
_mocker.Verify<IMovieRequestRepository>(x => x.SaveChangesAsync(), Times.Once);
|
||||||
|
_mocker.Verify<IPlexContentRepository>(x => x.Get("test", ProviderType.ImdbId), Times.Once);
|
||||||
|
_mocker.Verify<IPlexContentRepository>(x => x.Get(It.IsAny<string>(), ProviderType.TheMovieDbId), Times.Never);
|
||||||
|
_mocker.Verify<INotificationHelper>(x => x.Notify(It.Is<NotificationOptions>(x => x.NotificationType == NotificationType.RequestAvailable)), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ProcessMovies_4K_ShouldMarkAvailable_WhenInPlex_WithImdbId_And_4K_FeatureEnabled()
|
||||||
|
{
|
||||||
|
_mocker.Setup<IFeatureService, Task<bool>>(x => x.FeatureEnabled(FeatureNames.Movie4KRequests)).ReturnsAsync(true);
|
||||||
|
var request = new MovieRequests
|
||||||
|
{
|
||||||
|
ImdbId = "test",
|
||||||
|
Is4kRequest = true,
|
||||||
|
Has4KRequest = true,
|
||||||
|
};
|
||||||
|
_mocker.Setup<IMovieRequestRepository>(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
|
||||||
|
_mocker.Setup<IPlexContentRepository, Task<PlexServerContent>>(x => x.Get("test", ProviderType.ImdbId)).ReturnsAsync(new PlexServerContent { Has4K = true });
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<IMovieRequestRepository>(x => x.SaveChangesAsync(), Times.Once);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(request.Available, Is.False);
|
||||||
|
Assert.That(request.MarkedAsAvailable, Is.Null);
|
||||||
|
Assert.That(request.Available4K, Is.True);
|
||||||
|
Assert.That(request.MarkedAsAvailable4K, Is.Not.Null);
|
||||||
|
});
|
||||||
|
|
||||||
|
_mocker.Verify<IMovieRequestRepository>(x => x.SaveChangesAsync(), Times.Once);
|
||||||
|
_mocker.Verify<IPlexContentRepository>(x => x.Get("test", ProviderType.ImdbId), Times.Once);
|
||||||
|
_mocker.Verify<IPlexContentRepository>(x => x.Get(It.IsAny<string>(), ProviderType.TheMovieDbId), Times.Never);
|
||||||
|
_mocker.Verify<INotificationHelper>(x => x.Notify(It.Is<NotificationOptions>(x => x.NotificationType == NotificationType.RequestAvailable)), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -71,19 +162,96 @@ namespace Ombi.Schedule.Tests
|
||||||
{
|
{
|
||||||
ImdbId = "test"
|
ImdbId = "test"
|
||||||
};
|
};
|
||||||
_movie.Setup(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
|
_mocker.Setup<IMovieRequestRepository>(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
|
||||||
|
|
||||||
await Checker.Execute(null);
|
await _subject.Execute(null);
|
||||||
|
|
||||||
Assert.False(request.Available);
|
Assert.False(request.Available);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task ProcessTv_ShouldMark_Episode_Available_WhenInPlex()
|
public async Task ProcessTv_ShouldMark_Episode_Available_WhenInPlex_MovieDbId()
|
||||||
{
|
{
|
||||||
var request = new ChildRequests
|
var request = CreateChildRequest(null, 33, 99);
|
||||||
|
_mocker.Setup<ITvRequestRepository>(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<IPlexContentRepository, IQueryable<IMediaServerEpisode>>(x => x.GetAllEpisodes()).Returns(new List<PlexEpisode>
|
||||||
{
|
{
|
||||||
ParentRequest = new TvRequests { TvDbId = 1 },
|
new PlexEpisode
|
||||||
|
{
|
||||||
|
Series = new PlexServerContent
|
||||||
|
{
|
||||||
|
TheMovieDbId = 33.ToString(),
|
||||||
|
Title = "Test"
|
||||||
|
},
|
||||||
|
EpisodeNumber = 1,
|
||||||
|
SeasonNumber = 2,
|
||||||
|
}
|
||||||
|
}.AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<ITvRequestRepository>(x => x.Save(), Times.AtLeastOnce);
|
||||||
|
|
||||||
|
Assert.True(request.SeasonRequests[0].Episodes[0].Available);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ProcessTv_ShouldMark_Episode_Available_WhenInPlex_ImdbId()
|
||||||
|
{
|
||||||
|
var request = CreateChildRequest("abc", -1, 99);
|
||||||
|
_mocker.Setup<ITvRequestRepository>(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<IPlexContentRepository, IQueryable<IMediaServerEpisode>>(x => x.GetAllEpisodes()).Returns(new List<PlexEpisode>
|
||||||
|
{
|
||||||
|
new PlexEpisode
|
||||||
|
{
|
||||||
|
Series = new PlexServerContent
|
||||||
|
{
|
||||||
|
ImdbId = "abc",
|
||||||
|
},
|
||||||
|
EpisodeNumber = 1,
|
||||||
|
SeasonNumber = 2,
|
||||||
|
}
|
||||||
|
}.AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<ITvRequestRepository>(x => x.Save(), Times.AtLeastOnce);
|
||||||
|
|
||||||
|
Assert.True(request.SeasonRequests[0].Episodes[0].Available);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ProcessTv_ShouldMark_Episode_Available_By_TitleMatch()
|
||||||
|
{
|
||||||
|
var request = CreateChildRequest("abc", -1, 99);
|
||||||
|
_mocker.Setup<ITvRequestRepository>(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable().BuildMock().Object);
|
||||||
|
_mocker.Setup<IPlexContentRepository, IQueryable<IMediaServerEpisode>>(x => x.GetAllEpisodes()).Returns(new List<PlexEpisode>
|
||||||
|
{
|
||||||
|
new PlexEpisode
|
||||||
|
{
|
||||||
|
Series = new PlexServerContent
|
||||||
|
{
|
||||||
|
Title = "UNITTEST",
|
||||||
|
ImdbId = "invlaid",
|
||||||
|
},
|
||||||
|
EpisodeNumber = 1,
|
||||||
|
SeasonNumber = 2,
|
||||||
|
}
|
||||||
|
}.AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<ITvRequestRepository>(x => x.Save(), Times.AtLeastOnce);
|
||||||
|
|
||||||
|
Assert.True(request.SeasonRequests[0].Episodes[0].Available);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChildRequests CreateChildRequest(string imdbId, int theMovieDbId, int tvdbId)
|
||||||
|
{
|
||||||
|
return new ChildRequests
|
||||||
|
{
|
||||||
|
Title = "UnitTest",
|
||||||
|
ParentRequest = new TvRequests { ImdbId = imdbId, ExternalProviderId = theMovieDbId, TvDbId = tvdbId },
|
||||||
SeasonRequests = new EditableList<SeasonRequests>
|
SeasonRequests = new EditableList<SeasonRequests>
|
||||||
{
|
{
|
||||||
new SeasonRequests
|
new SeasonRequests
|
||||||
|
@ -93,7 +261,7 @@ namespace Ombi.Schedule.Tests
|
||||||
new EpisodeRequests
|
new EpisodeRequests
|
||||||
{
|
{
|
||||||
EpisodeNumber = 1,
|
EpisodeNumber = 1,
|
||||||
Season = new SeasonRequests
|
Season = new SeasonRequests
|
||||||
{
|
{
|
||||||
SeasonNumber = 2
|
SeasonNumber = 2
|
||||||
}
|
}
|
||||||
|
@ -106,27 +274,6 @@ namespace Ombi.Schedule.Tests
|
||||||
Email = "abc"
|
Email = "abc"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_tv.Setup(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable().BuildMock().Object);
|
|
||||||
_repo.Setup(x => x.GetAllEpisodes()).Returns(new List<PlexEpisode>
|
|
||||||
{
|
|
||||||
new PlexEpisode
|
|
||||||
{
|
|
||||||
Series = new PlexServerContent
|
|
||||||
{
|
|
||||||
TvDbId = 1.ToString(),
|
|
||||||
},
|
|
||||||
EpisodeNumber = 1,
|
|
||||||
SeasonNumber = 2
|
|
||||||
}
|
|
||||||
}.AsQueryable().BuildMock().Object);
|
|
||||||
_repo.Setup(x => x.Include(It.IsAny<IQueryable<PlexEpisode>>(),It.IsAny<Expression<Func<PlexEpisode, PlexServerContent>>>()));
|
|
||||||
|
|
||||||
await Checker.Execute(null);
|
|
||||||
|
|
||||||
_tv.Verify(x => x.Save(), Times.Once);
|
|
||||||
|
|
||||||
Assert.True(request.SeasonRequests[0].Episodes[0].Available);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
328
src/Ombi.Schedule.Tests/PlexUserImporterTests.cs
Normal file
328
src/Ombi.Schedule.Tests/PlexUserImporterTests.cs
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Moq;
|
||||||
|
using Moq.AutoMock;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Ombi.Api.Plex;
|
||||||
|
using Ombi.Api.Plex.Models;
|
||||||
|
using Ombi.Api.Plex.Models.Friends;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Core.Settings.Models.External;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Hubs;
|
||||||
|
using Ombi.Schedule.Jobs.Plex;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Test.Common;
|
||||||
|
using Ombi.Tests;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Schedule.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class PlexUserImporterTests
|
||||||
|
{
|
||||||
|
private List<OmbiUser> _users = new List<OmbiUser>
|
||||||
|
{
|
||||||
|
new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="abc", NormalizedUserName = "ABC", UserType = UserType.LocalUser},
|
||||||
|
new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="sys", NormalizedUserName = "SYS", UserType = UserType.SystemUser},
|
||||||
|
new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="plex", NormalizedUserName = "PLEX", UserType = UserType.PlexUser, ProviderUserId = "PLEX_ID", Email = "dupe"},
|
||||||
|
};
|
||||||
|
private AutoMocker _mocker;
|
||||||
|
private PlexUserImporter _subject;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
_mocker = new AutoMocker();
|
||||||
|
|
||||||
|
var um = MockHelper.MockUserManager(_users);
|
||||||
|
var hub = SignalRHelper.MockHub<NotificationHub>();
|
||||||
|
|
||||||
|
_mocker.Use(um);
|
||||||
|
_mocker.Use(hub);
|
||||||
|
|
||||||
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings
|
||||||
|
{
|
||||||
|
Enable = true,
|
||||||
|
Servers = new List<PlexServers>
|
||||||
|
{
|
||||||
|
new PlexServers { Name = "Test", MachineIdentifier = "123", PlexAuthToken = "abc" }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_subject = _mocker.CreateInstance<PlexUserImporter>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Exits_WhenNot_Enabled()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = false });
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Exits_When_Plex_Not_Enabled()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = true });
|
||||||
|
|
||||||
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = false });
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Exits_When_Plex_No_AuthToken()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = true });
|
||||||
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings
|
||||||
|
{
|
||||||
|
Enable = true,
|
||||||
|
Servers = new List<PlexServers>
|
||||||
|
{
|
||||||
|
new PlexServers { Name = "Test", MachineIdentifier = "123", PlexAuthToken = null }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Only_Imports_Plex_Admin()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = false });
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexAccount>>(x => x.GetAccount(It.IsAny<string>())).ReturnsAsync(new PlexAccount
|
||||||
|
{
|
||||||
|
user = new User
|
||||||
|
{
|
||||||
|
email = "email",
|
||||||
|
authentication_token = "user_token",
|
||||||
|
title = "user_title",
|
||||||
|
username = "user_username",
|
||||||
|
id = "user_id",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.CreateAsync(It.Is<OmbiUser>(x => x.UserName == "user_username" && x.Email == "email" && x.ProviderUserId == "user_id" && x.UserType == UserType.PlexUser)))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.AddToRoleAsync(It.Is<OmbiUser>(x => x.UserName == "user_username"), It.Is<string>(x => x == OmbiRoles.Admin)))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Only_Imports_Plex_Admin_Already_Exists()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = false });
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexAccount>>(x => x.GetAccount(It.IsAny<string>())).ReturnsAsync(new PlexAccount
|
||||||
|
{
|
||||||
|
user = new User
|
||||||
|
{
|
||||||
|
email = "email",
|
||||||
|
authentication_token = "user_token",
|
||||||
|
title = "user_title",
|
||||||
|
username = "newUsername",
|
||||||
|
id = "PLEX_ID",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.CreateAsync(It.Is<OmbiUser>(x => x.UserName == "user_username" && x.Email == "email" && x.ProviderUserId == "user_id" && x.UserType == UserType.PlexUser)))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.AddToRoleAsync(It.Is<OmbiUser>(x => x.UserName == "user_username"), It.Is<string>(x => x == OmbiRoles.Admin)))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Never);
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.UpdateAsync(It.Is<OmbiUser>(x => x.Email == "email" && x.UserName == "newUsername")), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Only_Imports_Plex_Admin_Username_Clash()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = false });
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexAccount>>(x => x.GetAccount(It.IsAny<string>())).ReturnsAsync(new PlexAccount
|
||||||
|
{
|
||||||
|
user = new User
|
||||||
|
{
|
||||||
|
email = "email",
|
||||||
|
authentication_token = "user_token",
|
||||||
|
title = "user_title",
|
||||||
|
username = "abc",
|
||||||
|
id = "nah",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.CreateAsync(It.Is<OmbiUser>(x => x.UserName == "user_username" && x.Email == "email" && x.ProviderUserId == "user_id" && x.UserType == UserType.PlexUser)))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.AddToRoleAsync(It.Is<OmbiUser>(x => x.UserName == "user_username"), It.Is<string>(x => x == OmbiRoles.Admin)))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Never);
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.UpdateAsync(It.IsAny<OmbiUser>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Doesnt_Import_Banned_Users()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = true, BannedPlexUserIds = new List<string> { "Banned" } });
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexFriends>>(x => x.GetUsers(It.IsAny<string>())).ReturnsAsync(new PlexFriends
|
||||||
|
{
|
||||||
|
User = new UserFriends[]
|
||||||
|
{
|
||||||
|
new UserFriends
|
||||||
|
{
|
||||||
|
Email = "email",
|
||||||
|
Id = "Banned",
|
||||||
|
Title = "title",
|
||||||
|
Username = "username"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Doesnt_Import_Managed_User()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = true });
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexFriends>>(x => x.GetUsers(It.IsAny<string>())).ReturnsAsync(new PlexFriends
|
||||||
|
{
|
||||||
|
User = new UserFriends[]
|
||||||
|
{
|
||||||
|
new UserFriends
|
||||||
|
{
|
||||||
|
Email = "email",
|
||||||
|
Id = "id",
|
||||||
|
Title = "title",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Doesnt_Import_DuplicateEmail()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = true });
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexFriends>>(x => x.GetUsers(It.IsAny<string>())).ReturnsAsync(new PlexFriends
|
||||||
|
{
|
||||||
|
User = new UserFriends[]
|
||||||
|
{
|
||||||
|
new UserFriends
|
||||||
|
{
|
||||||
|
Email = "dupe",
|
||||||
|
Id = "id",
|
||||||
|
Title = "title",
|
||||||
|
Username = "username"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<OmbiUser>>(x => x.FindByEmailAsync("dupe")).ReturnsAsync(new OmbiUser());
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Created_Plex_User()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = true, DefaultRoles = new List<string>
|
||||||
|
{
|
||||||
|
OmbiRoles.RequestMovie
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexFriends>>(x => x.GetUsers(It.IsAny<string>())).ReturnsAsync(new PlexFriends
|
||||||
|
{
|
||||||
|
User = new UserFriends[]
|
||||||
|
{
|
||||||
|
new UserFriends
|
||||||
|
{
|
||||||
|
Email = "email",
|
||||||
|
Id = "id",
|
||||||
|
Username = "plex"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.CreateAsync(It.Is<OmbiUser>(x => x.UserName == "plex" && x.Email == "email" && x.ProviderUserId == "id" && x.UserType == UserType.PlexUser)))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.AddToRoleAsync(It.Is<OmbiUser>(x => x.UserName == "plex"), OmbiRoles.RequestMovie))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.CreateAsync(It.IsAny<OmbiUser>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Import_Update_Plex_User()
|
||||||
|
{
|
||||||
|
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||||
|
.ReturnsAsync(new UserManagementSettings
|
||||||
|
{
|
||||||
|
ImportPlexAdmin = false,
|
||||||
|
ImportPlexUsers = true,
|
||||||
|
DefaultRoles = new List<string>
|
||||||
|
{
|
||||||
|
OmbiRoles.RequestMovie
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexFriends>>(x => x.GetUsers(It.IsAny<string>())).ReturnsAsync(new PlexFriends
|
||||||
|
{
|
||||||
|
User = new UserFriends[]
|
||||||
|
{
|
||||||
|
new UserFriends
|
||||||
|
{
|
||||||
|
Email = "email",
|
||||||
|
Id = "PLEX_ID",
|
||||||
|
Username = "user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.CreateAsync(It.Is<OmbiUser>(x => x.UserName == "plex" && x.Email == "email" && x.ProviderUserId == "id" && x.UserType == UserType.PlexUser)))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
|
||||||
|
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.AddToRoleAsync(It.Is<OmbiUser>(x => x.UserName == "plex"), OmbiRoles.RequestMovie))
|
||||||
|
.ReturnsAsync(IdentityResult.Success);
|
||||||
|
|
||||||
|
await _subject.Execute(null);
|
||||||
|
|
||||||
|
_mocker.Verify<OmbiUserManager>(x => x.UpdateAsync(It.Is<OmbiUser>(x => x.ProviderUserId == "PLEX_ID" && x.Email == "email" && x.UserName == "user")), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using Moq;
|
using MockQueryable.Moq;
|
||||||
|
using Moq;
|
||||||
using Moq.AutoMock;
|
using Moq.AutoMock;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Ombi.Api.Plex;
|
using Ombi.Api.Plex;
|
||||||
|
@ -8,6 +9,7 @@ using Ombi.Core.Engine.Interfaces;
|
||||||
using Ombi.Core.Models.Requests;
|
using Ombi.Core.Models.Requests;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Core.Settings.Models.External;
|
using Ombi.Core.Settings.Models.External;
|
||||||
|
using Ombi.Core.Tests;
|
||||||
using Ombi.Schedule.Jobs.Plex;
|
using Ombi.Schedule.Jobs.Plex;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
@ -32,11 +34,12 @@ namespace Ombi.Schedule.Tests
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_mocker = new AutoMocker();
|
_mocker = new AutoMocker();
|
||||||
var um = MockHelper.MockUserManager(new List<OmbiUser> { new OmbiUser { Id = "abc", UserType = UserType.PlexUser, MediaServerToken = "abc", UserName = "abc", NormalizedUserName = "ABC" } });
|
var um = MockHelper.MockUserManager(new List<OmbiUser> { new OmbiUser { Id = "abc", UserType = UserType.PlexUser, MediaServerToken = "token1", UserName = "abc", NormalizedUserName = "ABC" } });
|
||||||
_mocker.Use(um);
|
_mocker.Use(um);
|
||||||
_context = _mocker.GetMock<IJobExecutionContext>();
|
_context = _mocker.GetMock<IJobExecutionContext>();
|
||||||
_context.Setup(x => x.CancellationToken).Returns(CancellationToken.None);
|
_context.Setup(x => x.CancellationToken).Returns(CancellationToken.None);
|
||||||
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
||||||
|
_mocker.Setup<IRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll()).Returns(new List<PlexWatchlistUserError>().AsQueryable().BuildMock().Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -53,6 +56,7 @@ namespace Ombi.Schedule.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TerminatesWhenWatchlistIsNotEnabled()
|
public async Task TerminatesWhenWatchlistIsNotEnabled()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = false });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = false });
|
||||||
await _subject.Execute(null);
|
await _subject.Execute(null);
|
||||||
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
|
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
|
||||||
|
@ -75,9 +79,74 @@ namespace Ombi.Schedule.Tests
|
||||||
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
|
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task AuthenticationError()
|
||||||
|
{
|
||||||
|
|
||||||
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer { AuthError = true });
|
||||||
|
await _subject.Execute(_context.Object);
|
||||||
|
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
|
||||||
|
_mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
|
||||||
|
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
|
||||||
|
_mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
|
||||||
|
_mocker.Verify<IRepository<PlexWatchlistUserError>>(x => x.Add(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task FailedWatchListUser_NewToken_ShouldBeRemoved()
|
||||||
|
{
|
||||||
|
_mocker.Setup<IRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll()).Returns(new List<PlexWatchlistUserError>
|
||||||
|
{
|
||||||
|
new PlexWatchlistUserError
|
||||||
|
{
|
||||||
|
UserId = "abc",
|
||||||
|
MediaServerToken = "dead"
|
||||||
|
}
|
||||||
|
}.AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer { AuthError = false });
|
||||||
|
await _subject.Execute(_context.Object);
|
||||||
|
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
|
||||||
|
_mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
|
||||||
|
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
|
||||||
|
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
|
||||||
|
_mocker.Verify<IRepository<PlexWatchlistUserError>>(x => x.Add(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Never);
|
||||||
|
_mocker.Verify<IRepository<PlexWatchlistUserError>>(x => x.Delete(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task FailedWatchListUser_OldToken_ShouldSkip()
|
||||||
|
{
|
||||||
|
_mocker.Setup<IRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll()).Returns(new List<PlexWatchlistUserError>
|
||||||
|
{
|
||||||
|
new PlexWatchlistUserError
|
||||||
|
{
|
||||||
|
UserId = "abc",
|
||||||
|
MediaServerToken = "token1"
|
||||||
|
}
|
||||||
|
}.AsQueryable().BuildMock().Object);
|
||||||
|
|
||||||
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer { AuthError = false });
|
||||||
|
await _subject.Execute(_context.Object);
|
||||||
|
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
|
||||||
|
_mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
|
||||||
|
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
|
||||||
|
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
|
||||||
|
_mocker.Verify<IRepository<PlexWatchlistUserError>>(x => x.Add(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Never);
|
||||||
|
_mocker.Verify<IRepository<PlexWatchlistUserError>>(x => x.Delete(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task NoPlexUsersWithToken()
|
public async Task NoPlexUsersWithToken()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
var um = MockHelper.MockUserManager(new List<OmbiUser>
|
var um = MockHelper.MockUserManager(new List<OmbiUser>
|
||||||
{
|
{
|
||||||
|
@ -102,6 +171,7 @@ namespace Ombi.Schedule.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public async Task MultipleUsers()
|
public async Task MultipleUsers()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
var um = MockHelper.MockUserManager(new List<OmbiUser>
|
var um = MockHelper.MockUserManager(new List<OmbiUser>
|
||||||
{
|
{
|
||||||
|
@ -125,6 +195,7 @@ namespace Ombi.Schedule.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public async Task MovieRequestFromWatchList_NoGuid()
|
public async Task MovieRequestFromWatchList_NoGuid()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
||||||
{
|
{
|
||||||
|
@ -175,6 +246,7 @@ namespace Ombi.Schedule.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TvRequestFromWatchList_NoGuid()
|
public async Task TvRequestFromWatchList_NoGuid()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
||||||
{
|
{
|
||||||
|
@ -224,6 +296,7 @@ namespace Ombi.Schedule.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public async Task MovieRequestFromWatchList_AlreadyRequested()
|
public async Task MovieRequestFromWatchList_AlreadyRequested()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
||||||
{
|
{
|
||||||
|
@ -273,6 +346,7 @@ namespace Ombi.Schedule.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TvRequestFromWatchList_AlreadyRequested()
|
public async Task TvRequestFromWatchList_AlreadyRequested()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
||||||
{
|
{
|
||||||
|
@ -322,6 +396,7 @@ namespace Ombi.Schedule.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public async Task MovieRequestFromWatchList_NoTmdbGuid()
|
public async Task MovieRequestFromWatchList_NoTmdbGuid()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
||||||
{
|
{
|
||||||
|
@ -371,6 +446,7 @@ namespace Ombi.Schedule.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TvRequestFromWatchList_NoTmdbGuid()
|
public async Task TvRequestFromWatchList_NoTmdbGuid()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
||||||
{
|
{
|
||||||
|
@ -420,6 +496,7 @@ namespace Ombi.Schedule.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public async Task MovieRequestFromWatchList_AlreadyImported()
|
public async Task MovieRequestFromWatchList_AlreadyImported()
|
||||||
{
|
{
|
||||||
|
|
||||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,7 +11,6 @@ using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Hubs;
|
using Ombi.Hubs;
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Schedule.Jobs.Plex.Models;
|
|
||||||
using Ombi.Settings.Settings.Models.External;
|
using Ombi.Settings.Settings.Models.External;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
@ -20,17 +19,13 @@ using Quartz;
|
||||||
|
|
||||||
namespace Ombi.Schedule.Jobs.Radarr
|
namespace Ombi.Schedule.Jobs.Radarr
|
||||||
{
|
{
|
||||||
public class ArrAvailabilityChecker : IArrAvailabilityChecker
|
public class ArrAvailabilityChecker : AvailabilityChecker, IArrAvailabilityChecker
|
||||||
{
|
{
|
||||||
private readonly IExternalRepository<RadarrCache> _radarrRepo;
|
private readonly IExternalRepository<RadarrCache> _radarrRepo;
|
||||||
private readonly IExternalRepository<SonarrCache> _sonarrRepo;
|
private readonly IExternalRepository<SonarrCache> _sonarrRepo;
|
||||||
private readonly ILogger<ArrAvailabilityChecker> _logger;
|
|
||||||
private readonly ISettingsService<RadarrSettings> _radarrSettings;
|
private readonly ISettingsService<RadarrSettings> _radarrSettings;
|
||||||
private readonly ISettingsService<SonarrSettings> _sonarrSettings;
|
private readonly ISettingsService<SonarrSettings> _sonarrSettings;
|
||||||
private readonly IExternalRepository<SonarrEpisodeCache> _sonarrEpisodeRepo;
|
private readonly IExternalRepository<SonarrEpisodeCache> _sonarrEpisodeRepo;
|
||||||
private readonly INotificationHelper _notification;
|
|
||||||
private readonly IHubContext<NotificationHub> _hub;
|
|
||||||
private readonly ITvRequestRepository _tvRequest;
|
|
||||||
private readonly IMovieRequestRepository _movies;
|
private readonly IMovieRequestRepository _movies;
|
||||||
|
|
||||||
public ArrAvailabilityChecker(
|
public ArrAvailabilityChecker(
|
||||||
|
@ -42,15 +37,12 @@ namespace Ombi.Schedule.Jobs.Radarr
|
||||||
ILogger<ArrAvailabilityChecker> log,
|
ILogger<ArrAvailabilityChecker> log,
|
||||||
ISettingsService<RadarrSettings> radarrSettings,
|
ISettingsService<RadarrSettings> radarrSettings,
|
||||||
ISettingsService<SonarrSettings> sonarrSettings)
|
ISettingsService<SonarrSettings> sonarrSettings)
|
||||||
|
: base(tvRequest, notification, log, hub)
|
||||||
{
|
{
|
||||||
_radarrRepo = radarrRepo;
|
_radarrRepo = radarrRepo;
|
||||||
_sonarrRepo = sonarrRepo;
|
_sonarrRepo = sonarrRepo;
|
||||||
_sonarrEpisodeRepo = sonarrEpisodeRepo;
|
_sonarrEpisodeRepo = sonarrEpisodeRepo;
|
||||||
_notification = notification;
|
|
||||||
_hub = hub;
|
|
||||||
_tvRequest = tvRequest;
|
|
||||||
_movies = movies;
|
_movies = movies;
|
||||||
_logger = log;
|
|
||||||
_radarrSettings = radarrSettings;
|
_radarrSettings = radarrSettings;
|
||||||
_sonarrSettings = sonarrSettings;
|
_sonarrSettings = sonarrSettings;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +74,7 @@ namespace Ombi.Schedule.Jobs.Radarr
|
||||||
var available = availableRadarrMovies.FirstOrDefault(x => x.TheMovieDbId == movieRequest.TheMovieDbId);
|
var available = availableRadarrMovies.FirstOrDefault(x => x.TheMovieDbId == movieRequest.TheMovieDbId);
|
||||||
if (available != null)
|
if (available != null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Found move '{movieRequest.Title}' available in Radarr");
|
_log.LogInformation($"Found move '{movieRequest.Title}' available in Radarr");
|
||||||
if (available.Has4K && !movieRequest.Available4K)
|
if (available.Has4K && !movieRequest.Available4K)
|
||||||
{
|
{
|
||||||
itemsForAvailability.Add(new AvailabilityModel
|
itemsForAvailability.Add(new AvailabilityModel
|
||||||
|
@ -114,7 +106,7 @@ namespace Ombi.Schedule.Jobs.Radarr
|
||||||
}
|
}
|
||||||
foreach (var item in itemsForAvailability)
|
foreach (var item in itemsForAvailability)
|
||||||
{
|
{
|
||||||
await _notification.Notify(new NotificationOptions
|
await _notificationService.Notify(new NotificationOptions
|
||||||
{
|
{
|
||||||
DateTime = DateTime.Now,
|
DateTime = DateTime.Now,
|
||||||
NotificationType = NotificationType.RequestAvailable,
|
NotificationType = NotificationType.RequestAvailable,
|
||||||
|
@ -127,9 +119,9 @@ namespace Ombi.Schedule.Jobs.Radarr
|
||||||
|
|
||||||
public async Task ProcessTvShows()
|
public async Task ProcessTvShows()
|
||||||
{
|
{
|
||||||
var tv = await _tvRequest.GetChild().Where(x => !x.Available).ToListAsync();
|
var tv = await _tvRepo.GetChild().Where(x => !x.Available).ToListAsync();
|
||||||
var sonarrEpisodes = _sonarrEpisodeRepo.GetAll().Where(x => x.HasFile);
|
var sonarrEpisodes = _sonarrEpisodeRepo.GetAll().Where(x => x.HasFile);
|
||||||
|
|
||||||
foreach (var child in tv)
|
foreach (var child in tv)
|
||||||
{
|
{
|
||||||
var tvDbId = child.ParentRequest.TvDbId;
|
var tvDbId = child.ParentRequest.TvDbId;
|
||||||
|
@ -140,83 +132,10 @@ namespace Ombi.Schedule.Jobs.Radarr
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (!seriesEpisodes.Any())
|
await ProcessTvShow(seriesEpisodes, child);
|
||||||
//{
|
|
||||||
// // Let's try and match the series by name
|
|
||||||
// seriesEpisodes = sonarrEpisodes.Where(x =>
|
|
||||||
// x.EpisodeNumber == child.Title &&
|
|
||||||
// x.Series.ReleaseYear == child.ParentRequest.ReleaseDate.Year.ToString());
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
var availableEpisode = new List<AvailabilityModel>();
|
|
||||||
foreach (var season in child.SeasonRequests)
|
|
||||||
{
|
|
||||||
foreach (var episode in season.Episodes)
|
|
||||||
{
|
|
||||||
if (episode.Available)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var foundEp = await seriesEpisodes.AnyAsync(
|
|
||||||
x => x.EpisodeNumber == episode.EpisodeNumber &&
|
|
||||||
x.SeasonNumber == episode.Season.SeasonNumber);
|
|
||||||
|
|
||||||
if (foundEp)
|
|
||||||
{
|
|
||||||
availableEpisode.Add(new AvailabilityModel
|
|
||||||
{
|
|
||||||
Id = episode.Id,
|
|
||||||
EpisodeNumber = episode.EpisodeNumber,
|
|
||||||
SeasonNumber = episode.Season.SeasonNumber
|
|
||||||
});
|
|
||||||
episode.Available = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableEpisode.Any())
|
|
||||||
{
|
|
||||||
await _tvRequest.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if all of the episodes in all seasons are available for this request
|
|
||||||
var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available));
|
|
||||||
if (allAvailable)
|
|
||||||
{
|
|
||||||
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
|
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Sonarr Availability Checker found some new available Shows!");
|
|
||||||
child.Available = true;
|
|
||||||
child.MarkedAsAvailable = DateTime.UtcNow;
|
|
||||||
_logger.LogInformation("[ARR_AC] - Child request {0} is now available, sending notification", $"{child.Title} - {child.Id}");
|
|
||||||
// We have ful-fulled this request!
|
|
||||||
await _tvRequest.Save();
|
|
||||||
await _notification.Notify(new NotificationOptions
|
|
||||||
{
|
|
||||||
DateTime = DateTime.Now,
|
|
||||||
NotificationType = NotificationType.RequestAvailable,
|
|
||||||
RequestId = child.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
Recipient = child.RequestedUser.Email
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (availableEpisode.Any())
|
|
||||||
{
|
|
||||||
var notification = new NotificationOptions
|
|
||||||
{
|
|
||||||
DateTime = DateTime.Now,
|
|
||||||
NotificationType = NotificationType.PartiallyAvailable,
|
|
||||||
RequestId = child.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
Recipient = child.RequestedUser.Email,
|
|
||||||
};
|
|
||||||
notification.Substitutes.Add("Season", availableEpisode.First().SeasonNumber.ToString());
|
|
||||||
notification.Substitutes.Add("Episodes", string.Join(", ", availableEpisode.Select(x => x.EpisodeNumber)));
|
|
||||||
await _notification.Notify(notification);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _tvRequest.Save();
|
await _tvRepo.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
106
src/Ombi.Schedule/Jobs/AvailabilityChecker.cs
Normal file
106
src/Ombi.Schedule/Jobs/AvailabilityChecker.cs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ombi.Core;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Hubs;
|
||||||
|
using Ombi.Notifications.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Entities.Requests;
|
||||||
|
using Ombi.Store.Repository.Requests;
|
||||||
|
|
||||||
|
namespace Ombi.Schedule.Jobs
|
||||||
|
{
|
||||||
|
public abstract class AvailabilityChecker
|
||||||
|
{
|
||||||
|
protected readonly ITvRequestRepository _tvRepo;
|
||||||
|
protected readonly INotificationHelper _notificationService;
|
||||||
|
protected readonly ILogger _log;
|
||||||
|
protected readonly IHubContext<NotificationHub> _hub;
|
||||||
|
|
||||||
|
public AvailabilityChecker(ITvRequestRepository tvRequest, INotificationHelper notification,
|
||||||
|
ILogger log, IHubContext<NotificationHub> hub)
|
||||||
|
{
|
||||||
|
_tvRepo = tvRequest;
|
||||||
|
_notificationService = notification;
|
||||||
|
_log = log;
|
||||||
|
_hub = hub;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task ProcessTvShow(IQueryable<IBaseMediaServerEpisode> seriesEpisodes, ChildRequests child)
|
||||||
|
{
|
||||||
|
var availableEpisode = new List<AvailabilityModel>();
|
||||||
|
foreach (var season in child.SeasonRequests)
|
||||||
|
{
|
||||||
|
foreach (var episode in season.Episodes)
|
||||||
|
{
|
||||||
|
if (episode.Available)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var foundEp = await seriesEpisodes.AnyAsync(
|
||||||
|
x => x.EpisodeNumber == episode.EpisodeNumber &&
|
||||||
|
x.SeasonNumber == episode.Season.SeasonNumber);
|
||||||
|
|
||||||
|
if (foundEp)
|
||||||
|
{
|
||||||
|
availableEpisode.Add(new AvailabilityModel
|
||||||
|
{
|
||||||
|
Id = episode.Id,
|
||||||
|
EpisodeNumber = episode.EpisodeNumber,
|
||||||
|
SeasonNumber = episode.Season.SeasonNumber
|
||||||
|
});
|
||||||
|
episode.Available = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableEpisode.Any())
|
||||||
|
{
|
||||||
|
await _tvRepo.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if all of the episodes in all seasons are available for this request
|
||||||
|
var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available));
|
||||||
|
if (allAvailable)
|
||||||
|
{
|
||||||
|
// We have ful-fulled this request!
|
||||||
|
child.Available = true;
|
||||||
|
child.MarkedAsAvailable = DateTime.UtcNow;
|
||||||
|
await _hub?.Clients?.Clients(NotificationHub.AdminConnectionIds)?
|
||||||
|
.SendAsync(NotificationHub.NotificationEvent, "Availability Checker found some new available Shows!");
|
||||||
|
_log.LogInformation("Child request {0} is now available, sending notification", $"{child.Title} - {child.Id}");
|
||||||
|
|
||||||
|
await _tvRepo.Save();
|
||||||
|
await _notificationService.Notify(new NotificationOptions
|
||||||
|
{
|
||||||
|
DateTime = DateTime.Now,
|
||||||
|
NotificationType = NotificationType.RequestAvailable,
|
||||||
|
RequestId = child.Id,
|
||||||
|
RequestType = RequestType.TvShow,
|
||||||
|
Recipient = child.RequestedUser.Email
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (availableEpisode.Any())
|
||||||
|
{
|
||||||
|
var notification = new NotificationOptions
|
||||||
|
{
|
||||||
|
DateTime = DateTime.Now,
|
||||||
|
NotificationType = NotificationType.PartiallyAvailable,
|
||||||
|
RequestId = child.Id,
|
||||||
|
RequestType = RequestType.TvShow,
|
||||||
|
Recipient = child.RequestedUser.Email,
|
||||||
|
};
|
||||||
|
notification.Substitutes.Add("Season", availableEpisode.First().SeasonNumber.ToString());
|
||||||
|
notification.Substitutes.Add("Episodes", string.Join(", ", availableEpisode.Select(x => x.EpisodeNumber)));
|
||||||
|
notification.Substitutes.Add("EpisodesCount", $"{availableEpisode.Count}");
|
||||||
|
notification.Substitutes.Add("SeasonEpisodes", string.Join(", ", availableEpisode.Select(x => $"{x.SeasonNumber}x{x.EpisodeNumber}")));
|
||||||
|
await _notificationService.Notify(notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Core;
|
using Ombi.Core;
|
||||||
using Ombi.Core.Notifications;
|
|
||||||
using Ombi.Core.Services;
|
using Ombi.Core.Services;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Hubs;
|
using Ombi.Hubs;
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Schedule.Jobs.Ombi;
|
|
||||||
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;
|
||||||
|
@ -20,38 +17,31 @@ using Quartz;
|
||||||
|
|
||||||
namespace Ombi.Schedule.Jobs.Emby
|
namespace Ombi.Schedule.Jobs.Emby
|
||||||
{
|
{
|
||||||
public class EmbyAvaliabilityChecker : IEmbyAvaliabilityChecker
|
public class EmbyAvaliabilityChecker : AvailabilityChecker, IEmbyAvaliabilityChecker
|
||||||
{
|
{
|
||||||
public EmbyAvaliabilityChecker(IEmbyContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m,
|
public EmbyAvaliabilityChecker(IEmbyContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m,
|
||||||
INotificationHelper n, ILogger<EmbyAvaliabilityChecker> log, IHubContext<NotificationHub> notification, IFeatureService featureService)
|
INotificationHelper n, ILogger<EmbyAvaliabilityChecker> log, IHubContext<NotificationHub> notification, IFeatureService featureService)
|
||||||
|
: base(t, n, log, notification)
|
||||||
{
|
{
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_tvRepo = t;
|
|
||||||
_movieRepo = m;
|
_movieRepo = m;
|
||||||
_notificationService = n;
|
|
||||||
_log = log;
|
|
||||||
_notification = notification;
|
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ITvRequestRepository _tvRepo;
|
|
||||||
private readonly IMovieRequestRepository _movieRepo;
|
private readonly IMovieRequestRepository _movieRepo;
|
||||||
private readonly IEmbyContentRepository _repo;
|
private readonly IEmbyContentRepository _repo;
|
||||||
private readonly INotificationHelper _notificationService;
|
|
||||||
private readonly ILogger<EmbyAvaliabilityChecker> _log;
|
|
||||||
private readonly IHubContext<NotificationHub> _notification;
|
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext job)
|
public async Task Execute(IJobExecutionContext job)
|
||||||
{
|
{
|
||||||
_log.LogInformation("Starting Emby Availability Check");
|
_log.LogInformation("Starting Emby Availability Check");
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Emby Availability Checker Started");
|
.SendAsync(NotificationHub.NotificationEvent, "Emby Availability Checker Started");
|
||||||
await ProcessMovies();
|
await ProcessMovies();
|
||||||
await ProcessTv();
|
await ProcessTv();
|
||||||
|
|
||||||
_log.LogInformation("Finished Emby Availability Check");
|
_log.LogInformation("Finished Emby Availability Check");
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Emby Availability Checker Finished");
|
.SendAsync(NotificationHub.NotificationEvent, "Emby Availability Checker Finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,68 +157,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
x.Series.Title == child.Title);
|
x.Series.Title == child.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
var availableEpisode = new List<AvailabilityModel>();
|
await ProcessTvShow(seriesEpisodes, child);
|
||||||
foreach (var season in child.SeasonRequests)
|
|
||||||
{
|
|
||||||
foreach (var episode in season.Episodes)
|
|
||||||
{
|
|
||||||
if (episode.Available)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var foundEp = await seriesEpisodes.FirstOrDefaultAsync(
|
|
||||||
x => x.EpisodeNumber == episode.EpisodeNumber &&
|
|
||||||
x.SeasonNumber == episode.Season.SeasonNumber);
|
|
||||||
|
|
||||||
if (foundEp != null)
|
|
||||||
{
|
|
||||||
availableEpisode.Add(new AvailabilityModel
|
|
||||||
{
|
|
||||||
Id = episode.Id,
|
|
||||||
EpisodeNumber = episode.EpisodeNumber,
|
|
||||||
SeasonNumber = episode.Season.SeasonNumber
|
|
||||||
});
|
|
||||||
episode.Available = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableEpisode.Any())
|
|
||||||
{
|
|
||||||
await _tvRepo.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if all of the episodes in all seasons are available for this request
|
|
||||||
var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available));
|
|
||||||
if (allAvailable)
|
|
||||||
{
|
|
||||||
// We have fulfulled this request!
|
|
||||||
child.Available = true;
|
|
||||||
child.MarkedAsAvailable = DateTime.Now;
|
|
||||||
await _notificationService.Notify(new NotificationOptions
|
|
||||||
{
|
|
||||||
DateTime = DateTime.Now,
|
|
||||||
NotificationType = NotificationType.RequestAvailable,
|
|
||||||
RequestId = child.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
Recipient = child.RequestedUser.Email
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (availableEpisode.Any())
|
|
||||||
{
|
|
||||||
var notification = new NotificationOptions
|
|
||||||
{
|
|
||||||
DateTime = DateTime.Now,
|
|
||||||
NotificationType = NotificationType.PartiallyAvailable,
|
|
||||||
RequestId = child.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
Recipient = child.RequestedUser.Email,
|
|
||||||
};
|
|
||||||
notification.Substitutes.Add("Season", availableEpisode.First().SeasonNumber.ToString());
|
|
||||||
notification.Substitutes.Add("Episodes", string.Join(", ", availableEpisode.Select(x => x.EpisodeNumber)));
|
|
||||||
await _notificationService.Notify(notification);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _tvRepo.Save();
|
await _tvRepo.Save();
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
@ -45,38 +44,31 @@ using Quartz;
|
||||||
|
|
||||||
namespace Ombi.Schedule.Jobs.Jellyfin
|
namespace Ombi.Schedule.Jobs.Jellyfin
|
||||||
{
|
{
|
||||||
public class JellyfinAvaliabilityChecker : IJellyfinAvaliabilityChecker
|
public class JellyfinAvaliabilityChecker : AvailabilityChecker, IJellyfinAvaliabilityChecker
|
||||||
{
|
{
|
||||||
public JellyfinAvaliabilityChecker(IJellyfinContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m,
|
public JellyfinAvaliabilityChecker(IJellyfinContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m,
|
||||||
INotificationHelper n, ILogger<JellyfinAvaliabilityChecker> log, IHubContext<NotificationHub> notification, IFeatureService featureService)
|
INotificationHelper n, ILogger<JellyfinAvaliabilityChecker> log, IHubContext<NotificationHub> notification, IFeatureService featureService)
|
||||||
|
: base(t, n, log, notification)
|
||||||
{
|
{
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_tvRepo = t;
|
|
||||||
_movieRepo = m;
|
_movieRepo = m;
|
||||||
_notificationService = n;
|
|
||||||
_log = log;
|
|
||||||
_notification = notification;
|
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ITvRequestRepository _tvRepo;
|
|
||||||
private readonly IMovieRequestRepository _movieRepo;
|
private readonly IMovieRequestRepository _movieRepo;
|
||||||
private readonly IJellyfinContentRepository _repo;
|
private readonly IJellyfinContentRepository _repo;
|
||||||
private readonly INotificationHelper _notificationService;
|
|
||||||
private readonly ILogger<JellyfinAvaliabilityChecker> _log;
|
|
||||||
private readonly IHubContext<NotificationHub> _notification;
|
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext job)
|
public async Task Execute(IJobExecutionContext job)
|
||||||
{
|
{
|
||||||
_log.LogInformation("Starting Jellyfin Availability Check");
|
_log.LogInformation("Starting Jellyfin Availability Check");
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Availability Checker Started");
|
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Availability Checker Started");
|
||||||
await ProcessMovies();
|
await ProcessMovies();
|
||||||
await ProcessTv();
|
await ProcessTv();
|
||||||
|
|
||||||
_log.LogInformation("Finished Jellyfin Availability Check");
|
_log.LogInformation("Finished Jellyfin Availability Check");
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Availability Checker Finished");
|
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Availability Checker Finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,68 +185,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
||||||
x.Series.Title == child.Title);
|
x.Series.Title == child.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
var availableEpisode = new List<AvailabilityModel>();
|
await ProcessTvShow(seriesEpisodes, child);
|
||||||
foreach (var season in child.SeasonRequests)
|
|
||||||
{
|
|
||||||
foreach (var episode in season.Episodes)
|
|
||||||
{
|
|
||||||
if (episode.Available)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var foundEp = await seriesEpisodes.FirstOrDefaultAsync(
|
|
||||||
x => x.EpisodeNumber == episode.EpisodeNumber &&
|
|
||||||
x.SeasonNumber == episode.Season.SeasonNumber);
|
|
||||||
|
|
||||||
if (foundEp != null)
|
|
||||||
{
|
|
||||||
availableEpisode.Add(new AvailabilityModel
|
|
||||||
{
|
|
||||||
Id = episode.Id,
|
|
||||||
EpisodeNumber = episode.EpisodeNumber,
|
|
||||||
SeasonNumber = episode.Season.SeasonNumber
|
|
||||||
});
|
|
||||||
episode.Available = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableEpisode.Any())
|
|
||||||
{
|
|
||||||
await _tvRepo.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if all of the episodes in all seasons are available for this request
|
|
||||||
var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available));
|
|
||||||
if (allAvailable)
|
|
||||||
{
|
|
||||||
// We have fulfulled this request!
|
|
||||||
child.Available = true;
|
|
||||||
child.MarkedAsAvailable = DateTime.Now;
|
|
||||||
await _notificationService.Notify(new NotificationOptions
|
|
||||||
{
|
|
||||||
DateTime = DateTime.Now,
|
|
||||||
NotificationType = NotificationType.RequestAvailable,
|
|
||||||
RequestId = child.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
Recipient = child.RequestedUser.Email
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (availableEpisode.Any())
|
|
||||||
{
|
|
||||||
var notification = new NotificationOptions
|
|
||||||
{
|
|
||||||
DateTime = DateTime.Now,
|
|
||||||
NotificationType = NotificationType.PartiallyAvailable,
|
|
||||||
RequestId = child.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
Recipient = child.RequestedUser.Email,
|
|
||||||
};
|
|
||||||
notification.Substitutes.Add("Season", availableEpisode.First().SeasonNumber.ToString());
|
|
||||||
notification.Substitutes.Add("Episodes", string.Join(", ", availableEpisode.Select(x => x.EpisodeNumber)));
|
|
||||||
await _notificationService.Notify(notification);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _tvRepo.Save();
|
await _tvRepo.Save();
|
||||||
|
|
|
@ -10,7 +10,6 @@ using Ombi.Core.Services;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Hubs;
|
using Ombi.Hubs;
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Schedule.Jobs.Plex.Models;
|
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Entities.Requests;
|
using Ombi.Store.Entities.Requests;
|
||||||
|
@ -20,47 +19,39 @@ using Quartz;
|
||||||
|
|
||||||
namespace Ombi.Schedule.Jobs.Plex
|
namespace Ombi.Schedule.Jobs.Plex
|
||||||
{
|
{
|
||||||
public class PlexAvailabilityChecker : IPlexAvailabilityChecker
|
public class PlexAvailabilityChecker : AvailabilityChecker, IPlexAvailabilityChecker
|
||||||
{
|
{
|
||||||
public PlexAvailabilityChecker(IPlexContentRepository repo, ITvRequestRepository tvRequest, IMovieRequestRepository movies,
|
public PlexAvailabilityChecker(IPlexContentRepository repo, ITvRequestRepository tvRequest, IMovieRequestRepository movies,
|
||||||
INotificationHelper notification, ILogger<PlexAvailabilityChecker> log, IHubContext<NotificationHub> hub, IFeatureService featureService)
|
INotificationHelper notification, ILogger<PlexAvailabilityChecker> log, IHubContext<NotificationHub> hub, IFeatureService featureService)
|
||||||
|
: base(tvRequest, notification, log, hub)
|
||||||
{
|
{
|
||||||
_tvRepo = tvRequest;
|
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_movieRepo = movies;
|
_movieRepo = movies;
|
||||||
_notificationService = notification;
|
|
||||||
_log = log;
|
|
||||||
_notification = hub;
|
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ITvRequestRepository _tvRepo;
|
|
||||||
private readonly IMovieRequestRepository _movieRepo;
|
private readonly IMovieRequestRepository _movieRepo;
|
||||||
private readonly IPlexContentRepository _repo;
|
private readonly IPlexContentRepository _repo;
|
||||||
private readonly INotificationHelper _notificationService;
|
|
||||||
private readonly ILogger _log;
|
|
||||||
private readonly IHubContext<NotificationHub> _notification;
|
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext job)
|
public async Task Execute(IJobExecutionContext job)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Plex Availability Check Started");
|
.SendAsync(NotificationHub.NotificationEvent, "Plex Availability Check Started");
|
||||||
await ProcessMovies();
|
await ProcessMovies();
|
||||||
await ProcessTv();
|
await ProcessTv();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Plex Availability Check Failed");
|
.SendAsync(NotificationHub.NotificationEvent, "Plex Availability Check Failed");
|
||||||
_log.LogError(e, "Exception thrown in Plex availbility checker");
|
_log.LogError(e, "Exception thrown in Plex availbility checker");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Plex Availability Check Finished");
|
.SendAsync(NotificationHub.NotificationEvent, "Plex Availability Check Finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,16 +69,22 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
{
|
{
|
||||||
var useImdb = false;
|
var useImdb = false;
|
||||||
var useTvDb = false;
|
var useTvDb = false;
|
||||||
|
var useMovieDb = false;
|
||||||
if (child.ParentRequest.ImdbId.HasValue())
|
if (child.ParentRequest.ImdbId.HasValue())
|
||||||
{
|
{
|
||||||
useImdb = true;
|
useImdb = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.ParentRequest.TvDbId.ToString().HasValue())
|
if (child.ParentRequest.TvDbId > 0)
|
||||||
{
|
{
|
||||||
useTvDb = true;
|
useTvDb = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (child.ParentRequest.ExternalProviderId > 0)
|
||||||
|
{
|
||||||
|
useMovieDb = true;
|
||||||
|
}
|
||||||
|
|
||||||
var tvDbId = child.ParentRequest.TvDbId;
|
var tvDbId = child.ParentRequest.TvDbId;
|
||||||
var imdbId = child.ParentRequest.ImdbId;
|
var imdbId = child.ParentRequest.ImdbId;
|
||||||
IQueryable<IMediaServerEpisode> seriesEpisodes = null;
|
IQueryable<IMediaServerEpisode> seriesEpisodes = null;
|
||||||
|
@ -99,83 +96,19 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
{
|
{
|
||||||
seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
|
seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
|
||||||
}
|
}
|
||||||
|
if (useMovieDb && (seriesEpisodes == null || !seriesEpisodes.Any()))
|
||||||
if (seriesEpisodes == null)
|
|
||||||
{
|
{
|
||||||
continue;
|
seriesEpisodes = plexEpisodes.Where(x => x.Series.TheMovieDbId == child.ParentRequest.ExternalProviderId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!seriesEpisodes.Any())
|
if (seriesEpisodes == null || !seriesEpisodes.Any())
|
||||||
{
|
{
|
||||||
// Let's try and match the series by name
|
// Let's try and match the series by name
|
||||||
seriesEpisodes = plexEpisodes.Where(x =>
|
seriesEpisodes = plexEpisodes.Where(x =>
|
||||||
x.Series.Title == child.Title);
|
x.Series.Title.Equals(child.Title, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var availableEpisode = new List<AvailabilityModel>();
|
await ProcessTvShow(seriesEpisodes, child);
|
||||||
foreach (var season in child.SeasonRequests)
|
|
||||||
{
|
|
||||||
foreach (var episode in season.Episodes)
|
|
||||||
{
|
|
||||||
if (episode.Available)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var foundEp = await seriesEpisodes.AnyAsync(
|
|
||||||
x => x.EpisodeNumber == episode.EpisodeNumber &&
|
|
||||||
x.SeasonNumber == episode.Season.SeasonNumber);
|
|
||||||
|
|
||||||
if (foundEp)
|
|
||||||
{
|
|
||||||
availableEpisode.Add(new AvailabilityModel
|
|
||||||
{
|
|
||||||
Id = episode.Id,
|
|
||||||
EpisodeNumber = episode.EpisodeNumber,
|
|
||||||
SeasonNumber = episode.Season.SeasonNumber
|
|
||||||
});
|
|
||||||
episode.Available = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableEpisode.Any())
|
|
||||||
{
|
|
||||||
await _tvRepo.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if all of the episodes in all seasons are available for this request
|
|
||||||
var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available));
|
|
||||||
if (allAvailable)
|
|
||||||
{
|
|
||||||
child.Available = true;
|
|
||||||
child.MarkedAsAvailable = DateTime.UtcNow;
|
|
||||||
_log.LogInformation("[PAC] - Child request {0} is now available, sending notification", $"{child.Title} - {child.Id}");
|
|
||||||
// We have ful-fulled this request!
|
|
||||||
await _tvRepo.Save();
|
|
||||||
await _notificationService.Notify(new NotificationOptions
|
|
||||||
{
|
|
||||||
DateTime = DateTime.Now,
|
|
||||||
NotificationType = NotificationType.RequestAvailable,
|
|
||||||
RequestId = child.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
Recipient = child.RequestedUser.Email
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (availableEpisode.Any())
|
|
||||||
{
|
|
||||||
var notification = new NotificationOptions
|
|
||||||
{
|
|
||||||
DateTime = DateTime.Now,
|
|
||||||
NotificationType = NotificationType.PartiallyAvailable,
|
|
||||||
RequestId = child.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
Recipient = child.RequestedUser.Email,
|
|
||||||
};
|
|
||||||
notification.Substitutes.Add("Season", availableEpisode.First().SeasonNumber.ToString());
|
|
||||||
notification.Substitutes.Add("Episodes", string.Join(", " ,availableEpisode.Select(x => x.EpisodeNumber)));
|
|
||||||
await _notificationService.Notify(notification);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _tvRepo.Save();
|
await _tvRepo.Save();
|
||||||
|
|
|
@ -124,7 +124,6 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
{
|
{
|
||||||
await NotifyClient("Plex Sync - Checking if any requests are now available");
|
await NotifyClient("Plex Sync - Checking if any requests are now available");
|
||||||
Logger.LogInformation("Kicking off Plex Availability Checker");
|
Logger.LogInformation("Kicking off Plex Availability Checker");
|
||||||
await _mediaCacheService.Purge();
|
|
||||||
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
|
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
|
||||||
}
|
}
|
||||||
var processedCont = processedContent?.Content?.Count() ?? 0;
|
var processedCont = processedContent?.Content?.Count() ?? 0;
|
||||||
|
@ -133,6 +132,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
|
|
||||||
await NotifyClient(recentlyAddedSearch ? $"Plex Recently Added Sync Finished, We processed {processedCont}, and {processedEp} Episodes" : "Plex Content Sync Finished");
|
await NotifyClient(recentlyAddedSearch ? $"Plex Recently Added Sync Finished, We processed {processedCont}, and {processedEp} Episodes" : "Plex Content Sync Finished");
|
||||||
|
|
||||||
|
await _mediaCacheService.Purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ProcessedContent> StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch)
|
private async Task<ProcessedContent> StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch)
|
||||||
|
@ -496,31 +496,31 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
await Repo.Update(existingContent);
|
await Repo.Update(existingContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just check the key
|
//// Just check the key
|
||||||
if (existingKey != null)
|
//if (existingKey != null)
|
||||||
{
|
//{
|
||||||
// The rating key is all good!
|
// // The rating key is all good!
|
||||||
}
|
//}
|
||||||
else
|
//else
|
||||||
{
|
//{
|
||||||
// This means the rating key has changed somehow.
|
// // This means the rating key has changed somehow.
|
||||||
// Should probably delete this and get the new one
|
// // Should probably delete this and get the new one
|
||||||
var oldKey = existingContent.Key;
|
// var oldKey = existingContent.Key;
|
||||||
Repo.DeleteWithoutSave(existingContent);
|
// Repo.DeleteWithoutSave(existingContent);
|
||||||
|
|
||||||
// Because we have changed the rating key, we need to change all children too
|
// // Because we have changed the rating key, we need to change all children too
|
||||||
var episodeToChange = Repo.GetAllEpisodes().Cast<PlexEpisode>().Where(x => x.GrandparentKey == oldKey);
|
// var episodeToChange = Repo.GetAllEpisodes().Cast<PlexEpisode>().Where(x => x.GrandparentKey == oldKey);
|
||||||
if (episodeToChange.Any())
|
// if (episodeToChange.Any())
|
||||||
{
|
// {
|
||||||
foreach (var e in episodeToChange)
|
// foreach (var e in episodeToChange)
|
||||||
{
|
// {
|
||||||
Repo.DeleteWithoutSave(e);
|
// Repo.DeleteWithoutSave(e);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
await Repo.SaveChangesAsync();
|
// await Repo.SaveChangesAsync();
|
||||||
existingContent = null;
|
// existingContent = null;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also make sure it's not already being processed...
|
// Also make sure it's not already being processed...
|
||||||
|
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Api.Plex;
|
using Ombi.Api.Plex;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Core.Settings.Models.External;
|
using Ombi.Core.Settings.Models.External;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
|
@ -19,7 +20,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
{
|
{
|
||||||
public class PlexUserImporter : IPlexUserImporter
|
public class PlexUserImporter : IPlexUserImporter
|
||||||
{
|
{
|
||||||
public PlexUserImporter(IPlexApi api, UserManager<OmbiUser> um, ILogger<PlexUserImporter> log,
|
public PlexUserImporter(IPlexApi api, OmbiUserManager um, ILogger<PlexUserImporter> log,
|
||||||
ISettingsService<PlexSettings> plexSettings, ISettingsService<UserManagementSettings> ums, IHubContext<NotificationHub> hub)
|
ISettingsService<PlexSettings> plexSettings, ISettingsService<UserManagementSettings> ums, IHubContext<NotificationHub> hub)
|
||||||
{
|
{
|
||||||
_api = api;
|
_api = api;
|
||||||
|
@ -33,7 +34,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IPlexApi _api;
|
private readonly IPlexApi _api;
|
||||||
private readonly UserManager<OmbiUser> _userManager;
|
private readonly OmbiUserManager _userManager;
|
||||||
private readonly ILogger<PlexUserImporter> _log;
|
private readonly ILogger<PlexUserImporter> _log;
|
||||||
private readonly ISettingsService<PlexSettings> _plexSettings;
|
private readonly ISettingsService<PlexSettings> _plexSettings;
|
||||||
private readonly ISettingsService<UserManagementSettings> _userManagementSettings;
|
private readonly ISettingsService<UserManagementSettings> _userManagementSettings;
|
||||||
|
@ -43,17 +44,17 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
public async Task Execute(IJobExecutionContext job)
|
public async Task Execute(IJobExecutionContext job)
|
||||||
{
|
{
|
||||||
var userManagementSettings = await _userManagementSettings.GetSettingsAsync();
|
var userManagementSettings = await _userManagementSettings.GetSettingsAsync();
|
||||||
if (!userManagementSettings.ImportPlexUsers)
|
if (!userManagementSettings.ImportPlexUsers && !userManagementSettings.ImportPlexAdmin)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings = await _plexSettings.GetSettingsAsync();
|
var settings = await _plexSettings.GetSettingsAsync();
|
||||||
if (!settings.Enable)
|
if (!settings.Enable)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Plex User Importer Started");
|
.SendAsync(NotificationHub.NotificationEvent, "Plex User Importer Started");
|
||||||
var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.PlexUser).ToListAsync();
|
var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.PlexUser).ToListAsync();
|
||||||
|
@ -64,78 +65,13 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ImportAdmin(userManagementSettings, server, allUsers);
|
if (userManagementSettings.ImportPlexAdmin)
|
||||||
|
|
||||||
var users = await _api.GetUsers(server.PlexAuthToken);
|
|
||||||
|
|
||||||
foreach (var plexUser in users.User)
|
|
||||||
{
|
{
|
||||||
// Check if we should import this user
|
await ImportAdmin(userManagementSettings, server, allUsers);
|
||||||
if (userManagementSettings.BannedPlexUserIds.Contains(plexUser.Id))
|
}
|
||||||
{
|
if (userManagementSettings.ImportPlexUsers)
|
||||||
// Do not import these, they are not allowed into the country.
|
{
|
||||||
continue;
|
await ImportPlexUsers(userManagementSettings, allUsers, server);
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this Plex User already exists
|
|
||||||
// We are using the Plex USERNAME and Not the TITLE, the Title is for HOME USERS
|
|
||||||
var existingPlexUser = allUsers.FirstOrDefault(x => x.ProviderUserId == plexUser.Id);
|
|
||||||
if (existingPlexUser == null)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (!plexUser.Username.HasValue())
|
|
||||||
{
|
|
||||||
_log.LogInformation("Could not create Plex user since the have no username, PlexUserId: {0}", plexUser.Id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((plexUser.Email.HasValue()) && await _userManager.FindByEmailAsync(plexUser.Email) != null)
|
|
||||||
{
|
|
||||||
_log.LogWarning($"Cannot add user {plexUser.Username} because their email address is already in Ombi, skipping this user");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Create this users
|
|
||||||
// We do not store a password against the user since they will authenticate via Plex
|
|
||||||
var newUser = new OmbiUser
|
|
||||||
{
|
|
||||||
UserType = UserType.PlexUser,
|
|
||||||
UserName = plexUser?.Username ?? plexUser.Id,
|
|
||||||
ProviderUserId = plexUser.Id,
|
|
||||||
Email = plexUser?.Email ?? string.Empty,
|
|
||||||
Alias = string.Empty,
|
|
||||||
MovieRequestLimit = userManagementSettings.MovieRequestLimit,
|
|
||||||
MovieRequestLimitType = userManagementSettings.MovieRequestLimitType,
|
|
||||||
EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit,
|
|
||||||
EpisodeRequestLimitType = userManagementSettings.EpisodeRequestLimitType,
|
|
||||||
MusicRequestLimit = userManagementSettings.MusicRequestLimit,
|
|
||||||
MusicRequestLimitType = userManagementSettings.MusicRequestLimitType,
|
|
||||||
StreamingCountry = userManagementSettings.DefaultStreamingCountry
|
|
||||||
};
|
|
||||||
_log.LogInformation("Creating Plex user {0}", newUser.UserName);
|
|
||||||
var result = await _userManager.CreateAsync(newUser);
|
|
||||||
if (!LogResult(result))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (userManagementSettings.DefaultRoles.Any())
|
|
||||||
{
|
|
||||||
// Get the new user object to avoid any concurrency failures
|
|
||||||
var dbUser =
|
|
||||||
await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == newUser.UserName);
|
|
||||||
foreach (var defaultRole in userManagementSettings.DefaultRoles)
|
|
||||||
{
|
|
||||||
await _userManager.AddToRoleAsync(dbUser, defaultRole);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Do we need to update this user?
|
|
||||||
existingPlexUser.Email = plexUser.Email;
|
|
||||||
existingPlexUser.UserName = plexUser.Username;
|
|
||||||
|
|
||||||
await _userManager.UpdateAsync(existingPlexUser);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,13 +79,83 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Plex User Importer Finished");
|
.SendAsync(NotificationHub.NotificationEvent, "Plex User Importer Finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ImportPlexUsers(UserManagementSettings userManagementSettings, List<OmbiUser> allUsers, PlexServers server)
|
||||||
|
{
|
||||||
|
var users = await _api.GetUsers(server.PlexAuthToken);
|
||||||
|
|
||||||
|
foreach (var plexUser in users.User)
|
||||||
|
{
|
||||||
|
// Check if we should import this user
|
||||||
|
if (userManagementSettings.BannedPlexUserIds.Contains(plexUser.Id))
|
||||||
|
{
|
||||||
|
// Do not import these, they are not allowed into the country.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this Plex User already exists
|
||||||
|
// We are using the Plex USERNAME and Not the TITLE, the Title is for HOME USERS
|
||||||
|
var existingPlexUser = allUsers.FirstOrDefault(x => x.ProviderUserId == plexUser.Id);
|
||||||
|
if (existingPlexUser == null)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!plexUser.Username.HasValue())
|
||||||
|
{
|
||||||
|
_log.LogInformation("Could not create Plex user since the have no username, PlexUserId: {0}", plexUser.Id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((plexUser.Email.HasValue()) && await _userManager.FindByEmailAsync(plexUser.Email) != null)
|
||||||
|
{
|
||||||
|
_log.LogWarning($"Cannot add user {plexUser.Username} because their email address is already in Ombi, skipping this user");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Create this users
|
||||||
|
// We do not store a password against the user since they will authenticate via Plex
|
||||||
|
var newUser = new OmbiUser
|
||||||
|
{
|
||||||
|
UserType = UserType.PlexUser,
|
||||||
|
UserName = plexUser?.Username ?? plexUser.Id,
|
||||||
|
ProviderUserId = plexUser.Id,
|
||||||
|
Email = plexUser?.Email ?? string.Empty,
|
||||||
|
Alias = string.Empty,
|
||||||
|
MovieRequestLimit = userManagementSettings.MovieRequestLimit,
|
||||||
|
MovieRequestLimitType = userManagementSettings.MovieRequestLimitType,
|
||||||
|
EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit,
|
||||||
|
EpisodeRequestLimitType = userManagementSettings.EpisodeRequestLimitType,
|
||||||
|
MusicRequestLimit = userManagementSettings.MusicRequestLimit,
|
||||||
|
MusicRequestLimitType = userManagementSettings.MusicRequestLimitType,
|
||||||
|
StreamingCountry = userManagementSettings.DefaultStreamingCountry
|
||||||
|
};
|
||||||
|
_log.LogInformation("Creating Plex user {0}", newUser.UserName);
|
||||||
|
var result = await _userManager.CreateAsync(newUser);
|
||||||
|
if (!LogResult(result))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (userManagementSettings.DefaultRoles.Any())
|
||||||
|
{
|
||||||
|
// Get the new user object to avoid any concurrency failures
|
||||||
|
var dbUser =
|
||||||
|
await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == newUser.UserName);
|
||||||
|
foreach (var defaultRole in userManagementSettings.DefaultRoles)
|
||||||
|
{
|
||||||
|
await _userManager.AddToRoleAsync(dbUser, defaultRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Do we need to update this user?
|
||||||
|
existingPlexUser.Email = plexUser.Email;
|
||||||
|
existingPlexUser.UserName = plexUser.Username;
|
||||||
|
|
||||||
|
await _userManager.UpdateAsync(existingPlexUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ImportAdmin(UserManagementSettings settings, PlexServers server, List<OmbiUser> allUsers)
|
private async Task ImportAdmin(UserManagementSettings settings, PlexServers server, List<OmbiUser> allUsers)
|
||||||
{
|
{
|
||||||
if (!settings.ImportPlexAdmin)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var plexAdmin = (await _api.GetAccount(server.PlexAuthToken)).user;
|
var plexAdmin = (await _api.GetAccount(server.PlexAuthToken)).user;
|
||||||
|
|
||||||
// Check if the admin is already in the DB
|
// Check if the admin is already in the DB
|
||||||
|
@ -166,6 +172,14 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we don't have a user with the same username
|
||||||
|
var normalUsername = plexAdmin.username.ToUpperInvariant();
|
||||||
|
if (await _userManager.Users.AnyAsync(x => x.NormalizedUserName == normalUsername))
|
||||||
|
{
|
||||||
|
_log.LogWarning($"Cannot add user {plexAdmin.username} because their username is already in Ombi, skipping this user");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var newUser = new OmbiUser
|
var newUser = new OmbiUser
|
||||||
{
|
{
|
||||||
UserType = UserType.PlexUser,
|
UserType = UserType.PlexUser,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Api.Plex;
|
using Ombi.Api.Plex;
|
||||||
using Ombi.Api.Plex.Models;
|
using Ombi.Api.Plex.Models;
|
||||||
|
@ -32,10 +33,11 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
private readonly IHubContext<NotificationHub> _hub;
|
private readonly IHubContext<NotificationHub> _hub;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IExternalRepository<PlexWatchlistHistory> _watchlistRepo;
|
private readonly IExternalRepository<PlexWatchlistHistory> _watchlistRepo;
|
||||||
|
private readonly IRepository<PlexWatchlistUserError> _userError;
|
||||||
|
|
||||||
public PlexWatchlistImport(IPlexApi plexApi, ISettingsService<PlexSettings> settings, OmbiUserManager ombiUserManager,
|
public PlexWatchlistImport(IPlexApi plexApi, ISettingsService<PlexSettings> settings, OmbiUserManager ombiUserManager,
|
||||||
IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IHubContext<NotificationHub> hub,
|
IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IHubContext<NotificationHub> hub,
|
||||||
ILogger<PlexWatchlistImport> logger, IExternalRepository<PlexWatchlistHistory> watchlistRepo)
|
ILogger<PlexWatchlistImport> logger, IExternalRepository<PlexWatchlistHistory> watchlistRepo, IRepository<PlexWatchlistUserError> userError)
|
||||||
{
|
{
|
||||||
_plexApi = plexApi;
|
_plexApi = plexApi;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
|
@ -45,6 +47,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
_hub = hub;
|
_hub = hub;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_watchlistRepo = watchlistRepo;
|
_watchlistRepo = watchlistRepo;
|
||||||
|
_userError = userError;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext context)
|
public async Task Execute(IJobExecutionContext context)
|
||||||
|
@ -64,9 +67,35 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Check if the user has errors and the token is the same (not refreshed)
|
||||||
|
var failedUser = await _userError.GetAll().Where(x => x.UserId == user.Id).FirstOrDefaultAsync();
|
||||||
|
if (failedUser != null)
|
||||||
|
{
|
||||||
|
if (failedUser.MediaServerToken.Equals(user.MediaServerToken))
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Skipping Plex Watchlist Import for user '{user.UserName}' as they failed previously and the token has not yet been refreshed");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// remove that guy
|
||||||
|
await _userError.Delete(failedUser);
|
||||||
|
failedUser = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogDebug($"Starting Watchlist Import for {user.UserName} with token {user.MediaServerToken}");
|
_logger.LogDebug($"Starting Watchlist Import for {user.UserName} with token {user.MediaServerToken}");
|
||||||
var watchlist = await _plexApi.GetWatchlist(user.MediaServerToken, context?.CancellationToken ?? CancellationToken.None);
|
var watchlist = await _plexApi.GetWatchlist(user.MediaServerToken, context?.CancellationToken ?? CancellationToken.None);
|
||||||
|
if (watchlist?.AuthError ?? false)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Auth failed for user '{user.UserName}'. Need to re-authenticate with Ombi.");
|
||||||
|
await _userError.Add(new PlexWatchlistUserError
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
MediaServerToken = user.MediaServerToken,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (watchlist == null || !(watchlist.MediaContainer?.Metadata?.Any() ?? false))
|
if (watchlist == null || !(watchlist.MediaContainer?.Metadata?.Any() ?? false))
|
||||||
{
|
{
|
||||||
_logger.LogDebug($"No watchlist found for {user.UserName}");
|
_logger.LogDebug($"No watchlist found for {user.UserName}");
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace Ombi.Schedule.Jobs.Sonarr
|
||||||
{
|
{
|
||||||
public class SonarrSync : ISonarrSync
|
public class SonarrSync : ISonarrSync
|
||||||
{
|
{
|
||||||
public SonarrSync(ISettingsService<SonarrSettings> s, ISonarrApi api, ILogger<SonarrSync> l, ExternalContext ctx,
|
public SonarrSync(ISettingsService<SonarrSettings> s, ISonarrV3Api api, ILogger<SonarrSync> l, ExternalContext ctx,
|
||||||
IMovieDbApi movieDbApi)
|
IMovieDbApi movieDbApi)
|
||||||
{
|
{
|
||||||
_settings = s;
|
_settings = s;
|
||||||
|
@ -35,7 +35,7 @@ namespace Ombi.Schedule.Jobs.Sonarr
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ISettingsService<SonarrSettings> _settings;
|
private readonly ISettingsService<SonarrSettings> _settings;
|
||||||
private readonly ISonarrApi _api;
|
private readonly ISonarrV3Api _api;
|
||||||
private readonly ILogger<SonarrSync> _log;
|
private readonly ILogger<SonarrSync> _log;
|
||||||
private readonly ExternalContext _ctx;
|
private readonly ExternalContext _ctx;
|
||||||
private readonly IMovieDbApi _movieDbApi;
|
private readonly IMovieDbApi _movieDbApi;
|
||||||
|
@ -74,8 +74,6 @@ namespace Ombi.Schedule.Jobs.Sonarr
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var existingSeries = await _ctx.SonarrCache.Select(x => x.TvDbId).ToListAsync();
|
|
||||||
|
|
||||||
var sonarrCacheToSave = new HashSet<SonarrCache>();
|
var sonarrCacheToSave = new HashSet<SonarrCache>();
|
||||||
foreach (var id in ids)
|
foreach (var id in ids)
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
public bool UseCustomPage { get; set; }
|
public bool UseCustomPage { get; set; }
|
||||||
public bool HideAvailableFromDiscover { get; set; }
|
public bool HideAvailableFromDiscover { get; set; }
|
||||||
public string Favicon { get; set; }
|
public string Favicon { get; set; }
|
||||||
|
public bool HideAvailableRecentlyRequested { get; set; }
|
||||||
|
|
||||||
public string AddToUrl(string part)
|
public string AddToUrl(string part)
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
public string QualityProfileAnime { get; set; }
|
public string QualityProfileAnime { get; set; }
|
||||||
public string RootPathAnime { get; set; }
|
public string RootPathAnime { get; set; }
|
||||||
public bool AddOnly { get; set; }
|
public bool AddOnly { get; set; }
|
||||||
public bool V3 { get; set; }
|
|
||||||
public int LanguageProfile { get; set; }
|
public int LanguageProfile { get; set; }
|
||||||
public int LanguageProfileAnime { get; set; }
|
public int LanguageProfileAnime { get; set; }
|
||||||
public bool ScanForAvailability { get; set; }
|
public bool ScanForAvailability { get; set; }
|
||||||
|
|
|
@ -45,6 +45,7 @@ namespace Ombi.Store.Context
|
||||||
public DbSet<RequestLog> RequestLogs { get; set; }
|
public DbSet<RequestLog> RequestLogs { get; set; }
|
||||||
public DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; }
|
public DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; }
|
||||||
public DbSet<Votes> Votes { get; set; }
|
public DbSet<Votes> Votes { get; set; }
|
||||||
|
public DbSet<PlexWatchlistUserError> PlexWatchListUserError { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public DbSet<Audit> Audit { get; set; }
|
public DbSet<Audit> Audit { get; set; }
|
||||||
|
@ -212,7 +213,7 @@ namespace Ombi.Store.Context
|
||||||
notificationToAdd = new NotificationTemplates
|
notificationToAdd = new NotificationTemplates
|
||||||
{
|
{
|
||||||
NotificationType = notificationType,
|
NotificationType = notificationType,
|
||||||
Message = "Your TV request for {Title} is now partially available! Season {PartiallyAvailableSeasonNumber} Episodes {PartiallyAvailableEpisodeNumbers}!",
|
Message = "Your TV request for {Title} is now partially available! Episodes {PartiallyAvailableEpisodesList}!",
|
||||||
Subject = "{ApplicationName}: Partially Available Request!",
|
Subject = "{ApplicationName}: Partially Available Request!",
|
||||||
Agent = agent,
|
Agent = agent,
|
||||||
Enabled = true,
|
Enabled = true,
|
||||||
|
|
|
@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
namespace Ombi.Store.Entities
|
namespace Ombi.Store.Entities
|
||||||
{
|
{
|
||||||
public interface IMediaServerContent: IEntity
|
public interface IMediaServerContent : IEntity
|
||||||
{
|
{
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string ImdbId { get; set; }
|
public string ImdbId { get; set; }
|
||||||
|
@ -29,10 +29,8 @@ namespace Ombi.Store.Entities
|
||||||
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
|
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IMediaServerEpisode
|
public interface IMediaServerEpisode : IBaseMediaServerEpisode
|
||||||
{
|
{
|
||||||
public int EpisodeNumber { get; set; }
|
|
||||||
public int SeasonNumber { get; set; }
|
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Season key
|
/// The Season key
|
||||||
|
@ -46,6 +44,12 @@ namespace Ombi.Store.Entities
|
||||||
public IMediaServerContent SeriesIsIn(ICollection<IMediaServerContent> content);
|
public IMediaServerContent SeriesIsIn(ICollection<IMediaServerContent> content);
|
||||||
public bool IsIn(IMediaServerContent content);
|
public bool IsIn(IMediaServerContent content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IBaseMediaServerEpisode
|
||||||
|
{
|
||||||
|
public int EpisodeNumber { get; set; }
|
||||||
|
public int SeasonNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public enum MediaType
|
public enum MediaType
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,7 +36,7 @@ namespace Ombi.Store.Entities
|
||||||
public abstract RecentlyAddedType RecentlyAddedType { get; }
|
public abstract RecentlyAddedType RecentlyAddedType { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class MediaServerEpisode: Entity, IMediaServerEpisode
|
public abstract class MediaServerEpisode : Entity, IMediaServerEpisode
|
||||||
{
|
{
|
||||||
public int EpisodeNumber { get; set; }
|
public int EpisodeNumber { get; set; }
|
||||||
public int SeasonNumber { get; set; }
|
public int SeasonNumber { get; set; }
|
||||||
|
|
11
src/Ombi.Store/Entities/PlexWatchlistUserError.cs
Normal file
11
src/Ombi.Store/Entities/PlexWatchlistUserError.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Entities
|
||||||
|
{
|
||||||
|
[Table(nameof(PlexWatchlistUserError))]
|
||||||
|
public class PlexWatchlistUserError : Entity
|
||||||
|
{
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public string MediaServerToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
namespace Ombi.Store.Entities
|
namespace Ombi.Store.Entities
|
||||||
{
|
{
|
||||||
[Table("SonarrEpisodeCache")]
|
[Table("SonarrEpisodeCache")]
|
||||||
public class SonarrEpisodeCache : Entity
|
public class SonarrEpisodeCache : Entity, IBaseMediaServerEpisode
|
||||||
{
|
{
|
||||||
public int SeasonNumber { get; set; }
|
public int SeasonNumber { get; set; }
|
||||||
public int EpisodeNumber { get; set; }
|
public int EpisodeNumber { get; set; }
|
||||||
|
|
1302
src/Ombi.Store/Migrations/OmbiMySql/20220823190345_PlexWatchlistUserError.Designer.cs
generated
Normal file
1302
src/Ombi.Store/Migrations/OmbiMySql/20220823190345_PlexWatchlistUserError.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,36 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Ombi.Store.Migrations.OmbiMySql
|
||||||
|
{
|
||||||
|
public partial class PlexWatchlistUserError : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PlexWatchlistUserError",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
UserId = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
MediaServerToken = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PlexWatchlistUserError", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PlexWatchlistUserError");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -350,6 +350,23 @@ namespace Ombi.Store.Migrations.OmbiMySql
|
||||||
b.ToTable("AspNetUsers", (string)null);
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Ombi.Store.Entities.PlexWatchlistUserError", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("MediaServerToken")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PlexWatchlistUserError");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
|
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
1300
src/Ombi.Store/Migrations/OmbiSqlite/20220823192128_PlexWatchlistUserError.Designer.cs
generated
Normal file
1300
src/Ombi.Store/Migrations/OmbiSqlite/20220823192128_PlexWatchlistUserError.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,32 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Ombi.Store.Migrations.OmbiSqlite
|
||||||
|
{
|
||||||
|
public partial class PlexWatchlistUserError : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PlexWatchlistUserError",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
UserId = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
MediaServerToken = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PlexWatchlistUserError", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PlexWatchlistUserError");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -348,6 +348,23 @@ namespace Ombi.Store.Migrations.OmbiSqlite
|
||||||
b.ToTable("AspNetUsers", (string)null);
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Ombi.Store.Entities.PlexWatchlistUserError", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("MediaServerToken")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PlexWatchlistUserError");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
|
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
21
src/Ombi.Tests/SignalRHelper.cs
Normal file
21
src/Ombi.Tests/SignalRHelper.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Moq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ombi.Tests
|
||||||
|
{
|
||||||
|
public class SignalRHelper
|
||||||
|
{
|
||||||
|
public static Mock<IHubContext<T>> MockHub<T>() where T : Hub
|
||||||
|
{
|
||||||
|
Mock<IHubClients> mockClients = new Mock<IHubClients>();
|
||||||
|
Mock<IClientProxy> mockClientProxy = new Mock<IClientProxy>();
|
||||||
|
mockClients.Setup(clients => clients.Clients(It.IsAny<IReadOnlyList<string>>())).Returns(mockClientProxy.Object);
|
||||||
|
|
||||||
|
var hubContext = new Mock<IHubContext<T>>();
|
||||||
|
hubContext.Setup(x => x.Clients).Returns(() => mockClients.Object);
|
||||||
|
|
||||||
|
return hubContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,5 +46,7 @@ namespace Ombi.Api.TheMovieDb
|
||||||
Task<List<Language>> GetLanguages(CancellationToken cancellationToken);
|
Task<List<Language>> GetLanguages(CancellationToken cancellationToken);
|
||||||
Task<List<WatchProvidersResults>> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken);
|
Task<List<WatchProvidersResults>> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken);
|
||||||
Task<List<MovieDbSearchResult>> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken);
|
Task<List<MovieDbSearchResult>> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken);
|
||||||
|
Task<MovieDbImages> GetTvImages(string theMovieDbId, CancellationToken token);
|
||||||
|
Task<MovieDbImages> GetMovieImages(string theMovieDbId, CancellationToken token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
44
src/Ombi.TheMovieDbApi/Models/TvImages.cs
Normal file
44
src/Ombi.TheMovieDbApi/Models/TvImages.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
namespace Ombi.Api.TheMovieDb.Models
|
||||||
|
{
|
||||||
|
public class MovieDbImages
|
||||||
|
{
|
||||||
|
public Backdrop[] backdrops { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public Logo[] logos { get; set; }
|
||||||
|
public Poster[] posters { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Backdrop
|
||||||
|
{
|
||||||
|
public float aspect_ratio { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public string iso_639_1 { get; set; }
|
||||||
|
public string file_path { get; set; }
|
||||||
|
public float vote_average { get; set; }
|
||||||
|
public int vote_count { get; set; }
|
||||||
|
public int width { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Logo
|
||||||
|
{
|
||||||
|
public float aspect_ratio { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public string iso_639_1 { get; set; }
|
||||||
|
public string file_path { get; set; }
|
||||||
|
public float vote_average { get; set; }
|
||||||
|
public int vote_count { get; set; }
|
||||||
|
public int width { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Poster
|
||||||
|
{
|
||||||
|
public float aspect_ratio { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public string iso_639_1 { get; set; }
|
||||||
|
public string file_path { get; set; }
|
||||||
|
public float vote_average { get; set; }
|
||||||
|
public int vote_count { get; set; }
|
||||||
|
public int width { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -514,6 +514,22 @@ namespace Ombi.Api.TheMovieDb
|
||||||
return Api.Request<WatchProviders>(request, token);
|
return Api.Request<WatchProviders>(request, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<MovieDbImages> GetTvImages(string theMovieDbId, CancellationToken token)
|
||||||
|
{
|
||||||
|
var request = new Request($"tv/{theMovieDbId}/images", BaseUri, HttpMethod.Get);
|
||||||
|
request.AddQueryString("api_key", ApiToken);
|
||||||
|
|
||||||
|
return Api.Request<MovieDbImages>(request, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<MovieDbImages> GetMovieImages(string theMovieDbId, CancellationToken token)
|
||||||
|
{
|
||||||
|
var request = new Request($"movie/{theMovieDbId}/images", BaseUri, HttpMethod.Get);
|
||||||
|
request.AddQueryString("api_key", ApiToken);
|
||||||
|
|
||||||
|
return Api.Request<MovieDbImages>(request, token);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task AddDiscoverSettings(Request request)
|
private async Task AddDiscoverSettings(Request request)
|
||||||
{
|
{
|
||||||
var settings = await Settings;
|
var settings = await Settings;
|
||||||
|
|
3
src/Ombi/.vscode/settings.json
vendored
3
src/Ombi/.vscode/settings.json
vendored
|
@ -22,7 +22,8 @@
|
||||||
"emby",
|
"emby",
|
||||||
"availability-rules",
|
"availability-rules",
|
||||||
"details",
|
"details",
|
||||||
"requests"
|
"requests",
|
||||||
|
"sonarr"
|
||||||
],
|
],
|
||||||
"rpc.enabled": true
|
"rpc.enabled": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,15 @@ module.exports = {
|
||||||
"addons": [
|
"addons": [
|
||||||
"@storybook/addon-links",
|
"@storybook/addon-links",
|
||||||
"@storybook/addon-essentials",
|
"@storybook/addon-essentials",
|
||||||
"@storybook/addon-interactions"
|
"@storybook/addon-interactions",
|
||||||
|
"@storybook/preset-scss",
|
||||||
],
|
],
|
||||||
"framework": "@storybook/angular",
|
"framework": "@storybook/angular",
|
||||||
"core": {
|
"core": {
|
||||||
"builder": "@storybook/builder-webpack5"
|
"builder": "@storybook/builder-webpack5"
|
||||||
},
|
},
|
||||||
"staticDirs": ['../../wwwroot/images']
|
"staticDirs": [{ from: '../../wwwroot/images', to: 'images' }, { from: '../../wwwroot/translations', to: 'translations'}],
|
||||||
|
"features": {
|
||||||
|
interactionsDebugger: true,
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,4 +2,9 @@
|
||||||
.test-class {
|
.test-class {
|
||||||
background-color: purple;
|
background-color: purple;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #0f171f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,5 +1,8 @@
|
||||||
import { setCompodocJson } from "@storybook/addon-docs/angular";
|
import { setCompodocJson } from "@storybook/addon-docs/angular";
|
||||||
import docJson from "../documentation.json";
|
import docJson from "../documentation.json";
|
||||||
|
|
||||||
|
import '../src/styles/_imports.scss';
|
||||||
|
|
||||||
setCompodocJson(docJson);
|
setCompodocJson(docJson);
|
||||||
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
|
|
|
@ -26,12 +26,12 @@
|
||||||
"@angular/platform-server": "^14.0.0",
|
"@angular/platform-server": "^14.0.0",
|
||||||
"@angular/router": "^14.0.0",
|
"@angular/router": "^14.0.0",
|
||||||
"@angularclass/hmr": "^3.0.0",
|
"@angularclass/hmr": "^3.0.0",
|
||||||
"@microsoft/signalr": "^6.0.7",
|
|
||||||
"@auth0/angular-jwt": "^5.0.2",
|
"@auth0/angular-jwt": "^5.0.2",
|
||||||
"@fortawesome/fontawesome-free": "^6.0.0",
|
"@fortawesome/fontawesome-free": "^6.0.0",
|
||||||
"@fullcalendar/core": "^4.2.0",
|
"@fullcalendar/core": "^4.2.0",
|
||||||
"@fullcalendar/daygrid": "^4.4.0",
|
"@fullcalendar/daygrid": "^4.4.0",
|
||||||
"@fullcalendar/interaction": "^4.2.0",
|
"@fullcalendar/interaction": "^4.2.0",
|
||||||
|
"@microsoft/signalr": "^6.0.7",
|
||||||
"@ngx-translate/core": "^14.0.0",
|
"@ngx-translate/core": "^14.0.0",
|
||||||
"@ngx-translate/http-loader": "^7.0.0",
|
"@ngx-translate/http-loader": "^7.0.0",
|
||||||
"@ngxs/devtools-plugin": "^3.7.3",
|
"@ngxs/devtools-plugin": "^3.7.3",
|
||||||
|
@ -57,16 +57,16 @@
|
||||||
"popper.js": "^1.14.3",
|
"popper.js": "^1.14.3",
|
||||||
"primeicons": "^5.0.0",
|
"primeicons": "^5.0.0",
|
||||||
"primeng": "^13.2.0",
|
"primeng": "^13.2.0",
|
||||||
|
"protractor": "~5.4.0",
|
||||||
"rxjs": "^7.5.4",
|
"rxjs": "^7.5.4",
|
||||||
"sass-recursive-map-merge": "^1.0.1",
|
"sass-recursive-map-merge": "^1.0.1",
|
||||||
"store": "^2.0.12",
|
"store": "^2.0.12",
|
||||||
"ts-md5": "^1.2.7",
|
"ts-md5": "^1.2.7",
|
||||||
"tslib": "^1.10.0",
|
|
||||||
"tslint-angular": "^1.1.2",
|
|
||||||
"zone.js": "~0.11.4",
|
|
||||||
"protractor": "~5.4.0",
|
|
||||||
"ts-node": "~5.0.1",
|
"ts-node": "~5.0.1",
|
||||||
"tslint": "^5.12.0"
|
"tslib": "^1.10.0",
|
||||||
|
"tslint": "^5.12.0",
|
||||||
|
"tslint-angular": "^1.1.2",
|
||||||
|
"zone.js": "~0.11.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^14.0.0",
|
"@angular-devkit/build-angular": "^14.0.0",
|
||||||
|
@ -75,7 +75,6 @@
|
||||||
"@angular/language-service": "^14.0.0",
|
"@angular/language-service": "^14.0.0",
|
||||||
"@babel/core": "^7.18.9",
|
"@babel/core": "^7.18.9",
|
||||||
"@compodoc/compodoc": "^1.1.19",
|
"@compodoc/compodoc": "^1.1.19",
|
||||||
"@types/node": "^16.11.45",
|
|
||||||
"@storybook/addon-actions": "^6.5.9",
|
"@storybook/addon-actions": "^6.5.9",
|
||||||
"@storybook/addon-essentials": "^6.5.9",
|
"@storybook/addon-essentials": "^6.5.9",
|
||||||
"@storybook/addon-interactions": "^6.5.9",
|
"@storybook/addon-interactions": "^6.5.9",
|
||||||
|
@ -84,8 +83,10 @@
|
||||||
"@storybook/builder-webpack5": "^6.5.9",
|
"@storybook/builder-webpack5": "^6.5.9",
|
||||||
"@storybook/manager-webpack5": "^6.5.9",
|
"@storybook/manager-webpack5": "^6.5.9",
|
||||||
"@storybook/testing-library": "^0.0.13",
|
"@storybook/testing-library": "^0.0.13",
|
||||||
|
"@storybook/preset-scss": "^1.0.3",
|
||||||
"@types/jasmine": "~3.6.7",
|
"@types/jasmine": "~3.6.7",
|
||||||
"@types/jasminewd2": "~2.0.8",
|
"@types/jasminewd2": "~2.0.8",
|
||||||
|
"@types/node": "^16.11.45",
|
||||||
"babel-loader": "^8.2.5",
|
"babel-loader": "^8.2.5",
|
||||||
"chromatic": "^6.7.1",
|
"chromatic": "^6.7.1",
|
||||||
"codelyzer": "^6.0.1",
|
"codelyzer": "^6.0.1",
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
// also exported from '@storybook/angular' if you can deal with breaking changes in 6.1
|
||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { Story, Meta, moduleMetadata } from '@storybook/angular';
|
||||||
|
import { ButtonComponent } from './button.component';
|
||||||
|
|
||||||
|
// More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
|
||||||
|
export default {
|
||||||
|
title: 'Button Component',
|
||||||
|
component: ButtonComponent,
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_BASE_HREF,
|
||||||
|
useValue: {}
|
||||||
|
},
|
||||||
|
MatButtonModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
// More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args
|
||||||
|
const Template: Story<ButtonComponent> = (args: ButtonComponent) => ({
|
||||||
|
props: args,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Primary = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
Primary.args = {
|
||||||
|
type: 'primary',
|
||||||
|
text: 'Primary',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Secondary = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
Secondary.args = {
|
||||||
|
type: 'accent',
|
||||||
|
text: 'Secondary',
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { OmbiCommonModules } from "../modules";
|
||||||
|
import { ChangeDetectionStrategy, Component, Input } from "@angular/core";
|
||||||
|
import { MatButtonModule } from "@angular/material/button";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'ombi-button',
|
||||||
|
imports: [...OmbiCommonModules, MatButtonModule],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
template: `
|
||||||
|
<button [id]="id" [type]="type" [class]="class" [data-toggle]="dataToggle" mat-raised-button [data-target]="dataTarget">{{text}}</button>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class ButtonComponent {
|
||||||
|
|
||||||
|
@Input() public text: string;
|
||||||
|
|
||||||
|
@Input() public id: string;
|
||||||
|
@Input() public type: string = "primary";
|
||||||
|
@Input() public class: string;
|
||||||
|
@Input('data-toggle') public dataToggle: string;
|
||||||
|
@Input('data-target') public dataTarget: string;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<div id="detailed-{{request.mediaId}}" class="detailed-container" [style.background-image]="background">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-5 col-lg-5 col-md-5 col-sm-12 posterColumn">
|
||||||
|
<ombi-image (click)="click()" [src]="request.posterPath" [type]="request.type" class="poster" alt="{{request.title}}">
|
||||||
|
</ombi-image>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-7 col-lg-7 col-md-7 col-sm-12">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 title">
|
||||||
|
<div class="text-right year"><sup>{{request.releaseDate | date:'yyyy'}}</sup></div>
|
||||||
|
<h3 id="detailed-request-title-{{request.mediaId}}">{{request.title}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-12" *ngIf="request.username">
|
||||||
|
<p id="detailed-request-requestedby-{{request.mediaId}}">{{'MediaDetails.RequestedBy' | translate}} {{request.username}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<p id="detailed-request-date-{{request.mediaId}}">{{'MediaDetails.OnDate' | translate}} {{request.requestDate | amFromUtc | amLocal | amUserLocale | amDateFormat: 'l LT'}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<p id="detailed-request-status-{{request.mediaId}}">{{'MediaDetails.Status' | translate}} <span class="badge badge-{{getClass(request)}}">{{getStatus(request) | translate}}</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row action-items">
|
||||||
|
<div class="col-12" *ngIf="isAdmin">
|
||||||
|
<button *ngIf="!request.approved" id="detailed-request-approve-{{request.mediaId}}" color="accent" mat-raised-button (click)="approve()">{{'Common.Approve' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,54 @@
|
||||||
|
@import "~styles/variables.scss";
|
||||||
|
|
||||||
|
.detailed-container {
|
||||||
|
width: 400px;
|
||||||
|
height: 250px;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid $ombi-background-primary-accent;
|
||||||
|
|
||||||
|
@media (max-width:768px) {
|
||||||
|
width: 200px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: $ombi-background-accent;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
|
||||||
|
.overview {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 4;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year {
|
||||||
|
@media (max-width:768px) {
|
||||||
|
padding-top: 3%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .poster {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 10px;
|
||||||
|
opacity: 1;
|
||||||
|
display: block;
|
||||||
|
height: 225px;
|
||||||
|
width: 100%;
|
||||||
|
transition: .5s ease;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
border: 1px solid #35465c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-items {
|
||||||
|
@media (min-width:768px) {
|
||||||
|
bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
// also exported from '@storybook/angular' if you can deal with breaking changes in 6.1
|
||||||
|
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||||
|
import { Story, Meta, moduleMetadata } from '@storybook/angular';
|
||||||
|
import { IRecentlyRequested, RequestType } from '../../interfaces';
|
||||||
|
import { DetailedCardComponent } from './detailed-card.component';
|
||||||
|
import { TranslateModule } from "@ngx-translate/core";
|
||||||
|
import { ImageService } from "../../services/image.service";
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { PipeModule } from '../../pipes/pipe.module';
|
||||||
|
import { ImageComponent } from '../image/image.component';
|
||||||
|
|
||||||
|
function imageServiceMock(): Partial<ImageService> {
|
||||||
|
return {
|
||||||
|
getMoviePoster: () : Observable<string> => of("https://assets.fanart.tv/fanart/movies/603/movieposter/the-matrix-52256ae1021be.jpg"),
|
||||||
|
getMovieBackground : () : Observable<string> => of("https://assets.fanart.tv/fanart/movies/603/movieposter/the-matrix-52256ae1021be.jpg"),
|
||||||
|
getTmdbTvPoster : () : Observable<string> => of("/bfxwMdQyJc0CL24m5VjtWAN30mt.jpg"),
|
||||||
|
getTmdbTvBackground : () : Observable<string> => of("/bfxwMdQyJc0CL24m5VjtWAN30mt.jpg"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
|
||||||
|
export default {
|
||||||
|
title: 'Detailed Card Component',
|
||||||
|
component: DetailedCardComponent,
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_BASE_HREF,
|
||||||
|
useValue: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ImageService,
|
||||||
|
useValue: imageServiceMock()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
CommonModule,
|
||||||
|
ImageComponent,
|
||||||
|
SharedModule,
|
||||||
|
PipeModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
// More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args
|
||||||
|
const Template: Story<DetailedCardComponent> = (args: DetailedCardComponent) => ({
|
||||||
|
props: args,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NewMovieRequest = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
NewMovieRequest.args = {
|
||||||
|
request: {
|
||||||
|
title: 'The Matrix',
|
||||||
|
approved: false,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: false,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
username: 'John Doe',
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.movie,
|
||||||
|
mediaId: '603',
|
||||||
|
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
isAdmin: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MovieNoUsername = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
MovieNoUsername.args = {
|
||||||
|
request: {
|
||||||
|
title: 'The Matrix',
|
||||||
|
approved: false,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: false,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.movie,
|
||||||
|
mediaId: '603',
|
||||||
|
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AvailableMovie = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
AvailableMovie.args = {
|
||||||
|
request: {
|
||||||
|
title: 'The Matrix',
|
||||||
|
approved: false,
|
||||||
|
available: true,
|
||||||
|
tvPartiallyAvailable: false,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
username: 'John Doe',
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.movie,
|
||||||
|
mediaId: '603',
|
||||||
|
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ApprovedMovie = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
ApprovedMovie.args = {
|
||||||
|
request: {
|
||||||
|
title: 'The Matrix',
|
||||||
|
approved: true,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: false,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
username: 'John Doe',
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.movie,
|
||||||
|
mediaId: '603',
|
||||||
|
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewTvRequest = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
NewTvRequest.args = {
|
||||||
|
request: {
|
||||||
|
title: 'For All Mankind',
|
||||||
|
approved: false,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: false,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
username: 'John Doe',
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.tvShow,
|
||||||
|
mediaId: '603',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const ApprovedTv = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
ApprovedTv.args = {
|
||||||
|
request: {
|
||||||
|
title: 'For All Mankind',
|
||||||
|
approved: true,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: false,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
username: 'John Doe',
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.tvShow,
|
||||||
|
mediaId: '603',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AvailableTv = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
AvailableTv.args = {
|
||||||
|
request: {
|
||||||
|
title: 'For All Mankind',
|
||||||
|
approved: true,
|
||||||
|
available: true,
|
||||||
|
tvPartiallyAvailable: false,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
username: 'John Doe',
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.tvShow,
|
||||||
|
mediaId: '603',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const PartiallyAvailableTv = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
PartiallyAvailableTv.args = {
|
||||||
|
request: {
|
||||||
|
title: 'For All Mankind',
|
||||||
|
approved: true,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: true,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
username: 'John Doe',
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.tvShow,
|
||||||
|
mediaId: '603',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TvNoUsername = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
TvNoUsername.args = {
|
||||||
|
request: {
|
||||||
|
title: 'For All Mankind',
|
||||||
|
approved: true,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: true,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.tvShow,
|
||||||
|
mediaId: '603',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdminNewMovie = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
AdminNewMovie.args = {
|
||||||
|
request: {
|
||||||
|
title: 'The Matrix',
|
||||||
|
approved: false,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: false,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
username: 'John Doe',
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.movie,
|
||||||
|
mediaId: '603',
|
||||||
|
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
isAdmin: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdminTvShow = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
AdminTvShow.args = {
|
||||||
|
request: {
|
||||||
|
title: 'For All Mankind',
|
||||||
|
approved: false,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: true,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.tvShow,
|
||||||
|
mediaId: '603',
|
||||||
|
username: 'John Doe',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
isAdmin: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdminApprovedMovie = Template.bind({});
|
||||||
|
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
AdminApprovedMovie.args = {
|
||||||
|
request: {
|
||||||
|
title: 'The Matrix',
|
||||||
|
approved: true,
|
||||||
|
available: false,
|
||||||
|
tvPartiallyAvailable: false,
|
||||||
|
requestDate: new Date(2022, 1, 1),
|
||||||
|
username: 'John Doe',
|
||||||
|
userId: '12345',
|
||||||
|
type: RequestType.movie,
|
||||||
|
mediaId: '603',
|
||||||
|
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
|
||||||
|
releaseDate: new Date(2020, 1, 1),
|
||||||
|
} as IRecentlyRequested,
|
||||||
|
isAdmin: true,
|
||||||
|
};
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||||
|
import { IRecentlyRequested, RequestType } from "../../interfaces";
|
||||||
|
import { ImageService } from "app/services";
|
||||||
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: false,
|
||||||
|
selector: 'ombi-detailed-card',
|
||||||
|
templateUrl: './detailed-card.component.html',
|
||||||
|
styleUrls: ['./detailed-card.component.scss']
|
||||||
|
})
|
||||||
|
export class DetailedCardComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() public request: IRecentlyRequested;
|
||||||
|
@Input() public isAdmin: boolean = false;
|
||||||
|
@Output() public onClick: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
@Output() public onApprove: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
|
||||||
|
public RequestType = RequestType;
|
||||||
|
public loading: false;
|
||||||
|
|
||||||
|
private $imageSub = new Subject<void>();
|
||||||
|
|
||||||
|
public background: SafeStyle;
|
||||||
|
|
||||||
|
constructor(private imageService: ImageService, private sanitizer: DomSanitizer) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!this.request.posterPath) {
|
||||||
|
this.loadImages();
|
||||||
|
} else {
|
||||||
|
this.request.posterPath = `https://image.tmdb.org/t/p/w300${this.request.posterPath}`;
|
||||||
|
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(https://image.tmdb.org/t/p/w300" + this.request.background + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStatus(request: IRecentlyRequested) {
|
||||||
|
if (request.available) {
|
||||||
|
return "Common.Available";
|
||||||
|
}
|
||||||
|
if (request.tvPartiallyAvailable) {
|
||||||
|
return "Common.PartiallyAvailable";
|
||||||
|
}
|
||||||
|
if (request.approved) {
|
||||||
|
return "Common.Approved";
|
||||||
|
} else {
|
||||||
|
return "Common.Pending";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public click() {
|
||||||
|
this.onClick.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public approve() {
|
||||||
|
this.onApprove.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getClass(request: IRecentlyRequested) {
|
||||||
|
if (request.available || request.tvPartiallyAvailable) {
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
if (request.approved) {
|
||||||
|
return "primary";
|
||||||
|
} else {
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.$imageSub.next();
|
||||||
|
this.$imageSub.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadImages() {
|
||||||
|
switch (this.request.type) {
|
||||||
|
case RequestType.movie:
|
||||||
|
this.imageService.getMoviePoster(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = x);
|
||||||
|
this.imageService.getMovieBackground(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => {
|
||||||
|
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(" + x + ")");
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case RequestType.tvShow:
|
||||||
|
this.imageService.getTmdbTvPoster(Number(this.request.mediaId)).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = `https://image.tmdb.org/t/p/w300${x}`);
|
||||||
|
this.imageService.getTmdbTvBackground(Number(this.request.mediaId)).pipe(takeUntil(this.$imageSub)).subscribe(x => {
|
||||||
|
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(https://image.tmdb.org/t/p/w300" + x + ")");
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
<img src="{{src}}" (onError)="onError($event)" [class]="class" [id]="id" [style]="style"/>
|
<img src="{{src}}" (error)="onError($event)" [class]="class" [id]="id" [style]="style"/>
|
||||||
|
|
|
@ -28,6 +28,8 @@ import { APP_BASE_HREF } from "@angular/common";
|
||||||
private defaultMovie = "/images/default_movie_poster.png";
|
private defaultMovie = "/images/default_movie_poster.png";
|
||||||
private defaultMusic = "i/mages/default-music-placeholder.png";
|
private defaultMusic = "i/mages/default-music-placeholder.png";
|
||||||
|
|
||||||
|
private alreadyErrored = false;
|
||||||
|
|
||||||
constructor (@Inject(APP_BASE_HREF) public href: string) {
|
constructor (@Inject(APP_BASE_HREF) public href: string) {
|
||||||
if (this.href.length > 1) {
|
if (this.href.length > 1) {
|
||||||
this.baseUrl = this.href;
|
this.baseUrl = this.href;
|
||||||
|
@ -35,6 +37,9 @@ import { APP_BASE_HREF } from "@angular/common";
|
||||||
}
|
}
|
||||||
|
|
||||||
public onError(event: any) {
|
public onError(event: any) {
|
||||||
|
if (this.alreadyErrored) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// set to a placeholder
|
// set to a placeholder
|
||||||
switch(this.type) {
|
switch(this.type) {
|
||||||
case RequestType.movie:
|
case RequestType.movie:
|
||||||
|
@ -48,10 +53,11 @@ import { APP_BASE_HREF } from "@angular/common";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.alreadyErrored = true;
|
||||||
// Retry the original image
|
// Retry the original image
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
event.target.src = this.src;
|
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
event.target.src = this.src;
|
||||||
}, Math.floor(Math.random() * (7000 - 1000 + 1)) + 1000);
|
}, Math.floor(Math.random() * (7000 - 1000 + 1)) + 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
export * from "./image-background/image-background.component";
|
export * from "./image-background/image-background.component";
|
||||||
export * from "./image/image.component";
|
export * from "./image/image.component";
|
||||||
|
export * from "./detailed-card/detailed-card.component";
|
|
@ -1,3 +1,4 @@
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { MomentModule } from "ngx-moment";
|
||||||
|
|
||||||
export const OmbiCommonModules = [ CommonModule ];
|
export const OmbiCommonModules = [ CommonModule, MomentModule ];
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { SearchV2Service } from "../../../services";
|
import { SearchV2Service } from "../../../services";
|
||||||
import { IActorCredits, IActorCast } from "../../../interfaces/ISearchTvResultV2";
|
import { IActorCredits, IActorCast, IActorCrew } from "../../../interfaces/ISearchTvResultV2";
|
||||||
import { IDiscoverCardResult } from "../../interfaces";
|
import { IDiscoverCardResult } from "../../interfaces";
|
||||||
import { RequestType } from "../../../interfaces";
|
import { RequestType } from "../../../interfaces";
|
||||||
import { AuthService } from "../../../auth/auth.service";
|
import { AuthService } from "../../../auth/auth.service";
|
||||||
|
@ -38,13 +38,13 @@ export class DiscoverActorComponent implements OnInit {
|
||||||
this.searchService.getMoviesByActor(this.actorId),
|
this.searchService.getMoviesByActor(this.actorId),
|
||||||
this.searchService.getTvByActor(this.actorId)
|
this.searchService.getTvByActor(this.actorId)
|
||||||
]).subscribe(([movie, tv]) => {
|
]).subscribe(([movie, tv]) => {
|
||||||
this.pushDiscoverResults(movie.cast, RequestType.movie);
|
this.pushDiscoverResults(movie.crew, movie.cast, RequestType.movie);
|
||||||
this.pushDiscoverResults(tv.cast, RequestType.tvShow);
|
this.pushDiscoverResults(tv.crew, tv.cast, RequestType.tvShow);
|
||||||
this.finishLoading();
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pushDiscoverResults(cast: IActorCast[], type: RequestType) {
|
pushDiscoverResults(crew: IActorCrew[], cast: IActorCast[], type: RequestType) {
|
||||||
cast.forEach(m => {
|
cast.forEach(m => {
|
||||||
this.discoverResults.push({
|
this.discoverResults.push({
|
||||||
available: false,
|
available: false,
|
||||||
|
@ -62,6 +62,23 @@ export class DiscoverActorComponent implements OnInit {
|
||||||
background: ""
|
background: ""
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
crew.forEach(m => {
|
||||||
|
this.discoverResults.push({
|
||||||
|
available: false,
|
||||||
|
posterPath: m.poster_path ? `https://image.tmdb.org/t/p/w300/${m.poster_path}` : "../../../images/default_movie_poster.png",
|
||||||
|
requested: false,
|
||||||
|
title: m.title,
|
||||||
|
type: type,
|
||||||
|
id: m.id,
|
||||||
|
url: null,
|
||||||
|
rating: 0,
|
||||||
|
overview: m.overview,
|
||||||
|
approved: false,
|
||||||
|
imdbid: "",
|
||||||
|
denied: false,
|
||||||
|
background: ""
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private loading() {
|
private loading() {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
export const ResponsiveOptions = [
|
||||||
|
{
|
||||||
|
breakpoint: '1800px',
|
||||||
|
numVisible: 5,
|
||||||
|
numScroll: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '1650px',
|
||||||
|
numVisible: 3,
|
||||||
|
numScroll: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '1500px',
|
||||||
|
numVisible: 3,
|
||||||
|
numScroll: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '900px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '768px',
|
||||||
|
numVisible: 2,
|
||||||
|
numScroll: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '660px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '480px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1
|
||||||
|
}
|
||||||
|
];
|
|
@ -1,5 +1,12 @@
|
||||||
<div class="small-middle-container">
|
<div class="small-middle-container">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{'Discovery.RecentlyRequestedTab' | translate}}</h2>
|
||||||
|
<div>
|
||||||
|
<ombi-recently-list [id]="'recentlyRequested'"></ombi-recently-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="section" [hidden]="!showSeasonal">
|
<div class="section" [hidden]="!showSeasonal">
|
||||||
<h2>{{'Discovery.SeasonalTab' | translate}}</h2>
|
<h2>{{'Discovery.SeasonalTab' | translate}}</h2>
|
||||||
<div>
|
<div>
|
||||||
|
@ -29,10 +36,5 @@
|
||||||
<carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
|
<carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="section">
|
|
||||||
<h2>{{'Discovery.RecentlyRequestedTab' | translate}}</h2>
|
|
||||||
<div>
|
|
||||||
<carousel-list [id]="'recentlyRequested'" [discoverType]="DiscoverType.RecentlyRequested"></carousel-list>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
|
@ -7,9 +7,11 @@ import { DiscoverCardComponent } from "./card/discover-card.component";
|
||||||
import { DiscoverCollectionsComponent } from "./collections/discover-collections.component";
|
import { DiscoverCollectionsComponent } from "./collections/discover-collections.component";
|
||||||
import { DiscoverComponent } from "./discover/discover.component";
|
import { DiscoverComponent } from "./discover/discover.component";
|
||||||
import { DiscoverSearchResultsComponent } from "./search-results/search-results.component";
|
import { DiscoverSearchResultsComponent } from "./search-results/search-results.component";
|
||||||
|
import { RecentlyRequestedListComponent } from "./recently-requested-list/recently-requested-list.component";
|
||||||
import { MatDialog } from "@angular/material/dialog";
|
import { MatDialog } from "@angular/material/dialog";
|
||||||
import { RequestServiceV2 } from "../../services/requestV2.service";
|
import { RequestServiceV2 } from "../../services/requestV2.service";
|
||||||
import { Routes } from "@angular/router";
|
import { Routes } from "@angular/router";
|
||||||
|
import { DetailedCardComponent } from "app/components";
|
||||||
|
|
||||||
export const components: any[] = [
|
export const components: any[] = [
|
||||||
DiscoverComponent,
|
DiscoverComponent,
|
||||||
|
@ -18,6 +20,8 @@ export const components: any[] = [
|
||||||
DiscoverActorComponent,
|
DiscoverActorComponent,
|
||||||
DiscoverSearchResultsComponent,
|
DiscoverSearchResultsComponent,
|
||||||
CarouselListComponent,
|
CarouselListComponent,
|
||||||
|
RecentlyRequestedListComponent,
|
||||||
|
DetailedCardComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const providers: any[] = [
|
export const providers: any[] = [
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<div *ngIf="requests$ | async as requests">
|
||||||
|
<div *ngIf="requests.length > 0">
|
||||||
|
<p-carousel #carousel [value]="requests" [numVisible]="3" [numScroll]="1"
|
||||||
|
[responsiveOptions]="responsiveOptions" [page]="0">
|
||||||
|
<ng-template let-result pTemplate="item">
|
||||||
|
<ombi-detailed-card [request]="result" [isAdmin]="isAdmin" (onClick)="navigate(result)"
|
||||||
|
(onApprove)="approve(result)"></ombi-detailed-card>
|
||||||
|
</ng-template>
|
||||||
|
</p-carousel>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,112 @@
|
||||||
|
@import "~styles/variables.scss";
|
||||||
|
|
||||||
|
.ombi-card {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
::ng-deep .p-carousel-indicators {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.image {
|
||||||
|
border-radius: 10px;
|
||||||
|
opacity: 1;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
transition: .5s ease;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.middle {
|
||||||
|
transition: .5s ease;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 75%;
|
||||||
|
width: 90%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
-ms-transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.c {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c:hover .image {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c:hover .middle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-text {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.top-left {
|
||||||
|
font-size: 14px;
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
margin-top:-61px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:520px){
|
||||||
|
.right{
|
||||||
|
margin-top:0px;
|
||||||
|
text-align: center;;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.discover-filter-buttons-group {
|
||||||
|
background: $ombi-background-primary;
|
||||||
|
border: 1px solid #293a4c;
|
||||||
|
border-radius: 30px;
|
||||||
|
color:#fff;
|
||||||
|
margin-bottom:10px;
|
||||||
|
margin-right: 30px;
|
||||||
|
|
||||||
|
.discover-filter-button{
|
||||||
|
background:inherit;
|
||||||
|
color:inherit;
|
||||||
|
padding:0 0px;
|
||||||
|
border-radius: 30px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
border-left:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-button-toggle-appearance-standard .mat-button-toggle-label-content{
|
||||||
|
line-height:40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-active{
|
||||||
|
background:#293a4c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
::ng-deep .discover-filter-button .mat-button-toggle-button:focus{
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-skeleton {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width:755px){
|
||||||
|
::ng-deep .p-carousel-item{
|
||||||
|
flex: 1 0 200px !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { Component, OnInit, Input, ViewChild, OnDestroy } from "@angular/core";
|
||||||
|
import { IRecentlyRequested, IRequestEngineResult, RequestType } from "../../../interfaces";
|
||||||
|
import { Carousel } from 'primeng/carousel';
|
||||||
|
import { ResponsiveOptions } from "../carousel.options";
|
||||||
|
import { RequestServiceV2 } from "app/services/requestV2.service";
|
||||||
|
import { finalize, map, Observable, Subject, takeUntil, tap } from "rxjs";
|
||||||
|
import { Router } from "@angular/router";
|
||||||
|
import { AuthService } from "app/auth/auth.service";
|
||||||
|
import { NotificationService, RequestService } from "app/services";
|
||||||
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
|
|
||||||
|
export enum DiscoverType {
|
||||||
|
Upcoming,
|
||||||
|
Trending,
|
||||||
|
Popular,
|
||||||
|
RecentlyRequested,
|
||||||
|
Seasonal,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "ombi-recently-list",
|
||||||
|
templateUrl: "./recently-requested-list.component.html",
|
||||||
|
styleUrls: ["./recently-requested-list.component.scss"],
|
||||||
|
})
|
||||||
|
export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
@Input() public id: string;
|
||||||
|
@Input() public isAdmin: boolean;
|
||||||
|
@ViewChild('carousel', {static: false}) carousel: Carousel;
|
||||||
|
|
||||||
|
public requests$: Observable<IRecentlyRequested[]>;
|
||||||
|
|
||||||
|
public responsiveOptions: any;
|
||||||
|
public RequestType = RequestType;
|
||||||
|
public loadingFlag: boolean;
|
||||||
|
public DiscoverType = DiscoverType;
|
||||||
|
|
||||||
|
private $loadSub = new Subject<void>();
|
||||||
|
|
||||||
|
constructor(private requestServiceV2: RequestServiceV2,
|
||||||
|
private requestService: RequestService,
|
||||||
|
private router: Router,
|
||||||
|
private authService: AuthService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
private translateService: TranslateService) {
|
||||||
|
Carousel.prototype.onTouchMove = () => {};
|
||||||
|
this.responsiveOptions = ResponsiveOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.$loadSub.next();
|
||||||
|
this.$loadSub.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
this.loadData();
|
||||||
|
this.isAdmin = this.authService.isAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
public navigate(request: IRecentlyRequested) {
|
||||||
|
this.router.navigate([this.generateDetailsLink(request), request.mediaId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public approve(request: IRecentlyRequested) {
|
||||||
|
switch(request.type) {
|
||||||
|
case RequestType.movie:
|
||||||
|
this.requestService.approveMovie({id: request.requestId, is4K: false}).pipe(
|
||||||
|
map((res) => this.handleApproval(res, request))
|
||||||
|
).subscribe();
|
||||||
|
break;
|
||||||
|
case RequestType.tvShow:
|
||||||
|
this.requestService.approveChild({id: request.requestId}).pipe(
|
||||||
|
tap((res) => this.handleApproval(res, request))
|
||||||
|
).subscribe();
|
||||||
|
break;
|
||||||
|
case RequestType.album:
|
||||||
|
this.requestService.approveAlbum({id: request.requestId}).pipe(
|
||||||
|
tap((res) => this.handleApproval(res, request))
|
||||||
|
).subscribe();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleApproval(result: IRequestEngineResult, request: IRecentlyRequested) {
|
||||||
|
if (result.result) {
|
||||||
|
this.notificationService.success(this.translateService.instant("Requests.SuccessfullyApproved"));
|
||||||
|
request.approved = true;
|
||||||
|
} else {
|
||||||
|
this.notificationService.error(result.errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateDetailsLink(request: IRecentlyRequested): string {
|
||||||
|
switch (request.type) {
|
||||||
|
case RequestType.movie:
|
||||||
|
return `/details/movie/`;
|
||||||
|
case RequestType.tvShow:
|
||||||
|
return `/details/tv/`;
|
||||||
|
case RequestType.album: //Actually artist
|
||||||
|
return `/details/artist/`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadData() {
|
||||||
|
this.requests$ = this.requestServiceV2.getRecentlyRequested().pipe(
|
||||||
|
tap(() => this.loading()),
|
||||||
|
takeUntil(this.$loadSub),
|
||||||
|
finalize(() => this.finishLoading())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private loading() {
|
||||||
|
this.loadingFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private finishLoading() {
|
||||||
|
this.loadingFlag = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ import { ImageComponent } from 'app/components';
|
||||||
MatButtonToggleModule,
|
MatButtonToggleModule,
|
||||||
InfiniteScrollModule,
|
InfiniteScrollModule,
|
||||||
SkeletonModule,
|
SkeletonModule,
|
||||||
ImageComponent
|
ImageComponent,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
...fromComponents.components
|
...fromComponents.components
|
||||||
|
|
|
@ -107,3 +107,16 @@ export interface IPlexServerResponse {
|
||||||
port: string;
|
port: string;
|
||||||
scheme: string;
|
scheme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPlexWatchlistUsers {
|
||||||
|
userId: string;
|
||||||
|
syncStatus: WatchlistSyncStatus;
|
||||||
|
userName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WatchlistSyncStatus
|
||||||
|
{
|
||||||
|
Successful,
|
||||||
|
Failed,
|
||||||
|
NotEnabled
|
||||||
|
}
|
19
src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts
Normal file
19
src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { RequestType } from "./IRequestModel";
|
||||||
|
|
||||||
|
export interface IRecentlyRequested {
|
||||||
|
requestId: number;
|
||||||
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
available: boolean;
|
||||||
|
tvPartiallyAvailable: boolean;
|
||||||
|
requestDate: Date;
|
||||||
|
title: string;
|
||||||
|
overview: string;
|
||||||
|
releaseDate: Date;
|
||||||
|
approved: boolean;
|
||||||
|
mediaId: string;
|
||||||
|
type: RequestType;
|
||||||
|
|
||||||
|
posterPath: string;
|
||||||
|
background: string;
|
||||||
|
}
|
|
@ -142,7 +142,6 @@ export interface ISonarrSettings extends IExternalSettings {
|
||||||
rootPathAnime: string;
|
rootPathAnime: string;
|
||||||
fullRootPath: string;
|
fullRootPath: string;
|
||||||
addOnly: boolean;
|
addOnly: boolean;
|
||||||
v3: boolean;
|
|
||||||
languageProfile: number;
|
languageProfile: number;
|
||||||
languageProfileAnime: number;
|
languageProfileAnime: number;
|
||||||
scanForAvailability: boolean;
|
scanForAvailability: boolean;
|
||||||
|
|
|
@ -21,3 +21,4 @@ export * from "./IVote";
|
||||||
export * from "./IFailedRequests";
|
export * from "./IFailedRequests";
|
||||||
export * from "./IHub";
|
export * from "./IHub";
|
||||||
export * from "./ITester";
|
export * from "./ITester";
|
||||||
|
export * from "./IRecentlyRequested";
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ArtistDetailsComponent } from "./artist/artist-details.component";
|
||||||
import { ArtistInformationPanel } from "./artist/panels/artist-information-panel/artist-information-panel.component";
|
import { ArtistInformationPanel } from "./artist/panels/artist-information-panel/artist-information-panel.component";
|
||||||
import { ArtistReleasePanel } from "./artist/panels/artist-release-panel/artist-release-panel.component";
|
import { ArtistReleasePanel } from "./artist/panels/artist-release-panel/artist-release-panel.component";
|
||||||
import { CastCarouselComponent } from "./shared/cast-carousel/cast-carousel.component";
|
import { CastCarouselComponent } from "./shared/cast-carousel/cast-carousel.component";
|
||||||
|
import { CrewCarouselComponent } from "./shared/crew-carousel/crew-carousel.component";
|
||||||
import { DenyDialogComponent } from "./shared/deny-dialog/deny-dialog.component";
|
import { DenyDialogComponent } from "./shared/deny-dialog/deny-dialog.component";
|
||||||
import { IssuesPanelComponent } from "./shared/issues-panel/issues-panel.component";
|
import { IssuesPanelComponent } from "./shared/issues-panel/issues-panel.component";
|
||||||
import { MediaPosterComponent } from "./shared/media-poster/media-poster.component";
|
import { MediaPosterComponent } from "./shared/media-poster/media-poster.component";
|
||||||
|
@ -32,6 +33,7 @@ export const components: any[] = [
|
||||||
SocialIconsComponent,
|
SocialIconsComponent,
|
||||||
MediaPosterComponent,
|
MediaPosterComponent,
|
||||||
CastCarouselComponent,
|
CastCarouselComponent,
|
||||||
|
CrewCarouselComponent,
|
||||||
DenyDialogComponent,
|
DenyDialogComponent,
|
||||||
TvRequestsPanelComponent,
|
TvRequestsPanelComponent,
|
||||||
MovieAdvancedOptionsComponent,
|
MovieAdvancedOptionsComponent,
|
||||||
|
|
|
@ -10,9 +10,8 @@
|
||||||
|
|
||||||
<social-icons [homepage]="movie.homepage" [theMoviedbId]="movie.id"
|
<social-icons [homepage]="movie.homepage" [theMoviedbId]="movie.id"
|
||||||
[hasTrailer]="movie.videos?.results?.length > 0" [imdbId]="movie.imdbId"
|
[hasTrailer]="movie.videos?.results?.length > 0" [imdbId]="movie.imdbId"
|
||||||
[twitter]="movie.externalIds.twitterId" [facebook]="movie.externalIds.facebookId"
|
[twitter]="movie.externalIds?.twitterId" [facebook]="movie.externalIds?.facebookId"
|
||||||
[instagram]="movie.externalIds.instagramId" [available]="movie.available" [plexUrl]="movie.plexUrl"
|
[instagram]="movie.externalIds?.instagramId" [available]="movie.available" [isAdmin]="isAdmin"
|
||||||
[embyUrl]="movie.embyUrl" [jellyfinUrl]="movie.jellyfinUrl" [isAdmin]="isAdmin"
|
|
||||||
[canShowAdvanced]="showAdvanced && movieRequest" [type]="requestType" [has4KRequest]="movie.has4KRequest"
|
[canShowAdvanced]="showAdvanced && movieRequest" [type]="requestType" [has4KRequest]="movie.has4KRequest"
|
||||||
(openTrailer)="openDialog()" (onAdvancedOptions)="openAdvancedOptions()"
|
(openTrailer)="openDialog()" (onAdvancedOptions)="openAdvancedOptions()"
|
||||||
(onReProcessRequest)="reProcessRequest(false)" (onReProcess4KRequest)="reProcessRequest(true)">
|
(onReProcessRequest)="reProcessRequest(false)" (onReProcess4KRequest)="reProcessRequest(true)">
|
||||||
|
@ -198,13 +197,19 @@
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<cast-carousel [cast]="movie.credits.cast"></cast-carousel>
|
<cast-carousel [cast]="movie.credits.cast"></cast-carousel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<crew-carousel [crew]="movie.credits.crew"></crew-carousel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- <div class="row card-spacer" *ngIf="movie.videos?.results?.length > 0">
|
<!-- <div class="row card-spacer" *ngIf="movie.videos?.results?.length > 0">
|
||||||
|
|
||||||
<div class="col-md-6" *ngFor="let video of movie.videos?.results">
|
<div class="col-md-6" *ngFor="let video of movie.videos?.results">
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Component, OnInit, ViewEncapsulation } from "@angular/core";
|
||||||
import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService, SettingsStateService } from "../../../services";
|
import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService, SettingsStateService } from "../../../services";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2";
|
import { ICrewViewModel, ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2";
|
||||||
import { MatDialog } from "@angular/material/dialog";
|
import { MatDialog } from "@angular/material/dialog";
|
||||||
import { YoutubeTrailerComponent } from "../shared/youtube-trailer.component";
|
import { YoutubeTrailerComponent } from "../shared/youtube-trailer.component";
|
||||||
import { AuthService } from "../../../auth/auth.service";
|
import { AuthService } from "../../../auth/auth.service";
|
||||||
|
@ -82,6 +82,7 @@ export class MovieDetailsComponent implements OnInit{
|
||||||
this.searchService.getMovieByImdbId(this.imdbId).subscribe(async x => {
|
this.searchService.getMovieByImdbId(this.imdbId).subscribe(async x => {
|
||||||
this.movie = x;
|
this.movie = x;
|
||||||
this.checkPoster();
|
this.checkPoster();
|
||||||
|
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew);
|
||||||
if (this.movie.requestId > 0) {
|
if (this.movie.requestId > 0) {
|
||||||
// Load up this request
|
// Load up this request
|
||||||
this.hasRequest = true;
|
this.hasRequest = true;
|
||||||
|
@ -93,6 +94,7 @@ export class MovieDetailsComponent implements OnInit{
|
||||||
this.searchService.getFullMovieDetails(this.theMovidDbId).subscribe(async x => {
|
this.searchService.getFullMovieDetails(this.theMovidDbId).subscribe(async x => {
|
||||||
this.movie = x;
|
this.movie = x;
|
||||||
this.checkPoster();
|
this.checkPoster();
|
||||||
|
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew);
|
||||||
if (this.movie.requestId > 0) {
|
if (this.movie.requestId > 0) {
|
||||||
// Load up this request
|
// Load up this request
|
||||||
this.hasRequest = true;
|
this.hasRequest = true;
|
||||||
|
@ -319,4 +321,16 @@ export class MovieDetailsComponent implements OnInit{
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private orderCrew(crew: ICrewViewModel[]): ICrewViewModel[] {
|
||||||
|
return crew.sort((a, b) => {
|
||||||
|
if (a.job === "Director") {
|
||||||
|
return -1;
|
||||||
|
} else if (b.job === "Director") {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<a *ngIf="item.profile_path" [routerLink]="'/discover/actor/' + item.id">
|
<a *ngIf="item.profile_path" [routerLink]="'/discover/actor/' + item.id">
|
||||||
<ombi-image class="cast-profile-img" src="https://image.tmdb.org/t/p/w300{{item.profile_path}}"></ombi-image>
|
<ombi-image class="cast-profile-img" src="https://image.tmdb.org/t/p/w300{{item.profile_path}}"></ombi-image>
|
||||||
</a>
|
</a>
|
||||||
<!-- TODO get profile image default -->
|
<i *ngIf="!item.image && !item.profile_path" class="fa-solid fa-user-large fa-2xl crew-profile-img" aria-hidden="true"></i>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<mat-card class="mat-elevation-z8 spacing-below">
|
||||||
|
<mat-card-header>{{'MediaDetails.Crews.CrewTitle' | translate}}</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<p-carousel [value]="crew" easing="easeOutStrong" [responsiveOptions]="responsiveOptions" [numVisible]="5" >
|
||||||
|
<ng-template let-item pTemplate="item">
|
||||||
|
<div class="row justify-content-md-center mat-card mat-card-flat carousel-item">
|
||||||
|
<div [routerLink]="'/discover/actor/' + item.id" class="bottom-space link">
|
||||||
|
<a *ngIf="item.image">
|
||||||
|
<img class="crew-profile-img" src="https://image.tmdb.org/t/p/w300{{item.image}}">
|
||||||
|
</a>
|
||||||
|
<a *ngIf="item.profile_path">
|
||||||
|
<img class="crew-profile-img" src="https://image.tmdb.org/t/p/w300{{item.profile_path}}">
|
||||||
|
</a>
|
||||||
|
<i *ngIf="!item.image && !item.profile_path" class="fa-solid fa-user-large fa-2xl crew-profile-img" aria-hidden="true"></i>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<span *ngIf="item.name"><strong>{{item.name}}</strong></span>
|
||||||
|
<span *ngIf="item.person"><strong>{{item.person}}</strong></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<span *ngIf="item.job"><small>{{item.job}}</small></span>
|
||||||
|
<span *ngIf="item.department"><small>{{item.position}}</small></span>
|
||||||
|
<span *ngIf="item.title"><small>{{item.title}}</small></span>
|
||||||
|
<span *ngIf="item.overview"><small>{{item.overview}}</small></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-carousel>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
|
@ -0,0 +1,87 @@
|
||||||
|
@import "~@angular/material/theming";
|
||||||
|
@import "~styles/variables.scss";
|
||||||
|
::ng-deep body .ui-carousel .ui-carousel-content .ui-carousel-prev,
|
||||||
|
body .ui-carousel .ui-carousel-content .ui-carousel-next {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: solid 1px rgba(178, 193, 205, 0.64);
|
||||||
|
-moz-border-radius: 50%;
|
||||||
|
-webkit-border-radius: 50%;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 0.2em;
|
||||||
|
color: #333333;
|
||||||
|
-moz-transition: color 0.2s;
|
||||||
|
-o-transition: color 0.2s;
|
||||||
|
-webkit-transition: color 0.2s;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body .ui-carousel .ui-carousel-content .ui-carousel-prev:not(.ui-state-disabled):hover,
|
||||||
|
body .ui-carousel .ui-carousel-content .ui-carousel-next:not(.ui-state-disabled):hover {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #000;
|
||||||
|
border-color: solid 1px rgba(178, 193, 205, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body .ui-carousel .ui-carousel-dots-container .ui-carousel-dot-item>.ui-button {
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body .ui-carousel .ui-carousel-dots-container .ui-carousel-dot-item .ui-carousel-dot-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 6px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.44);
|
||||||
|
margin: 0 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body .ui-carousel .ui-carousel-dots-container .ui-carousel-dot-item .ui-carousel-dot-icon::before {
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body .ui-carousel .ui-carousel-dots-container .ui-carousel-dot-item.ui-state-highlight .ui-carousel-dot-icon {
|
||||||
|
background-color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .ui-carousel-next {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: solid 1px rgba(178, 193, 205, 0.64);
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 0.2em;
|
||||||
|
color: #333333;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .ui-carousel-content button:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-space {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 979px) {
|
||||||
|
|
||||||
|
.crew-profile-img {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 200px;
|
||||||
|
max-height: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 978px) {
|
||||||
|
|
||||||
|
.crew-profile-img {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 100px;
|
||||||
|
max-height: 100px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Component, Input } from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "crew-carousel",
|
||||||
|
templateUrl: "./crew-carousel.component.html",
|
||||||
|
styleUrls: ["./crew-carousel.component.scss"]
|
||||||
|
})
|
||||||
|
export class CrewCarouselComponent {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.responsiveOptions = [
|
||||||
|
{
|
||||||
|
breakpoint: '1024px',
|
||||||
|
numVisible: 5,
|
||||||
|
numScroll: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '768px',
|
||||||
|
numVisible: 3,
|
||||||
|
numScroll: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '560px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input() crew: any[];
|
||||||
|
public responsiveOptions: any[];
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue