Compare commits

...

56 Commits

Author SHA1 Message Date
Colin Gagnaire
925569a17b New: Add support for native Freebox Download Client
(cherry picked from commit fb76c237bfbb8aa43bcdd9ce34d90ea843011cee)
2022-12-17 02:40:51 +00:00
Qstick
da5e35fc25 Unbork UI Build 2022-11-28 22:11:13 -06:00
Mark McDowall
a6a2219bc4 Fixed: Handle Flood reporting errors for completed and stopped downloads
(cherry picked from commit f2b2eb69a3e8b271535bd92dc2f5cbfd45664daf)
2022-11-28 21:25:03 -06:00
Qstick
aa2855a62b Re-saving edited providers will forcibly save them
Fixes #533

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-27 19:32:14 -06:00
Qstick
599f52e72f Fixed: Validation when testing indexers, import lists, connections and download clients
Fixes #1612

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-27 19:10:12 -06:00
Qstick
c5c2b94b9a Fixup Repack Tests 2022-11-25 22:16:34 -06:00
Qstick
b016b36435 Fixed: Validate if equals or child for startup folder
Fixes #1712
Close #1713

(cherry picked from commit 0991cfe27efd6ddb533227b25754661e18d7e9ad)
2022-11-25 22:07:42 -06:00
Qstick
d93a0c27e9 Sentry logging exceptions
Fixes #855

Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
2022-11-25 21:58:58 -06:00
Qstick
eecf08e063 Updated NLog Version
Co-Authored-By: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com>
2022-11-25 21:52:52 -06:00
Qstick
fb1643f630 Lint Fixes 2022-11-25 21:39:44 -06:00
bakerboy448
a061179f6b Fixed: Repack Preference Ignored
Fixes #1867

(cherry picked from commit 04447d9d4db8cc3d54baf0a721f4430cf77908c4)
2022-11-25 21:39:44 -06:00
Stevie Robinson
b441f6c05b Fixed: updated rTorrent download client note
Fixes #1882

(cherry picked from commit 743d28b93a55553ee25381570d0daa04ed2117af)
2022-11-25 21:33:35 -06:00
Chromo-residuum-opec
0904eac300 Update help text for rTorrent download client options
Fixes #1868

(cherry picked from commit d2a23f7bcdf71800f019644d7b6b5d712e311d7f)
2022-11-25 21:31:57 -06:00
Qstick
29e3d8f477 Fixed: Fall back to sorting by release title if artist is not matched
Fixes #1870
2022-11-25 21:29:38 -06:00
Qstick
71bd88e531 New: Add Release group to history for all events
Fixes #1499

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-25 21:28:05 -06:00
Stevie Robinson
0689bec779 New: Add optional Source Title column to history
(cherry picked from commit 581fb2cb3d47d62fe16b840081647056ec77043d)
2022-11-25 21:16:37 -06:00
Devin Buhl
117a5c8010 New: Add application URL to host configuration settings
Fixes #1816
Closes #1817

(cherry picked from commit 762042ba97c2ae689cee32d8e66a458f6d7a8adc)
2022-11-25 21:16:37 -06:00
Qstick
d10d91439f New: Add indexer name to the download report log
Fixes #1904
2022-11-25 21:16:37 -06:00
Mark McDowall
ed0722bae4 New: Validate that naming formats don't contain illegal characters
Fixes #620

(cherry picked from commit 145c644c9d8f1636027da8a782a7e74f3182c678)
2022-11-25 21:16:37 -06:00
psylenced
e69371deca Fix: Trace logging postgres cleanse for large json files.
(cherry picked from commit 2ce9d099e1001eb4fccd61edcb0597782da872d4)
2022-11-25 20:30:54 -06:00
Chris
5ae8deb9d6 Fixed: Postgres secret regex now less greedy
[common]

(cherry picked from commit 812e5ac5a3d76b69c172c5611b315ea809ab4b48)
2022-11-25 20:30:54 -06:00
Chris
291674fc18 Fixed: Regex in log cleanser taking 10+ minutes on messages longer than 100k.
(cherry picked from commit d01bae92bfd68b04c54ab19bafe8af16c68ce711)
2022-11-25 20:30:54 -06:00
Qstick
774cd04d32 Fixed SeedConfigProvider cache refresh after indexer settings change
Fixes #1036

Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
2022-11-25 20:24:00 -06:00
Qstick
3eb0533b17 New: Disable autocomplete of port number
Fixes #1249

Co-Authored-By: Stevie Robinson <stevie.robinson@gmail.com>
2022-11-25 20:21:10 -06:00
Qstick
2fe11ca1a9 Rename NzbSearchService to ReleaseSearchService
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-25 20:18:28 -06:00
Qstick
c8ae6b0299 Fixed: Custom Script Health Issue Level
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-25 20:14:47 -06:00
Qstick
2086ae1e2c Ensure .Mono and .Windows projects have all dependencies in build output
Co-Authored-By: ta264 <ta264@users.noreply.github.com>
2022-11-25 20:10:23 -06:00
Chris
2aeec97d5e Added: Ntfy provider for notifications
(cherry picked from commit f6e6bc98f7846328d158ab2f804ca65b49632912)
2022-11-25 20:02:23 -06:00
Qstick
45e9d14916 New: Base API info endpoint
(cherry picked from commit 5e57ffbcf9ac3a346d4bf2b692248393215bad89)
2022-11-25 20:01:21 -06:00
Qstick
ded45a53f3 Improve page scrollbar
Fixed: Scrolling in Firefox in small window (requires refresh)
New: Style scrollbar in Firefox
Fixed: Scrolling with click and drag
Fixes #3088
Fixes #2706
2022-11-25 20:00:56 -06:00
Mark McDowall
d5cbb8f84f Replace react-custom-scrollbars
(cherry picked from commit 1c6c9a7960865d4bf8f58d5d04bef1aa1409594a)
2022-11-25 20:00:56 -06:00
Mark McDowall
cac0fca0d2 Added react-hooks lint rules
(cherry picked from commit 381d64259396582de8d63ada99333e42cf5e3189)
2022-11-25 19:55:55 -06:00
Mark McDowall
9b5a050c40 Publish ApplicationStartingEvent during startup
(cherry picked from commit 5400bce1295bdc4198d2cfe0b9258bbb7ccf0852)
2022-11-25 19:54:05 -06:00
Weblate
9fa67dbe5f Translated using Weblate (German) [skip ci]
Currently translated at 100.0% (879 of 879 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 62.4% (549 of 879 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 79.8% (702 of 879 strings)

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zalhera <tobias.bechen@gmail.com>
Co-authored-by: marapavelka <mara.pavelka@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translation: Servarr/Readarr
2022-11-25 19:25:06 -06:00
Mark McDowall
505ef5ee63 Fixed: Testing SABnzbd when no categories are configured
(cherry picked from commit 0e31281828c737e3f6eecbb870960194888a970a)
2022-11-25 19:24:38 -06:00
Mark McDowall
87a2d7382e Added SECURITY.md
(cherry picked from commit 80af164385d9087a627142ca2281ae74ac0572af)

(cherry picked from commit aa8e886dab3ffedc835900db39787d47dc12465c)
2022-11-21 19:25:11 -06:00
Qstick
7c74a8df01 Fixed: UI Errors in Add Book page
Fixes #1906
2022-11-03 16:30:54 -05:00
Qstick
f8324b3c92 Create CODE_OF_CONDUCT.md 2022-11-03 15:58:16 -05:00
Weblate
cb03f3bf6b Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 100.0% (879 of 879 strings)

Update translation files [skip ci]

Updated by "Remove blank strings" hook in Weblate.

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 69.0% (607 of 879 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 76.7% (675 of 879 strings)

Co-authored-by: Sincejunly <qq943384135@gmail.com>
Co-authored-by: Tiago Ribeiro <tiago.err@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: libsu <libsu@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2022-11-03 10:42:08 -05:00
ta264
d6f85ec4f9 Use wildcard for FreeBSD itemPattern 2022-11-02 21:22:34 +00:00
Qstick
dd0dd921b0 Ignore brotli test on osx
(cherry picked from commit 2afe6af5a6dac26fe9d2c20e806a93b621e18bbf)
2022-10-30 19:03:10 -05:00
Mark McDowall
1f02423148 Fixed: Series list jump bar click issues
(cherry picked from commit 9c7378625112088d022239fdbdb90c0dc941d61d)
2022-10-20 20:42:53 -05:00
Qstick
7a4fba851f fixup! 2022-10-20 20:39:36 -05:00
Qstick
2d5efc268f New: Retry Postgres connection 3 times (with 5 second sleep) on Startup
(cherry picked from commit 3e700b63c26b247fcac83428ba79e53c88f797ff)

(cherry picked from commit dbca393772d7f558b45a780a6767187bf5900a23)
2022-10-20 20:39:36 -05:00
Mark McDowall
cad5762f31 Sliding expiration for auth cookie and a little clean up
Fixes #1802

(cherry picked from commit 05ee4e644907b7f1e84589465ac9ab1848f5a766)
2022-10-20 20:23:15 -05:00
Qstick
bcc8370d05 Cleanup dual target and mono code
Fixes #1893
Fixes #1808
2022-10-20 20:19:52 -05:00
Qstick
3481168df5 Fixed: qBittorrent Updates
Fixes #1658
Fixes #1536
2022-10-20 20:03:55 -05:00
Qstick
337a30ac0f Add thread to discord notifications 2022-10-19 22:00:13 -05:00
Weblate
eaad4de4dc Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 69.0% (607 of 879 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 65.4% (575 of 879 strings)

Translated using Weblate (Slovak) [skip ci]

Currently translated at 7.3% (65 of 879 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 76.3% (671 of 879 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 76.3% (671 of 879 strings)

Translated using Weblate (Chinese (Traditional) (zh_TW)) [skip ci]

Currently translated at 0.6% (6 of 879 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 79.8% (702 of 879 strings)

Translated using Weblate (Japanese) [skip ci]

Currently translated at 62.3% (548 of 879 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 69.8% (614 of 879 strings)

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 62.3% (548 of 879 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 62.3% (548 of 879 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 63.8% (561 of 879 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 75.9% (668 of 879 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 79.8% (702 of 879 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 62.3% (548 of 879 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 62.3% (548 of 879 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 62.3% (548 of 879 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 62.3% (548 of 879 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 62.4% (549 of 879 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 67.2% (591 of 879 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 65.8% (579 of 879 strings)

Added translation using Weblate (Latvian) [skip ci]

Translated using Weblate (German) [skip ci]

Currently translated at 92.0% (809 of 879 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (879 of 879 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (879 of 879 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 67.9% (597 of 879 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 99.5% (875 of 879 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 75.0% (660 of 879 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 92.0% (809 of 879 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 67.1% (590 of 879 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dainel Amendoeira <daniel@amendoeira.eu>
Co-authored-by: Gian Klug <gian.klug@ict-scouts.ch>
Co-authored-by: Gylesie <github-anon.dasheens@aleeas.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: HiNesslio <chi.lio@shms-mail.ch>
Co-authored-by: Marcin <ml.cichy@gmail.com>
Co-authored-by: Mijail Todorovich <mijailtodorovich+git@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Thomas Schwery <thomas@schwery.me>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: cikyw <cikyw@vomoto.com>
Co-authored-by: fyu0h <biiigback@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: 麋鹿先生 <y.qiuyu@outlook.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_TW/
Translation: Servarr/Readarr
2022-10-16 10:06:07 -05:00
Mark McDowall
b98ae37abf Handle redirects for 308 redirects
(cherry picked from commit 6eed7c8fed096fa1762448bc57876440f542be98)
2022-10-16 10:03:35 -05:00
Stevie Robinson
5dac84f9d8 New: Torrent Seed Ratio no longer advance settings
(cherry picked from commit c98fac65ed8e669c5e97ed3255c424b61fe0e8b3)
2022-10-16 10:03:06 -05:00
bakerboy448
ef21c73619 update feature request template
[skip ci]

(cherry picked from commit 362e664ce6d301e8283d489b3e0d945db46d22d3)
2022-10-16 10:02:34 -05:00
Joe Milazzo
14d74f2eca New: Kavita Connection (#1880)
* Added ability for Readarr to inform Kavita when a change occurs for rescan.

* Use the existing API with a POST rather than a new API.

* Updated some wording

* Fixed PR comments
2022-10-02 18:41:38 -05:00
servarr[bot]
c3cbbb7627 Update Bug Report Template [skip ci] (#1842)
(cherry picked from commit 99e0d42b717b279be0f9005039025cd730f90f6c)

Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2022-09-26 21:05:36 -05:00
servarr[bot]
ad3a58c422 New: Parse version with a space before 'v''
* New: Parse anime version with a space before 'v'

(cherry picked from commit e9123982f33ab35ca022f91f345da05fef23d6dc)

* Delete AnimeVersionFixture.cs

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
Co-authored-by: Qstick <qstick@gmail.com>
2022-09-26 16:55:34 -05:00
Robin Dadswell
347b154882 Update Bug Report Template
[skip ci]

(cherry picked from commit b9185574f3761e125151907e3d31511689a4513e)
2022-09-26 16:42:52 -05:00
155 changed files with 3058 additions and 637 deletions

View File

@@ -5,9 +5,9 @@ body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
options:
- label: I have searched the existing issues
- label: I have searched the existing open and closed issues
required: true
- type: textarea
attributes:
@@ -42,12 +42,14 @@ body:
- **Docker Install**: Yes
- **Using Reverse Proxy**: No
- **Browser**: Firefox 90 (If UI related)
- **Database**: Sqlite 3.36.0
value: |
- OS:
- Readarr:
- Docker Install:
- Using Reverse Proxy:
- Browser:
- Database:
render: markdown
validations:
required: true

View File

@@ -5,9 +5,9 @@ body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature you are requesting.
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch.
options:
- label: I have searched the existing issues
- label: I have searched the existing open and closed issues
required: true
- type: textarea
attributes:

132
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,132 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<development@readarr.com>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

8
SECURITY.md Normal file
View File

@@ -0,0 +1,8 @@
# Security Policy
## Reporting a Vulnerability
Please report (suspected) security vulnerabilities on Discord (preferred) to
any of the Servarr Dev role holders (red names) or via email: development@servarr.com. You will receive a response from
us within 72 hours. If the issue is confirmed, we will release a patch as soon
as possible depending on complexity/severity.

View File

@@ -730,7 +730,7 @@ stages:
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '/$(pattern)'
itemPattern: '**/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- bash: |
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
@@ -1050,3 +1050,4 @@ stages:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
DISCORDCHANNELID: $(discordChannelId)
DISCORDWEBHOOKKEY: $(discordWebhookKey)
DISCORDTHREADID: $(discordThreadId)

View File

@@ -39,6 +39,7 @@ module.exports = {
plugins: [
'filenames',
'react',
'react-hooks',
'simple-import-sort',
'import'
],
@@ -308,7 +309,9 @@ module.exports = {
'react/react-in-jsx-scope': 2,
'react/self-closing-comp': 2,
'react/sort-comp': 2,
'react/jsx-wrap-multilines': 2
'react/jsx-wrap-multilines': 2,
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error'
},
overrides: [
{

View File

@@ -169,6 +169,16 @@ class HistoryRow extends Component {
);
}
if (name === 'sourceTitle') {
return (
<TableRowCell
key={name}
>
{sourceTitle}
</TableRowCell>
);
}
if (name === 'details') {
return (
<TableRowCell

View File

@@ -12,9 +12,9 @@ import ModalBody from 'Components/Modal/ModalBody';
import Portal from 'Components/Portal';
import Scroller from 'Components/Scroller/Scroller';
import { icons, scrollDirections, sizes } from 'Helpers/Props';
import { isMobile as isMobileUtil } from 'Utilities/browser';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import getUniqueElememtId from 'Utilities/getUniqueElementId';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import HintedSelectInputOption from './HintedSelectInputOption';
import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
import TextInput from './TextInput';

View File

@@ -33,7 +33,7 @@ function ConfirmModal(props) {
return () => unbindShortcut('enter', onConfirm);
}
}, [isOpen, onConfirm]);
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
return (
<Modal

View File

@@ -6,9 +6,9 @@ import ReactDOM from 'react-dom';
import FocusLock from 'react-focus-lock';
import ErrorBoundary from 'Components/Error/ErrorBoundary';
import { sizes } from 'Helpers/Props';
import { isIOS } from 'Utilities/browser';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import getUniqueElememtId from 'Utilities/getUniqueElementId';
import { isIOS } from 'Utilities/mobile';
import { setScrollLock } from 'Utilities/scrollLock';
import ModalError from './ModalError';
import styles from './Modal.css';

View File

@@ -1,23 +1,12 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import OverlayScroller from 'Components/Scroller/OverlayScroller';
import Scroller from 'Components/Scroller/Scroller';
import { scrollDirections } from 'Helpers/Props';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import { isLocked } from 'Utilities/scrollLock';
import styles from './PageContentBody.css';
class PageContentBody extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._isMobile = isMobileUtil();
}
//
// Listeners
@@ -41,10 +30,8 @@ class PageContentBody extends Component {
...otherProps
} = this.props;
const ScrollerComponent = this._isMobile ? Scroller : OverlayScroller;
return (
<ScrollerComponent
<Scroller
className={className}
scrollDirection={scrollDirections.VERTICAL}
{...otherProps}
@@ -53,7 +40,7 @@ class PageContentBody extends Component {
<div className={innerClassName}>
{children}
</div>
</ScrollerComponent>
</Scroller>
);
}
}

View File

@@ -1,4 +1,5 @@
.jumpBar {
z-index: $pageJumpBarZIndex;
display: flex;
align-content: stretch;
align-items: stretch;

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Scrollbars } from 'react-custom-scrollbars';
import { Scrollbars } from 'react-custom-scrollbars-2';
import { scrollDirections } from 'Helpers/Props';
import styles from './OverlayScroller.css';

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Measure from 'Components/Measure';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import { isMobile as isMobileUtil } from 'Utilities/browser';
import styles from './SwipeHeader.css';
function cursorPosition(event) {

View File

@@ -5,7 +5,7 @@ import { Manager, Popper, Reference } from 'react-popper';
import Portal from 'Components/Portal';
import { kinds, tooltipPositions } from 'Helpers/Props';
import dimensions from 'Styles/Variables/dimensions';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import { isMobile as isMobileUtil } from 'Utilities/browser';
import styles from './Tooltip.css';
let maxWidth = null;

View File

@@ -148,11 +148,10 @@ class AddNewItem extends Component {
);
} else if (item.book) {
const book = item.book;
const edition = book.editions.find((x) => x.monitored);
return (
<AddNewBookSearchResultConnector
key={item.id}
isExistingBook={'id' in edition && edition.id !== 0}
isExistingBook={'id' in book && book.id !== 0}
isExistingAuthor={'id' in book.author && book.author.id !== 0}
{...book}
/>

View File

@@ -138,17 +138,20 @@ class AddNewBookSearchResult extends Component {
null
}
<Link
className={styles.mbLink}
to={`https://goodreads.com/book/show/${editions[0].foreignEditionId}`}
onPress={this.onTVDBLinkPress}
>
<Icon
className={styles.mbLinkIcon}
name={icons.EXTERNAL_LINK}
size={28}
/>
</Link>
{
editions && editions.length > 1 ?
<Link
className={styles.mbLink}
to={`https://goodreads.com/book/show/${editions[0].foreignEditionId}`}
onPress={this.onTVDBLinkPress}
>
<Icon
className={styles.mbLinkIcon}
name={icons.EXTERNAL_LINK}
size={28}
/>
</Link> : null
}
</div>
</div>
@@ -218,7 +221,7 @@ AddNewBookSearchResult.propTypes = {
overview: PropTypes.string,
ratings: PropTypes.object.isRequired,
author: PropTypes.object,
editions: PropTypes.arrayOf(PropTypes.object).isRequired,
editions: PropTypes.arrayOf(PropTypes.object),
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingBook: PropTypes.bool.isRequired,
isExistingAuthor: PropTypes.bool.isRequired,

View File

@@ -173,7 +173,7 @@ class AddAuthorOptionsForm extends Component {
AddAuthorOptionsForm.propTypes = {
rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired,
monitorNewItems: PropTypes.string.isRequired,
monitorNewItems: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object,
metadataProfileId: PropTypes.object,
showMetadataProfile: PropTypes.bool.isRequired,

View File

@@ -21,6 +21,7 @@ function HostSettings(props) {
port,
urlBase,
instanceName,
applicationUrl,
enableSsl,
sslPort,
sslCertPath,
@@ -58,6 +59,7 @@ function HostSettings(props) {
name="port"
min={1}
max={65535}
autocomplete="off"
helpTextWarning={translate('PortHelpTextWarning')}
onChange={onInputChange}
{...port}
@@ -95,6 +97,21 @@ function HostSettings(props) {
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ApplicationURL')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="applicationUrl"
helpText={translate('ApplicationUrlHelpText')}
onChange={onInputChange}
{...applicationUrl}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}

View File

@@ -19,7 +19,7 @@ function PendingChangesModal(props) {
useEffect(() => {
bindShortcut('enter', onConfirm);
}, [onConfirm]);
}, [bindShortcut, onConfirm]);
return (
<Modal

View File

@@ -6,6 +6,7 @@ import getProviderState from 'Utilities/State/getProviderState';
import { removeItem, set, updateItem } from '../baseActions';
const abortCurrentRequests = {};
let lastSaveData = null;
export function createCancelSaveProviderHandler(section) {
return function(getState, payload, dispatch) {
@@ -27,27 +28,33 @@ function createSaveProviderHandler(section, url, options = {}, removeStale = fal
} = payload;
const saveData = Array.isArray(id) ? id.map((x) => getProviderState({ id: x, ...otherPayload }, getState, section)) : getProviderState({ id, ...otherPayload }, getState, section);
const requestUrl = id ? `${url}/${id}` : url;
const params = { ...queryParams };
// If the user is re-saving the same provider without changes
// force it to be saved. Only applies to editing existing providers.
if (id && _.isEqual(saveData, lastSaveData)) {
params.forceSave = true;
}
lastSaveData = saveData;
const ajaxOptions = {
url: `${url}?${$.param(queryParams, true)}`,
method: 'POST',
url: `${requestUrl}?${$.param(params, true)}`,
method: id ? 'PUT' : 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(saveData)
};
if (id) {
ajaxOptions.method = 'PUT';
if (!Array.isArray(id)) {
ajaxOptions.url = `${url}/${id}?${$.param(queryParams, true)}`;
}
}
const { request, abortRequest } = createAjaxRequest(ajaxOptions);
abortCurrentRequests[section] = abortRequest;
request.done((data) => {
lastSaveData = null;
if (!Array.isArray(data)) {
data = [data];
}

View File

@@ -71,6 +71,11 @@ export const defaultState = {
label: 'Release Group',
isVisible: false
},
{
name: 'sourceTitle',
label: 'Source Title',
isVisible: false
},
{
name: 'details',
columnLabel: 'Details',

View File

@@ -1,4 +1,7 @@
@define-mixin scrollbar {
scrollbar-color: var(--scrollbarBackgroundColor) transparent;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 10px;
height: 10px;

View File

@@ -1,4 +1,5 @@
module.exports = {
pageJumpBarZIndex: 10,
modalZIndex: 1000,
popperZIndex: 2000
};

View File

@@ -20,7 +20,6 @@ class About extends Component {
packageVersion,
packageAuthor,
isNetCore,
isMono,
isDocker,
runtimeVersion,
migrationVersion,
@@ -50,14 +49,6 @@ class About extends Component {
/>
}
{
isMono &&
<DescriptionListItem
title={translate('MonoVersion')}
data={runtimeVersion}
/>
}
{
isNetCore &&
<DescriptionListItem
@@ -121,7 +112,6 @@ About.propTypes = {
packageVersion: PropTypes.string,
packageAuthor: PropTypes.string,
isNetCore: PropTypes.bool.isRequired,
isMono: PropTypes.bool.isRequired,
runtimeVersion: PropTypes.string.isRequired,
isDocker: PropTypes.bool.isRequired,
migrationVersion: PropTypes.number.isRequired,

View File

@@ -10,3 +10,7 @@ export function isMobile() {
export function isIOS() {
return mobileDetect.is('iOS');
}
export function isFirefox() {
return window.navigator.userAgent.toLowerCase().indexOf('firefox/') >= 0;
}

View File

@@ -58,7 +58,7 @@
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
"react-autosuggest": "10.1.0",
"react-custom-scrollbars": "4.2.1",
"react-custom-scrollbars-2": "4.5.0",
"react-dnd": "14.0.2",
"react-dnd-html5-backend": "14.0.0",
"react-dnd-multi-backend": "6.0.2",
@@ -109,6 +109,7 @@
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.23.4",
"eslint-plugin-react": "7.24.0",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"esprint": "3.1.0",
"file-loader": "6.2.0",

View File

@@ -29,9 +29,9 @@
<PackageVersion Include="MonoTorrent" Version="2.0.6" />
<PackageVersion Include="NBuilder" Version="6.1.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
<PackageVersion Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageVersion Include="NLog" Version="4.7.14" />
<PackageVersion Include="NLog.Targets.Syslog" Version="6.0.2" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.1.0" />
<PackageVersion Include="NLog" Version="5.0.5" />
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageVersion Include="Npgsql" Version="6.0.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageVersion Include="NUnit" Version="3.13.3" />

View File

@@ -437,24 +437,6 @@ namespace NzbDrone.Common.Test.DiskTests
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
}
[Test]
public void CopyFolder_should_not_copy_casesensitive_folder()
{
MonoOnly();
WithRealDiskProvider();
var original = GetFilledTempFolder();
var root = new DirectoryInfo(GetTempFilePath());
var source = new DirectoryInfo(root.FullName + "A/series");
var destination = new DirectoryInfo(root.FullName + "A/Series");
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
// Note: Although technically possible top copy to different case, we're not allowing it
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
}
[Test]
public void CopyFolder_should_ignore_nfs_temp_file()
{
@@ -540,26 +522,6 @@ namespace NzbDrone.Common.Test.DiskTests
source.FullName.GetActualCasing().Should().Be(destination.FullName);
}
[Test]
public void MoveFolder_should_rename_casesensitive_folder()
{
MonoOnly();
WithRealDiskProvider();
var original = GetFilledTempFolder();
var root = new DirectoryInfo(GetTempFilePath());
var source = new DirectoryInfo(root.FullName + "A/series");
var destination = new DirectoryInfo(root.FullName + "A/Series");
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
Directory.Exists(source.FullName).Should().Be(false);
Directory.Exists(destination.FullName).Should().Be(true);
}
[Test]
public void should_throw_if_destination_is_readonly()
{

View File

@@ -207,6 +207,7 @@ namespace NzbDrone.Common.Test.Http
}
[Test]
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
public void should_execute_get_using_brotli()
{
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
@@ -307,11 +308,6 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_follow_redirects_to_https()
{
if (typeof(TDispatcher) == typeof(ManagedHttpDispatcher) && PlatformInfo.IsMono)
{
Assert.Ignore("Will fail on tls1.2 via managed dispatcher, ignore.");
}
var request = new HttpRequestBuilder($"https://{_httpBinHost}/redirect-to")
.AddQueryParam("url", $"https://readarr.com/")
.Build();

View File

@@ -61,6 +61,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
// Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
@@ -73,17 +74,36 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
// Webhooks - Notifiarr
// Notifiarr
[TestCase(@"https://xxx.yyy/api/v1/notification/readarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]
// Discord
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
public void should_clean_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210");
}
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
public void should_keep_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210");
cleansedMessage.Should().Contain("shouldkeep1");
cleansedMessage.Should().Contain("shouldkeep2");
cleansedMessage.Should().Contain("shouldkeep3");
}
//GoodReads
[TestCase(@"{""signatureMethod"": ""hmacSha1"",""signatureTreatment"": ""escaped"",""type"": ""protectedResource"",""method"": ""GET"",""token"": ""mytoken"",""tokenSecret"": ""mytokensecret"",""requestUrl"": ""https://www.goodreads.com/review/list.xml"",""parameters"": { ""_nc"": ""1"", ""v"": ""2"", ""id"": ""999999999"", ""shelf"": ""currently-reading"", ""per_page"": ""200"", ""page"": ""1""}")]
[TestCase(@"https://www.goodreads.com/series/311911?key=1234530f422f4aacb6b301233210aaaa&_nc=1&format=xml")]

View File

@@ -279,7 +279,7 @@ namespace NzbDrone.Common.Test
[Test]
public void GetUpdateClientExePath()
{
GetIAppDirectoryInfo().GetUpdateClientExePath(PlatformType.DotNet).Should().BeEquivalentTo(@"C:\Temp\readarr_update\Readarr.Update.exe".AsOsAgnostic());
GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\readarr_update\Readarr.Update".AsOsAgnostic().ProcessNameToExe());
}
[Test]

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
@@ -171,7 +171,7 @@ namespace NzbDrone.Common.Test
var processStarted = new ManualResetEventSlim();
string suffix;
if (OsInfo.IsWindows || PlatformInfo.IsMono)
if (OsInfo.IsWindows)
{
suffix = ".exe";
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using NzbDrone.Common.EnvironmentInfo;
@@ -24,7 +24,8 @@ namespace NzbDrone.Common.Disk
"/boot",
"/lib",
"/sbin",
"/proc"
"/proc",
"/usr/bin"
};
}
}

View File

@@ -1,17 +1,7 @@
using System;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Win32;
using System;
namespace NzbDrone.Common.EnvironmentInfo
{
public enum PlatformType
{
DotNet = 0,
Mono = 1,
NetCore = 2
}
public interface IPlatformInfo
{
Version Version { get; }
@@ -19,38 +9,18 @@ namespace NzbDrone.Common.EnvironmentInfo
public class PlatformInfo : IPlatformInfo
{
private static readonly Regex MonoVersionRegex = new Regex(@"(?<=\W|^)(?<version>\d+\.\d+(\.\d+)?(\.\d+)?)(?=\W)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static PlatformType _platform;
private static Version _version;
static PlatformInfo()
{
_platform = PlatformType.NetCore;
_version = Environment.Version;
}
public static PlatformType Platform => _platform;
public static bool IsMono => Platform == PlatformType.Mono;
public static bool IsDotNet => Platform == PlatformType.DotNet;
public static bool IsNetCore => Platform == PlatformType.NetCore;
public static string PlatformName
{
get
{
if (IsDotNet)
{
return ".NET";
}
else if (IsMono)
{
return "Mono";
}
else
{
return ".NET Core";
}
return ".NET";
}
}
@@ -60,107 +30,5 @@ namespace NzbDrone.Common.EnvironmentInfo
{
return _version;
}
private static Version GetMonoVersion()
{
try
{
var type = Type.GetType("Mono.Runtime");
if (type != null)
{
var displayNameMethod = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayNameMethod != null)
{
var displayName = displayNameMethod.Invoke(null, null).ToString();
var versionMatch = MonoVersionRegex.Match(displayName);
if (versionMatch.Success)
{
return new Version(versionMatch.Groups["version"].Value);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Couldnt get Mono version: " + ex.ToString());
}
return new Version();
}
private static Version GetDotNetVersion()
{
try
{
const string subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";
using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(subkey))
{
if (ndpKey == null)
{
return new Version(4, 0);
}
var releaseKey = (int)ndpKey.GetValue("Release");
if (releaseKey >= 528040)
{
return new Version(4, 8, 0);
}
if (releaseKey >= 461808)
{
return new Version(4, 7, 2);
}
if (releaseKey >= 461308)
{
return new Version(4, 7, 1);
}
if (releaseKey >= 460798)
{
return new Version(4, 7);
}
if (releaseKey >= 394802)
{
return new Version(4, 6, 2);
}
if (releaseKey >= 394254)
{
return new Version(4, 6, 1);
}
if (releaseKey >= 393295)
{
return new Version(4, 6);
}
if (releaseKey >= 379893)
{
return new Version(4, 5, 2);
}
if (releaseKey >= 378675)
{
return new Version(4, 5, 1);
}
if (releaseKey >= 378389)
{
return new Version(4, 5);
}
}
}
catch (Exception ex)
{
Console.WriteLine("Couldnt get .NET framework version: " + ex.ToString());
}
return new Version(4, 0);
}
}
}

View File

@@ -240,9 +240,9 @@ namespace NzbDrone.Common.Extensions
return null;
}
public static string ProcessNameToExe(this string processName, PlatformType runtime)
public static string ProcessNameToExe(this string processName)
{
if (OsInfo.IsWindows || runtime != PlatformType.NetCore)
if (OsInfo.IsWindows)
{
processName += ".exe";
}
@@ -250,11 +250,6 @@ namespace NzbDrone.Common.Extensions
return processName;
}
public static string ProcessNameToExe(this string processName)
{
return processName.ProcessNameToExe(PlatformInfo.Platform);
}
public static string GetLongestCommonPath(this List<string> paths)
{
var firstPath = paths.First();
@@ -347,9 +342,9 @@ namespace NzbDrone.Common.Extensions
return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME);
}
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo, PlatformType runtime)
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo)
{
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(runtime);
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe();
}
public static string GetDatabase(this IAppFolderInfo appFolderInfo)

View File

@@ -55,7 +55,8 @@ namespace NzbDrone.Common.Http
StatusCode == HttpStatusCode.Found ||
StatusCode == HttpStatusCode.TemporaryRedirect ||
StatusCode == HttpStatusCode.RedirectMethod ||
StatusCode == HttpStatusCode.SeeOther;
StatusCode == HttpStatusCode.SeeOther ||
StatusCode == HttpStatusCode.PermanentRedirect;
public string[] GetCookieHeaders()
{

View File

@@ -18,7 +18,7 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
@@ -51,7 +51,10 @@ namespace NzbDrone.Common.Instrumentation
// Webhooks
// Notifiarr
new Regex(@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
new Regex(@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Discord
new Regex(@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);

View File

@@ -1,6 +1,6 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NLog.Fluent;
namespace NzbDrone.Common.Instrumentation.Extensions
{
@@ -8,47 +8,46 @@ namespace NzbDrone.Common.Instrumentation.Extensions
{
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder SentryFingerprint(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return logBuilder.Property("Sentry", fingerprint);
}
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder WriteSentryDebug(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
}
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder WriteSentryInfo(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
}
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder WriteSentryWarn(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
}
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder WriteSentryError(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
}
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint)
{
SentryLogger.Log(level)
.CopyLogEvent(logBuilder.LogEventInfo)
SentryLogger.ForLogEvent(level)
.CopyLogEvent(logBuilder.LogEvent)
.SentryFingerprint(fingerprint)
.Write();
.Log();
return logBuilder.Property("Sentry", null);
return logBuilder.Property<string>("Sentry", null);
}
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent)
private static LogEventBuilder CopyLogEvent(this LogEventBuilder logBuilder, LogEventInfo logEvent)
{
return logBuilder.LoggerName(logEvent.LoggerName)
.TimeStamp(logEvent.TimeStamp)
return logBuilder.TimeStamp(logEvent.TimeStamp)
.Message(logEvent.Message, logEvent.Parameters)
.Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value))
.Properties(logEvent.Properties.Select(p => new KeyValuePair<string, object>(p.Key.ToString(), p.Value)))
.Exception(logEvent.Exception);
}
}

View File

@@ -38,16 +38,6 @@ namespace NzbDrone.Common.Instrumentation
return;
}
if (PlatformInfo.IsMono)
{
if ((exception is TypeInitializationException && exception.InnerException is DllNotFoundException) ||
exception is DllNotFoundException)
{
Logger.Debug(exception, "Minor Fail: " + exception.Message);
return;
}
}
Console.WriteLine("EPIC FAIL: {0}", exception);
Logger.Fatal(exception, "EPIC FAIL.");
}

View File

@@ -1,13 +1,15 @@
using NLog;
using System.Text;
using NLog;
using NLog.Targets;
namespace NzbDrone.Common.Instrumentation
{
public class NzbDroneFileTarget : FileTarget
{
protected override string GetFormattedMessage(LogEventInfo logEvent)
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{
return CleanseLogMessage.Cleanse(Layout.Render(logEvent));
var result = CleanseLogMessage.Cleanse(Layout.Render(logEvent));
target.Append(result);
}
}
}

View File

@@ -34,6 +34,8 @@ namespace NzbDrone.Common.Instrumentation
var appFolderInfo = new AppFolderInfo(startupContext);
RegisterGlobalFilters();
if (Debugger.IsAttached)
{
RegisterDebugger();
@@ -101,6 +103,16 @@ namespace NzbDrone.Common.Instrumentation
LogManager.Configuration.LoggingRules.Add(loggingRule);
}
private static void RegisterGlobalFilters()
{
LogManager.Setup().LoadConfiguration(c =>
{
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
});
}
private static void RegisterConsole()
{
var level = LogLevel.Trace;

View File

@@ -109,13 +109,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
o.Debug = false;
o.DiagnosticLevel = SentryLevel.Debug;
o.Release = BuildInfo.Release;
if (PlatformInfo.IsMono)
{
// Mono 6.0 broke GzipStream.WriteAsync
// TODO: Check specific version
o.RequestBodyCompressionLevel = System.IO.Compression.CompressionLevel.NoCompression;
}
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
o.Environment = BuildInfo.Branch;
@@ -158,14 +151,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
SentrySdk.ConfigureScope(scope =>
{
scope.SetTag("is_docker", $"{osInfo.IsDocker}");
if (osInfo.Name != null && PlatformInfo.IsMono)
{
// Sentry auto-detection of non-Windows platforms isn't that accurate on certain devices.
scope.Contexts.OperatingSystem.Name = osInfo.Name.FirstCharToUpper();
scope.Contexts.OperatingSystem.RawDescription = osInfo.FullName;
scope.Contexts.OperatingSystem.Version = osInfo.Version.ToString();
}
});
}
@@ -221,7 +206,11 @@ namespace NzbDrone.Common.Instrumentation.Sentry
if (ex != null)
{
fingerPrint.Add(ex.GetType().FullName);
fingerPrint.Add(ex.TargetSite.ToString());
if (ex.TargetSite != null)
{
fingerPrint.Add(ex.TargetSite.ToString());
}
if (ex.InnerException != null)
{
fingerPrint.Add(ex.InnerException.GetType().FullName);

View File

@@ -377,11 +377,6 @@ namespace NzbDrone.Common.Processes
private (string Path, string Args) GetPathAndArgs(string path, string args)
{
if (PlatformInfo.IsMono && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
{
return ("mono", $"--debug {path} {args}");
}
if (OsInfo.IsWindows && path.EndsWith(".bat", StringComparison.InvariantCultureIgnoreCase))
{
return ("cmd.exe", $"/c {path} {args}");

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
@@ -5,6 +6,7 @@ using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Books;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
@@ -244,5 +246,89 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Should()
.BeFalse();
}
[Test]
public void should_return_true_when_repacks_are_not_preferred()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
_trackFiles.Select(c =>
{
c.ReleaseGroup = "";
return c;
}).ToList();
_trackFiles.Select(c =>
{
c.Quality = new QualityModel(Quality.FLAC);
return c;
}).ToList();
var remoteAlbum = Builder<RemoteBook>.CreateNew()
.With(e => e.ParsedBookInfo = _parsedBookInfo)
.With(e => e.Books = _books)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_when_repack_but_auto_download_repacks_is_true()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.PreferAndUpgrade);
_parsedBookInfo.Quality.Revision.IsRepack = true;
_trackFiles.Select(c =>
{
c.ReleaseGroup = "Readarr";
return c;
}).ToList();
_trackFiles.Select(c =>
{
c.Quality = new QualityModel(Quality.FLAC);
return c;
}).ToList();
var remoteAlbum = Builder<RemoteBook>.CreateNew()
.With(e => e.ParsedBookInfo = _parsedBookInfo)
.With(e => e.Books = _books)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_false_when_repack_but_auto_download_repacks_is_false()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotUpgrade);
_parsedBookInfo.Quality.Revision.IsRepack = true;
_trackFiles.Select(c =>
{
c.ReleaseGroup = "Readarr";
return c;
}).ToList();
_trackFiles.Select(c =>
{
c.Quality = new QualityModel(Quality.FLAC);
return c;
}).ToList();
var remoteAlbum = Builder<RemoteBook>.CreateNew()
.With(e => e.ParsedBookInfo = _parsedBookInfo)
.With(e => e.Books = _books)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeFalse();
}
}
}

View File

@@ -0,0 +1,371 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Download.Clients.FreeboxDownload;
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.FreeboxDownloadTests
{
[TestFixture]
public class TorrentFreeboxDownloadFixture : DownloadClientFixtureBase<TorrentFreeboxDownload>
{
protected FreeboxDownloadSettings _settings;
protected FreeboxDownloadConfiguration _downloadConfiguration;
protected FreeboxDownloadTask _task;
protected string _defaultDestination = @"/some/path";
protected string _encodedDefaultDestination = "L3NvbWUvcGF0aA==";
protected string _category = "somecat";
protected string _encodedDefaultDestinationAndCategory = "L3NvbWUvcGF0aC9zb21lY2F0";
protected string _destinationDirectory = @"/path/to/media";
protected string _encodedDestinationDirectory = "L3BhdGgvdG8vbWVkaWE=";
protected OsPath _physicalPath = new OsPath("/mnt/sdb1/mydata");
protected string _downloadURL => "magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcad53426&dn=download";
[SetUp]
public void Setup()
{
Subject.Definition = new DownloadClientDefinition();
_settings = new FreeboxDownloadSettings()
{
Host = "127.0.0.1",
Port = 443,
ApiUrl = "/api/v1/",
AppId = "someid",
AppToken = "S0mEv3RY1oN9T0k3n"
};
Subject.Definition.Settings = _settings;
_downloadConfiguration = new FreeboxDownloadConfiguration()
{
DownloadDirectory = _encodedDefaultDestination
};
_task = new FreeboxDownloadTask()
{
Id = "id0",
Name = "name",
DownloadDirectory = "L3NvbWUvcGF0aA==",
InfoHash = "HASH",
QueuePosition = 1,
Status = FreeboxDownloadTaskStatus.Unknown,
Eta = 0,
Error = "none",
Type = FreeboxDownloadTaskType.Bt.ToString(),
IoPriority = FreeboxDownloadTaskIoPriority.Normal.ToString(),
StopRatio = 150,
PieceLength = 125,
CreatedTimestamp = 1665261599,
Size = 1000,
ReceivedPrct = 0,
ReceivedBytes = 0,
ReceivedRate = 0,
TransmittedPrct = 0,
TransmittedBytes = 0,
TransmittedRate = 0,
};
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
}
protected void GivenCategory()
{
_settings.Category = _category;
}
protected void GivenDestinationDirectory()
{
_settings.DestinationDirectory = _destinationDirectory;
}
protected virtual void GivenDownloadConfiguration()
{
Mocker.GetMock<IFreeboxDownloadProxy>()
.Setup(s => s.GetDownloadConfiguration(It.IsAny<FreeboxDownloadSettings>()))
.Returns(_downloadConfiguration);
}
protected virtual void GivenTasks(List<FreeboxDownloadTask> torrents)
{
if (torrents == null)
{
torrents = new List<FreeboxDownloadTask>();
}
Mocker.GetMock<IFreeboxDownloadProxy>()
.Setup(s => s.GetTasks(It.IsAny<FreeboxDownloadSettings>()))
.Returns(torrents);
}
protected void PrepareClientToReturnQueuedItem()
{
_task.Status = FreeboxDownloadTaskStatus.Queued;
GivenTasks(new List<FreeboxDownloadTask>
{
_task
});
}
protected void GivenSuccessfulDownload()
{
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
Mocker.GetMock<IFreeboxDownloadProxy>()
.Setup(s => s.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Setup(s => s.AddTaskFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
}
protected override RemoteEpisode CreateRemoteEpisode()
{
var episode = base.CreateRemoteEpisode();
episode.Release.DownloadUrl = _downloadURL;
return episode;
}
[Test]
public void Download_with_DestinationDirectory_should_force_directory()
{
GivenDestinationDirectory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
Subject.Download(remoteEpisode);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDestinationDirectory, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[Test]
public void Download_with_Category_should_force_directory()
{
GivenDownloadConfiguration();
GivenCategory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
Subject.Download(remoteEpisode);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDefaultDestinationAndCategory, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[Test]
public void Download_without_DestinationDirectory_and_Category_should_use_default()
{
GivenDownloadConfiguration();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
Subject.Download(remoteEpisode);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDefaultDestination, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[TestCase(false, false)]
[TestCase(true, true)]
public void Download_should_pause_torrent_as_expected(bool addPausedSetting, bool toBePausedFlag)
{
_settings.AddPaused = addPausedSetting;
GivenDownloadConfiguration();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
Subject.Download(remoteEpisode);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), toBePausedFlag, It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[TestCase(0, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
[TestCase(0, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, true)]
[TestCase(0, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, false)]
[TestCase(0, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.Last, false)]
[TestCase(15, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
[TestCase(15, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, false)]
[TestCase(15, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, true)]
[TestCase(15, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.Last, false)]
public void Download_should_queue_torrent_first_as_expected(int ageDay, int olderPriority, int recentPriority, bool toBeQueuedFirstFlag)
{
_settings.OlderPriority = olderPriority;
_settings.RecentPriority = recentPriority;
GivenDownloadConfiguration();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var episode = new Tv.Episode()
{
AirDateUtc = DateTime.UtcNow.Date.AddDays(-ageDay)
};
remoteEpisode.Episodes.Add(episode);
Subject.Download(remoteEpisode);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), toBeQueuedFirstFlag, It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[TestCase(0, 0)]
[TestCase(1.5, 150)]
public void Download_should_define_seed_ratio_as_expected(double? providerSeedRatio, double? expectedSeedRatio)
{
GivenDownloadConfiguration();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
remoteEpisode.SeedConfiguration = new TorrentSeedConfiguration();
remoteEpisode.SeedConfiguration.Ratio = providerSeedRatio;
Subject.Download(remoteEpisode);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), expectedSeedRatio, It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[Test]
public void GetItems_should_return_empty_list_if_no_tasks_available()
{
GivenTasks(new List<FreeboxDownloadTask>());
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_return_ignore_tasks_of_unknown_type()
{
_task.Status = FreeboxDownloadTaskStatus.Done;
_task.Type = "toto";
GivenTasks(new List<FreeboxDownloadTask> { _task });
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_when_destinationdirectory_is_set_should_ignore_downloads_in_wrong_folder()
{
_settings.DestinationDirectory = @"/some/path/that/will/not/match";
_task.Status = FreeboxDownloadTaskStatus.Done;
GivenTasks(new List<FreeboxDownloadTask> { _task });
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_when_category_is_set_should_ignore_downloads_in_wrong_folder()
{
_settings.Category = "somecategory";
_task.Status = FreeboxDownloadTaskStatus.Done;
GivenTasks(new List<FreeboxDownloadTask> { _task });
Subject.GetItems().Should().BeEmpty();
}
[TestCase(FreeboxDownloadTaskStatus.Downloading, false, false)]
[TestCase(FreeboxDownloadTaskStatus.Done, true, true)]
[TestCase(FreeboxDownloadTaskStatus.Seeding, false, false)]
[TestCase(FreeboxDownloadTaskStatus.Stopped, false, false)]
public void GetItems_should_return_canBeMoved_and_canBeDeleted_as_expected(FreeboxDownloadTaskStatus apiStatus, bool canMoveFilesExpected, bool canBeRemovedExpected)
{
_task.Status = apiStatus;
GivenTasks(new List<FreeboxDownloadTask>() { _task });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().CanBeRemoved.Should().Be(canBeRemovedExpected);
items.First().CanMoveFiles.Should().Be(canMoveFilesExpected);
}
[TestCase(FreeboxDownloadTaskStatus.Stopped, DownloadItemStatus.Paused)]
[TestCase(FreeboxDownloadTaskStatus.Stopping, DownloadItemStatus.Paused)]
[TestCase(FreeboxDownloadTaskStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(FreeboxDownloadTaskStatus.Starting, DownloadItemStatus.Downloading)]
[TestCase(FreeboxDownloadTaskStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(FreeboxDownloadTaskStatus.Retry, DownloadItemStatus.Downloading)]
[TestCase(FreeboxDownloadTaskStatus.Checking, DownloadItemStatus.Downloading)]
[TestCase(FreeboxDownloadTaskStatus.Error, DownloadItemStatus.Warning)]
[TestCase(FreeboxDownloadTaskStatus.Seeding, DownloadItemStatus.Completed)]
[TestCase(FreeboxDownloadTaskStatus.Done, DownloadItemStatus.Completed)]
[TestCase(FreeboxDownloadTaskStatus.Unknown, DownloadItemStatus.Downloading)]
public void GetItems_should_return_item_as_downloadItemStatus(FreeboxDownloadTaskStatus apiStatus, DownloadItemStatus expectedItemStatus)
{
_task.Status = apiStatus;
GivenTasks(new List<FreeboxDownloadTask>() { _task });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().Status.Should().Be(expectedItemStatus);
}
[Test]
public void GetItems_should_return_decoded_destination_directory()
{
var decodedDownloadDirectory = "/that/the/path";
_task.Status = FreeboxDownloadTaskStatus.Done;
_task.DownloadDirectory = "L3RoYXQvdGhlL3BhdGg=";
GivenTasks(new List<FreeboxDownloadTask> { _task });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be(decodedDownloadDirectory);
}
[Test]
public void GetItems_should_return_message_if_tasks_in_error()
{
_task.Status = FreeboxDownloadTaskStatus.Error;
_task.Error = "internal";
GivenTasks(new List<FreeboxDownloadTask> { _task });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().Message.Should().Be("Internal error.");
items.First().Status.Should().Be(DownloadItemStatus.Warning);
}
}
}

View File

@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
.Setup(s => s.GetAuthor(It.IsAny<int>()))
.Returns(_author);
Mocker.GetMock<ISearchForNzb>()
Mocker.GetMock<ISearchForReleases>()
.Setup(s => s.AuthorSearch(_author.Id, false, true, false))
.Returns(new List<DownloadDecision>());
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
Subject.Execute(new AuthorSearchCommand { AuthorId = _author.Id, Trigger = CommandTrigger.Manual });
Mocker.GetMock<ISearchForNzb>()
Mocker.GetMock<ISearchForReleases>()
.Verify(v => v.AuthorSearch(_author.Id, false, true, false),
Times.Exactly(_author.Books.Value.Count(s => s.Monitored)));
}

View File

@@ -16,11 +16,6 @@ namespace NzbDrone.Core.Test.MediaCoverTests
[SetUp]
public void SetUp()
{
if (PlatformInfo.IsMono && PlatformInfo.GetVersion() < new Version(5, 8))
{
Assert.Inconclusive("Not supported on Mono < 5.8");
}
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.IsAny<string>()))
.Returns<string>(s => File.Exists(s));

View File

@@ -7,7 +7,6 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class QualityParserFixture : CoreTest
{
public static object[] SelfQualityParserCases =

View File

@@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.UpdateTests
.Returns(true);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Readarr.Update.exe"))))
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Readarr.Update".ProcessNameToExe()))))
.Returns(true);
_sandboxFolder = Mocker.GetMock<IAppFolderInfo>().Object.GetUpdateSandboxFolder();
@@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.UpdateTests
public void should_return_with_warning_if_updater_doesnt_exists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Readarr.Update.exe"))))
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Readarr.Update".ProcessNameToExe()))))
.Returns(false);
Subject.Execute(new ApplicationUpdateCommand());

View File

@@ -410,6 +410,8 @@ namespace NzbDrone.Core.Configuration
public CertificateValidationType CertificateValidation =>
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
private string GetValue(string key)
{
return GetValue(key, string.Empty);

View File

@@ -97,5 +97,6 @@ namespace NzbDrone.Core.Configuration
int BackupRetention { get; }
CertificateValidationType CertificateValidation { get; }
string ApplicationUrl { get; }
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Data.Common;
using System.Data.SQLite;
using System.Net.Sockets;
using NLog;
using Npgsql;
using NzbDrone.Common.Disk;
@@ -131,6 +132,37 @@ namespace NzbDrone.Core.Datastore
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/readarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
}
catch (NpgsqlException e)
{
if (e.InnerException is SocketException)
{
var retryCount = 3;
while (true)
{
Logger.Error(e, "Failure to connect to Postgres DB, {0} retries remaining", retryCount);
try
{
_migrationController.Migrate(connectionString, migrationContext);
}
catch (Exception ex)
{
if (--retryCount > 0)
{
System.Threading.Thread.Sleep(5000);
continue;
}
throw new ReadarrStartupException(ex, "Error creating main database");
}
}
}
else
{
throw new ReadarrStartupException(e, "Error creating main database");
}
}
catch (Exception e)
{
throw new ReadarrStartupException(e, "Error creating main database");

View File

@@ -1,9 +1,11 @@
using System;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
@@ -11,12 +13,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
private readonly IMediaFileService _mediaFileService;
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IConfigService _configService;
private readonly Logger _logger;
public RepackSpecification(IMediaFileService mediaFileService, UpgradableSpecification upgradableSpecification, Logger logger)
public RepackSpecification(IMediaFileService mediaFileService, UpgradableSpecification upgradableSpecification, IConfigService configService, Logger logger)
{
_mediaFileService = mediaFileService;
_upgradableSpecification = upgradableSpecification;
_configService = configService;
_logger = logger;
}
@@ -30,6 +34,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return Decision.Accept();
}
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
{
_logger.Debug("Repacks are not preferred, skipping check");
return Decision.Accept();
}
foreach (var book in subject.Books)
{
var releaseGroup = subject.ParsedBookInfo.ReleaseGroup;
@@ -39,6 +51,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedBookInfo.Quality))
{
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
{
_logger.Debug("Auto downloading of repacks is disabled");
return Decision.Reject("Repack downloading is disabled");
}
var fileReleaseGroup = file.ReleaseGroup;
if (fileReleaseGroup.IsNullOrWhiteSpace())

View File

@@ -125,22 +125,22 @@ namespace NzbDrone.Core.Download.Clients.Flood
item.RemainingTime = TimeSpan.FromSeconds(properties.Eta);
}
if (properties.Status.Contains("error"))
{
item.Status = DownloadItemStatus.Warning;
}
else if (properties.Status.Contains("seeding") || properties.Status.Contains("complete"))
if (properties.Status.Contains("seeding") || properties.Status.Contains("complete"))
{
item.Status = DownloadItemStatus.Completed;
}
else if (properties.Status.Contains("downloading"))
{
item.Status = DownloadItemStatus.Downloading;
}
else if (properties.Status.Contains("stopped"))
{
item.Status = DownloadItemStatus.Paused;
}
else if (properties.Status.Contains("error"))
{
item.Status = DownloadItemStatus.Warning;
}
else if (properties.Status.Contains("downloading"))
{
item.Status = DownloadItemStatus.Downloading;
}
if (item.Status == DownloadItemStatus.Completed)
{

View File

@@ -0,0 +1,27 @@
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public static class EncodingForBase64
{
public static string EncodeBase64(this string text)
{
if (text == null)
{
return null;
}
byte[] textAsBytes = System.Text.Encoding.UTF8.GetBytes(text);
return System.Convert.ToBase64String(textAsBytes);
}
public static string DecodeBase64(this string encodedText)
{
if (encodedText == null)
{
return null;
}
byte[] textAsBytes = System.Convert.FromBase64String(encodedText);
return System.Text.Encoding.UTF8.GetString(textAsBytes);
}
}
}

View File

@@ -0,0 +1,10 @@
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public class FreeboxDownloadException : DownloadClientException
{
public FreeboxDownloadException(string message)
: base(message)
{
}
}
}

View File

@@ -0,0 +1,8 @@
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public enum FreeboxDownloadPriority
{
Last = 0,
First = 1
}
}

View File

@@ -0,0 +1,277 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public interface IFreeboxDownloadProxy
{
void Authenticate(FreeboxDownloadSettings settings);
string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings);
string AddTaskFromFile(string fileName, byte[] fileContent, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings);
void DeleteTask(string id, bool deleteData, FreeboxDownloadSettings settings);
FreeboxDownloadConfiguration GetDownloadConfiguration(FreeboxDownloadSettings settings);
List<FreeboxDownloadTask> GetTasks(FreeboxDownloadSettings settings);
}
public class FreeboxDownloadProxy : IFreeboxDownloadProxy
{
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
private ICached<string> _authSessionTokenCache;
public FreeboxDownloadProxy(ICacheManager cacheManager, IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
_authSessionTokenCache = cacheManager.GetCache<string>(GetType(), "authSessionToken");
}
public void Authenticate(FreeboxDownloadSettings settings)
{
var request = BuildRequest(settings).Resource("/login").Build();
var response = ProcessRequest<FreeboxLogin>(request, settings);
if (response.Result.LoggedIn == false)
{
throw new DownloadClientAuthenticationException("Not logged");
}
}
public string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
{
var request = BuildRequest(settings).Resource("/downloads/add").Post();
request.Headers.ContentType = "application/x-www-form-urlencoded";
request.AddFormParameter("download_url", System.Web.HttpUtility.UrlPathEncode(url));
if (!directory.IsNullOrWhiteSpace())
{
request.AddFormParameter("download_dir", directory);
}
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
SetTorrentSettings(response.Result.Id, addPaused, addFirst, seedRatio, settings);
return response.Result.Id;
}
public string AddTaskFromFile(string fileName, byte[] fileContent, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
{
var request = BuildRequest(settings).Resource("/downloads/add").Post();
request.AddFormUpload("download_file", fileName, fileContent, "multipart/form-data");
if (directory.IsNotNullOrWhiteSpace())
{
request.AddFormParameter("download_dir", directory);
}
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
SetTorrentSettings(response.Result.Id, addPaused, addFirst, seedRatio, settings);
return response.Result.Id;
}
public void DeleteTask(string id, bool deleteData, FreeboxDownloadSettings settings)
{
var uri = "/downloads/" + id;
if (deleteData == true)
{
uri += "/erase";
}
var request = BuildRequest(settings).Resource(uri).Build();
request.Method = HttpMethod.Delete;
ProcessRequest<string>(request, settings);
}
public FreeboxDownloadConfiguration GetDownloadConfiguration(FreeboxDownloadSettings settings)
{
var request = BuildRequest(settings).Resource("/downloads/config/").Build();
return ProcessRequest<FreeboxDownloadConfiguration>(request, settings).Result;
}
public List<FreeboxDownloadTask> GetTasks(FreeboxDownloadSettings settings)
{
var request = BuildRequest(settings).Resource("/downloads/").Build();
return ProcessRequest<List<FreeboxDownloadTask>>(request, settings).Result;
}
private static string BuildCachedHeaderKey(FreeboxDownloadSettings settings)
{
return $"{settings.Host}:{settings.AppId}:{settings.AppToken}";
}
private void SetTorrentSettings(string id, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
{
var request = BuildRequest(settings).Resource("/downloads/" + id).Build();
request.Method = HttpMethod.Put;
var body = new Dictionary<string, object> { };
if (addPaused)
{
body.Add("status", FreeboxDownloadTaskStatus.Stopped.ToString().ToLower());
}
if (addFirst)
{
body.Add("queue_pos", "1");
}
if (seedRatio != null)
{
// 0 means unlimited seeding
body.Add("stop_ratio", seedRatio);
}
if (body.Count == 0)
{
return;
}
request.SetContent(body.ToJson());
ProcessRequest<FreeboxDownloadTask>(request, settings);
}
private string GetSessionToken(HttpRequestBuilder requestBuilder, FreeboxDownloadSettings settings, bool force = false)
{
var sessionToken = _authSessionTokenCache.Find(BuildCachedHeaderKey(settings));
if (sessionToken == null || force)
{
_authSessionTokenCache.Remove(BuildCachedHeaderKey(settings));
_logger.Debug($"Client needs a new Session Token to reach the API with App ID '{settings.AppId}'");
// Obtaining a Session Token (from official documentation):
// To protect the app_token secret, it will never be used directly to authenticate the
// application, instead the API will provide a challenge the app will combine to its
// app_token to open a session and get a session_token.
// The validity of the session_token is limited in time and the app will have to renew
// this session_token once in a while.
// Retrieving the 'challenge' value (it changes frequently and have a limited time validity)
// needed to build password
var challengeRequest = requestBuilder.Resource("/login").Build();
challengeRequest.Method = HttpMethod.Get;
var challenge = ProcessRequest<FreeboxLogin>(challengeRequest, settings).Result.Challenge;
// The password is computed using the 'challenge' value and the 'app_token' ('App Token' setting)
var enc = System.Text.Encoding.ASCII;
var hmac = new HMACSHA1(enc.GetBytes(settings.AppToken));
hmac.Initialize();
var buffer = enc.GetBytes(challenge);
var password = System.BitConverter.ToString(hmac.ComputeHash(buffer)).Replace("-", "").ToLower();
// Both 'app_id' ('App ID' setting) and computed password are set to get a Session Token
var sessionRequest = requestBuilder.Resource("/login/session").Post().Build();
var body = new Dictionary<string, object>
{
{ "app_id", settings.AppId },
{ "password", password }
};
sessionRequest.SetContent(body.ToJson());
sessionToken = ProcessRequest<FreeboxLogin>(sessionRequest, settings).Result.SessionToken;
_authSessionTokenCache.Set(BuildCachedHeaderKey(settings), sessionToken);
_logger.Debug($"New Session Token stored in cache for App ID '{settings.AppId}', ready to reach API");
}
return sessionToken;
}
private HttpRequestBuilder BuildRequest(FreeboxDownloadSettings settings, bool authentication = true)
{
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.ApiUrl)
{
LogResponseContent = true
};
requestBuilder.Headers.ContentType = "application/json";
if (authentication == true)
{
requestBuilder.SetHeader("X-Fbx-App-Auth", GetSessionToken(requestBuilder, settings));
}
return requestBuilder;
}
private FreeboxResponse<T> ProcessRequest<T>(HttpRequest request, FreeboxDownloadSettings settings)
{
request.LogResponseContent = true;
request.SuppressHttpError = true;
HttpResponse response;
try
{
response = _httpClient.Execute(request);
}
catch (HttpRequestException ex)
{
throw new DownloadClientUnavailableException($"Unable to reach Freebox API. Verify 'Host', 'Port' or 'Use SSL' settings. (Error: {ex.Message})", ex);
}
catch (WebException ex)
{
throw new DownloadClientUnavailableException("Unable to connect to Freebox API, please check your settings", ex);
}
if (response.StatusCode == HttpStatusCode.Forbidden || response.StatusCode == HttpStatusCode.Unauthorized)
{
_authSessionTokenCache.Remove(BuildCachedHeaderKey(settings));
var responseContent = Json.Deserialize<FreeboxResponse<FreeboxLogin>>(response.Content);
var msg = $"Authentication to Freebox API failed. Reason: {responseContent.GetErrorDescription()}";
_logger.Error(msg);
throw new DownloadClientAuthenticationException(msg);
}
else if (response.StatusCode == HttpStatusCode.NotFound)
{
throw new FreeboxDownloadException("Unable to reach Freebox API. Verify 'API URL' setting for base URL and version.");
}
else if (response.StatusCode == HttpStatusCode.OK)
{
var responseContent = Json.Deserialize<FreeboxResponse<T>>(response.Content);
if (responseContent.Success)
{
return responseContent;
}
else
{
var msg = $"Freebox API returned error: {responseContent.GetErrorDescription()}";
_logger.Error(msg);
throw new DownloadClientException(msg);
}
}
else
{
throw new DownloadClientException("Unable to connect to Freebox, please check your settings.");
}
}
}
}

View File

@@ -0,0 +1,87 @@
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public class FreeboxDownloadSettingsValidator : AbstractValidator<FreeboxDownloadSettings>
{
public FreeboxDownloadSettingsValidator()
{
RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.ApiUrl).NotEmpty()
.WithMessage("'API URL' must not be empty.");
RuleFor(c => c.ApiUrl).ValidUrlBase();
RuleFor(c => c.AppId).NotEmpty()
.WithMessage("'App ID' must not be empty.");
RuleFor(c => c.AppToken).NotEmpty()
.WithMessage("'App Token' must not be empty.");
RuleFor(c => c.Category).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase)
.WithMessage("Allowed characters a-z and -");
RuleFor(c => c.DestinationDirectory).IsValidPath()
.When(c => c.DestinationDirectory.IsNotNullOrWhiteSpace());
RuleFor(c => c.DestinationDirectory).Empty()
.When(c => c.Category.IsNotNullOrWhiteSpace())
.WithMessage("Cannot use 'Category' and 'Destination Directory' at the same time.");
RuleFor(c => c.Category).Empty()
.When(c => c.DestinationDirectory.IsNotNullOrWhiteSpace())
.WithMessage("Cannot use 'Category' and 'Destination Directory' at the same time.");
}
}
public class FreeboxDownloadSettings : IProviderConfig
{
private static readonly FreeboxDownloadSettingsValidator Validator = new FreeboxDownloadSettingsValidator();
public FreeboxDownloadSettings()
{
Host = "mafreebox.freebox.fr";
Port = 443;
UseSsl = true;
ApiUrl = "/api/v1/";
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "Hostname or host IP address of the Freebox, defaults to 'mafreebox.freebox.fr' (will only work if on same network)")]
public string Host { get; set; }
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "Port used to access Freebox interface, defaults to '443'")]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secured connection when connecting to Freebox API")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "API URL", Type = FieldType.Textbox, Advanced = true, HelpText = "Define Freebox API base URL with API version, eg http://[host]:[port]/[api_base_url]/[api_version]/, defaults to '/api/v1/'")]
public string ApiUrl { get; set; }
[FieldDefinition(4, Label = "App ID", Type = FieldType.Textbox, HelpText = "App ID given when creating access to Freebox API (ie 'app_id')")]
public string AppId { get; set; }
[FieldDefinition(5, Label = "App Token", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "App token retrieved when creating access to Freebox API (ie 'app_token')")]
public string AppToken { get; set; }
[FieldDefinition(6, Label = "Destination Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Freebox download location")]
public string DestinationDirectory { get; set; }
[FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads (will create a [category] subdirectory in the output directory)")]
public string Category { get; set; }
[FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
public int RecentPriority { get; set; }
[FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
public int OlderPriority { get; set; }
[FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -0,0 +1,21 @@
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
{
public class FreeboxDownloadConfiguration
{
[JsonProperty(PropertyName = "download_dir")]
public string DownloadDirectory { get; set; }
public string DecodedDownloadDirectory
{
get
{
return DownloadDirectory.DecodeBase64();
}
set
{
DownloadDirectory = value.EncodeBase64();
}
}
}
}

View File

@@ -0,0 +1,137 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
{
public enum FreeboxDownloadTaskType
{
Bt,
Nzb,
Http,
Ftp
}
public enum FreeboxDownloadTaskStatus
{
Unknown,
Stopped,
Queued,
Starting,
Downloading,
Stopping,
Error,
Done,
Checking,
Repairing,
Extracting,
Seeding,
Retry
}
public enum FreeboxDownloadTaskIoPriority
{
Low,
Normal,
High
}
public class FreeboxDownloadTask
{
private static readonly Dictionary<string, string> Descriptions;
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "download_dir")]
public string DownloadDirectory { get; set; }
public string DecodedDownloadDirectory
{
get
{
return DownloadDirectory.DecodeBase64();
}
set
{
DownloadDirectory = value.EncodeBase64();
}
}
[JsonProperty(PropertyName = "info_hash")]
public string InfoHash { get; set; }
[JsonProperty(PropertyName = "queue_pos")]
public int QueuePosition { get; set; }
[JsonConverter(typeof(UnderscoreStringEnumConverter), FreeboxDownloadTaskStatus.Unknown)]
public FreeboxDownloadTaskStatus Status { get; set; }
[JsonProperty(PropertyName = "eta")]
public long Eta { get; set; }
[JsonProperty(PropertyName = "error")]
public string Error { get; set; }
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }
[JsonProperty(PropertyName = "io_priority")]
public string IoPriority { get; set; }
[JsonProperty(PropertyName = "stop_ratio")]
public long StopRatio { get; set; }
[JsonProperty(PropertyName = "piece_length")]
public long PieceLength { get; set; }
[JsonProperty(PropertyName = "created_ts")]
public long CreatedTimestamp { get; set; }
[JsonProperty(PropertyName = "size")]
public long Size { get; set; }
[JsonProperty(PropertyName = "rx_pct")]
public long ReceivedPrct { get; set; }
[JsonProperty(PropertyName = "rx_bytes")]
public long ReceivedBytes { get; set; }
[JsonProperty(PropertyName = "rx_rate")]
public long ReceivedRate { get; set; }
[JsonProperty(PropertyName = "tx_pct")]
public long TransmittedPrct { get; set; }
[JsonProperty(PropertyName = "tx_bytes")]
public long TransmittedBytes { get; set; }
[JsonProperty(PropertyName = "tx_rate")]
public long TransmittedRate { get; set; }
static FreeboxDownloadTask()
{
Descriptions = new Dictionary<string, string>
{
{ "internal", "Internal error." },
{ "disk_full", "The disk is full." },
{ "unknown", "Unknown error." },
{ "parse_error", "Parse error." },
{ "unknown_host", "Unknown host." },
{ "timeout", "Timeout." },
{ "bad_authentication", "Invalid credentials." },
{ "connection_refused", "Remote host refused connection." },
{ "bt_tracker_error", "Unable to announce on tracker." },
{ "bt_missing_files", "Missing torrent files." },
{ "bt_file_error", "Error accessing torrent files." },
{ "missing_ctx_file", "Error accessing task context file." },
{ "nzb_no_group", "Cannot find the requested group on server." },
{ "nzb_not_found", "Article not fount on the server." },
{ "nzb_invalid_crc", "Invalid article CRC." },
{ "nzb_invalid_size", "Invalid article size." },
{ "nzb_invalid_filename", "Invalid filename." },
{ "nzb_open_failed", "Error opening." },
{ "nzb_write_failed", "Error writing." },
{ "nzb_missing_size", "Missing article size." },
{ "nzb_decode_error", "Article decoding error." },
{ "nzb_missing_segments", "Missing article segments." },
{ "nzb_error", "Other nzb error." },
{ "nzb_authentication_required", "Nzb server need authentication." }
};
}
public string GetErrorDescription()
{
if (Descriptions.ContainsKey(Error))
{
return Descriptions[Error];
}
return $"{Error} - Unknown error";
}
}
}

View File

@@ -0,0 +1,18 @@
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
{
public class FreeboxLogin
{
[JsonProperty(PropertyName = "logged_in")]
public bool LoggedIn { get; set; }
[JsonProperty(PropertyName = "challenge")]
public string Challenge { get; set; }
[JsonProperty(PropertyName = "password_salt")]
public string PasswordSalt { get; set; }
[JsonProperty(PropertyName = "password_set")]
public bool PasswordSet { get; set; }
[JsonProperty(PropertyName = "session_token")]
public string SessionToken { get; set; }
}
}

View File

@@ -0,0 +1,69 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
{
public class FreeboxResponse<T>
{
private static readonly Dictionary<string, string> Descriptions;
[JsonProperty(PropertyName = "success")]
public bool Success { get; set; }
[JsonProperty(PropertyName = "msg")]
public string Message { get; set; }
[JsonProperty(PropertyName = "error_code")]
public string ErrorCode { get; set; }
[JsonProperty(PropertyName = "result")]
public T Result { get; set; }
static FreeboxResponse()
{
Descriptions = new Dictionary<string, string>
{
// Common errors
{ "invalid_request", "Your request is invalid." },
{ "invalid_api_version", "Invalid API base url or unknown API version." },
{ "internal_error", "Internal error." },
// Login API errors
{ "auth_required", "Invalid session token, or no session token sent." },
{ "invalid_token", "The app token you are trying to use is invalid or has been revoked." },
{ "pending_token", "The app token you are trying to use has not been validated by user yet." },
{ "insufficient_rights", "Your app permissions does not allow accessing this API." },
{ "denied_from_external_ip", "You are trying to get an app_token from a remote IP." },
{ "ratelimited", "Too many auth error have been made from your IP." },
{ "new_apps_denied", "New application token request has been disabled." },
{ "apps_denied", "API access from apps has been disabled." },
// Download API errors
{ "task_not_found", "No task was found with the given id." },
{ "invalid_operation", "Attempt to perform an invalid operation." },
{ "invalid_file", "Error with the download file (invalid format ?)." },
{ "invalid_url", "URL is invalid." },
{ "not_implemented", "Method not implemented." },
{ "out_of_memory", "No more memory available to perform the requested action." },
{ "invalid_task_type", "The task type is invalid." },
{ "hibernating", "The downloader is hibernating." },
{ "need_bt_stopped_done", "This action is only valid for Bittorrent task in stopped or done state." },
{ "bt_tracker_not_found", "Attempt to access an invalid tracker object." },
{ "too_many_tasks", "Too many tasks." },
{ "invalid_address", "Invalid peer address." },
{ "port_conflict", "Port conflict when setting config." },
{ "invalid_priority", "Invalid priority." },
{ "ctx_file_error", "Failed to initialize task context file (need to check disk)." },
{ "exists", "Same task already exists." },
{ "port_outside_range", "Incoming port is not available for this customer." }
};
}
public string GetErrorDescription()
{
if (Descriptions.ContainsKey(ErrorCode))
{
return Descriptions[ErrorCode];
}
return $"{ErrorCode} - Unknown error";
}
}
}

View File

@@ -0,0 +1,227 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public class TorrentFreeboxDownload : TorrentClientBase<FreeboxDownloadSettings>
{
private readonly IFreeboxDownloadProxy _proxy;
public TorrentFreeboxDownload(IFreeboxDownloadProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
{
_proxy = proxy;
}
public override string Name => "Freebox Download";
protected IEnumerable<FreeboxDownloadTask> GetTorrents()
{
return _proxy.GetTasks(Settings).Where(v => v.Type.ToLower() == FreeboxDownloadTaskType.Bt.ToString().ToLower());
}
public override IEnumerable<DownloadClientItem> GetItems()
{
var torrents = GetTorrents();
var queueItems = new List<DownloadClientItem>();
foreach (var torrent in torrents)
{
var outputPath = new OsPath(torrent.DecodedDownloadDirectory);
if (Settings.DestinationDirectory.IsNotNullOrWhiteSpace())
{
if (!new OsPath(Settings.DestinationDirectory).Contains(outputPath))
{
continue;
}
}
if (Settings.Category.IsNotNullOrWhiteSpace())
{
var directories = outputPath.FullPath.Split('\\', '/');
if (!directories.Contains(Settings.Category))
{
continue;
}
}
var item = new DownloadClientItem()
{
DownloadId = torrent.Id,
Category = Settings.Category,
Title = torrent.Name,
TotalSize = torrent.Size,
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
RemainingSize = (long)(torrent.Size * (double)(1 - ((double)torrent.ReceivedPrct / 10000))),
RemainingTime = torrent.Eta <= 0 ? null : TimeSpan.FromSeconds(torrent.Eta),
SeedRatio = torrent.StopRatio <= 0 ? 0 : torrent.StopRatio / 100,
OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath)
};
switch (torrent.Status)
{
case FreeboxDownloadTaskStatus.Stopped: // task is stopped, can be resumed by setting the status to downloading
case FreeboxDownloadTaskStatus.Stopping: // task is gracefully stopping
item.Status = DownloadItemStatus.Paused;
break;
case FreeboxDownloadTaskStatus.Queued: // task will start when a new download slot is available the queue position is stored in queue_pos attribute
item.Status = DownloadItemStatus.Queued;
break;
case FreeboxDownloadTaskStatus.Starting: // task is preparing to start download
case FreeboxDownloadTaskStatus.Downloading:
case FreeboxDownloadTaskStatus.Retry: // you can set a task status to retry to restart the download task.
case FreeboxDownloadTaskStatus.Checking: // checking data before lauching download.
item.Status = DownloadItemStatus.Downloading;
break;
case FreeboxDownloadTaskStatus.Error: // there was a problem with the download, you can get an error code in the error field
item.Status = DownloadItemStatus.Warning;
item.Message = torrent.GetErrorDescription();
break;
case FreeboxDownloadTaskStatus.Done: // the download is over. For bt you can resume seeding setting the status to seeding if the ratio is not reached yet
case FreeboxDownloadTaskStatus.Seeding: // download is over, the content is Change to being shared to other users. The task will automatically stop once the seed ratio has been reached
item.Status = DownloadItemStatus.Completed;
break;
case FreeboxDownloadTaskStatus.Unknown:
default: // new status in API? default to downloading
item.Message = "Unknown download state: " + torrent.Status;
_logger.Info(item.Message);
item.Status = DownloadItemStatus.Downloading;
break;
}
item.CanBeRemoved = item.CanMoveFiles = torrent.Status == FreeboxDownloadTaskStatus.Done;
queueItems.Add(item);
}
return queueItems;
}
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
{
return _proxy.AddTaskFromUrl(magnetLink,
GetDownloadDirectory().EncodeBase64(),
ToBePaused(),
ToBeQueuedFirst(remoteEpisode),
GetSeedRatio(remoteEpisode),
Settings);
}
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
{
return _proxy.AddTaskFromFile(filename,
fileContent,
GetDownloadDirectory().EncodeBase64(),
ToBePaused(),
ToBeQueuedFirst(remoteEpisode),
GetSeedRatio(remoteEpisode),
Settings);
}
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
_proxy.DeleteTask(item.DownloadId, deleteData, Settings);
}
public override DownloadClientInfo GetStatus()
{
var destDir = GetDownloadDirectory();
return new DownloadClientInfo
{
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "::1" || Settings.Host == "localhost",
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) }
};
}
protected override void Test(List<ValidationFailure> failures)
{
try
{
_proxy.Authenticate(Settings);
}
catch (DownloadClientUnavailableException ex)
{
failures.Add(new ValidationFailure("Host", ex.Message));
failures.Add(new ValidationFailure("Port", ex.Message));
}
catch (DownloadClientAuthenticationException ex)
{
failures.Add(new ValidationFailure("AppId", ex.Message));
failures.Add(new ValidationFailure("AppToken", ex.Message));
}
catch (FreeboxDownloadException ex)
{
failures.Add(new ValidationFailure("ApiUrl", ex.Message));
}
}
private string GetDownloadDirectory()
{
if (Settings.DestinationDirectory.IsNotNullOrWhiteSpace())
{
return Settings.DestinationDirectory.TrimEnd('/');
}
var destDir = _proxy.GetDownloadConfiguration(Settings).DecodedDownloadDirectory.TrimEnd('/');
if (Settings.Category.IsNotNullOrWhiteSpace())
{
destDir = $"{destDir}/{Settings.Category}";
}
return destDir;
}
private bool ToBePaused()
{
return Settings.AddPaused;
}
private bool ToBeQueuedFirst(RemoteEpisode remoteEpisode)
{
if ((remoteEpisode.IsRecentEpisode() && Settings.RecentPriority == (int)FreeboxDownloadPriority.First) ||
(!remoteEpisode.IsRecentEpisode() && Settings.OlderPriority == (int)FreeboxDownloadPriority.First))
{
return true;
}
return false;
}
private double? GetSeedRatio(RemoteEpisode remoteEpisode)
{
if (remoteEpisode.SeedConfiguration == null || remoteEpisode.SeedConfiguration.Ratio == null)
{
return null;
}
return remoteEpisode.SeedConfiguration.Ratio.Value * 100;
}
}
}

View File

@@ -88,6 +88,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
if (!addHasSetShareLimits && setShareLimits)
{
Proxy.SetTorrentSeedingConfiguration(hash.ToLower(), remoteBook.SeedConfiguration, Settings);
try
{
Proxy.SetTorrentSeedingConfiguration(hash.ToLower(), remoteBook.SeedConfiguration, Settings);
@@ -286,7 +288,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
break;
case "forcedDL": //torrent is being downloaded, and was forced started
case "forcedDL": // torrent is being downloaded, and was forced started
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
case "moving": // torrent is being moved from a folder
case "downloading": // torrent is being downloaded and data is being transferred
item.Status = DownloadItemStatus.Downloading;

View File

@@ -142,20 +142,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
var request = BuildRequest(settings).Resource("/api/v2/torrents/add")
.Post()
.AddFormParameter("urls", torrentUrl);
if (settings.MusicCategory.IsNotNullOrWhiteSpace())
{
request.AddFormParameter("category", settings.MusicCategory);
}
// Note: ForceStart is handled by separate api call
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
{
request.AddFormParameter("paused", false);
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
{
request.AddFormParameter("paused", true);
}
AddTorrentDownloadFormParameters(request, settings);
if (seedConfiguration != null)
{
@@ -177,20 +165,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
.Post()
.AddFormUpload("torrents", fileName, fileContent);
if (settings.MusicCategory.IsNotNullOrWhiteSpace())
{
request.AddFormParameter("category", settings.MusicCategory);
}
// Note: ForceStart is handled by separate api call
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
{
request.AddFormParameter("paused", false);
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
{
request.AddFormParameter("paused", true);
}
AddTorrentDownloadFormParameters(request, settings);
if (seedConfiguration != null)
{
@@ -259,6 +234,34 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
}
}
private void AddTorrentDownloadFormParameters(HttpRequestBuilder request, QBittorrentSettings settings)
{
if (settings.MusicCategory.IsNotNullOrWhiteSpace())
{
request.AddFormParameter("category", settings.MusicCategory);
}
// Note: ForceStart is handled by separate api call
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
{
request.AddFormParameter("paused", false);
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
{
request.AddFormParameter("paused", true);
}
if (settings.SequentialOrder)
{
request.AddFormParameter("sequentialDownload", true);
}
if (settings.FirstAndLast)
{
request.AddFormParameter("firstLastPiecePrio", true);
}
}
public void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
{
var request = BuildRequest(settings).Resource("/api/v2/torrents/setShareLimits")

View File

@@ -63,6 +63,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[FieldDefinition(10, Label = "Initial State", Type = FieldType.Select, SelectOptions = typeof(QBittorrentState), HelpText = "Initial state for torrents added to qBittorrent. Note that Forced Torrents do not abide by seed restrictions")]
public int InitialState { get; set; }
[FieldDefinition(11, Label = "Sequential Order", Type = FieldType.Checkbox, HelpText = "Download in sequential order (qBittorrent 4.1.0+)")]
public bool SequentialOrder { get; set; }
[FieldDefinition(12, Label = "First and Last First", Type = FieldType.Checkbox, HelpText = "Download first and last pieces first (qBittorrent 4.1.0+)")]
public bool FirstAndLast { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
public double Progress { get; set; } // Torrent progress (%/100)
public BigInteger Eta { get; set; } // Torrent ETA (seconds)
public BigInteger Eta { get; set; } // Torrent ETA (seconds) (QBit contains a bug exceeding ulong limits)
public string State { get; set; } // Torrent state. See possible values here below

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Newtonsoft.Json;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Download.Clients.Sabnzbd.JsonConverters;
@@ -7,10 +7,14 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabnzbdConfig
{
public SabnzbdConfig()
{
Categories = new List<SabnzbdCategory>();
Servers = new List<object>();
}
public SabnzbdConfigMisc Misc { get; set; }
public List<SabnzbdCategory> Categories { get; set; }
public List<object> Servers { get; set; }
}

View File

@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
public override string Name => "rTorrent";
public override ProviderMessage Message => new ProviderMessage($"Readarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers. After importing it will also set \"{_imported_view}\" as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", ProviderMessageType.Info);
public override ProviderMessage Message => new ProviderMessage($"rTorrent will not pause torrents when they meet the seed criteria. Readarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers only when Remove Completed is enabled. After importing it will also set \"{_imported_view}\" as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", ProviderMessageType.Info);
public override IEnumerable<DownloadClientItem> GetItems()
{

View File

@@ -37,10 +37,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to ruTorrent")]
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to rTorrent")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. When using ruTorrent this usually is RPC2 or (path to ruTorrent)/plugins/rpc/rpc.php")]
[FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. This is usually RPC2 or [path to ruTorrent]/plugins/rpc/rpc.php when using ruTorrent.")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(10, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing books released over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(11, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to ruTorrent in a stopped state")]
[FieldDefinition(11, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to rTorrent in a stopped state. This may break magnet files.")]
public bool AddStopped { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -164,14 +164,14 @@ namespace NzbDrone.Core.Download
}
else
{
_logger.Debug()
_logger.ForDebugEvent()
.Message("No books were just imported, but all books were previously imported, possible issue with download history.")
.Property("AuthorId", trackedDownload.RemoteBook.Author.Id)
.Property("DownloadId", trackedDownload.DownloadItem.DownloadId)
.Property("Title", trackedDownload.DownloadItem.Title)
.Property("Path", trackedDownload.DownloadItem.OutputPath.ToString())
.WriteSentryWarn("DownloadHistoryIncomplete")
.Write();
.Log();
}
trackedDownload.State = TrackedDownloadState.Imported;

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Download
@@ -13,5 +14,6 @@ namespace NzbDrone.Core.Download
public DownloadClientItemClientInfo DownloadClientInfo { get; set; }
public string DownloadId { get; set; }
public string Message { get; set; }
public TrackedDownload TrackedDownload { get; set; }
}
}

View File

@@ -110,7 +110,7 @@ namespace NzbDrone.Core.Download
bookGrabbedEvent.DownloadId = downloadClientId;
}
_logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle);
_logger.ProgressInfo("Report sent to {0} from indexer {1}. {2}", downloadClient.Definition.Name, remoteBook.Release.Indexer, downloadTitle);
_eventAggregator.PublishEvent(bookGrabbedEvent);
}
}

View File

@@ -42,6 +42,7 @@ namespace NzbDrone.Core.Download
SourceTitle = trackedDownload.DownloadItem.Title,
DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo,
DownloadId = trackedDownload.DownloadItem.DownloadId,
TrackedDownload = trackedDownload,
Message = "Manually ignored"
};

View File

@@ -52,7 +52,7 @@ namespace NzbDrone.Core.HealthCheck
.AddQueryParam("version", BuildInfo.Version)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
.AddQueryParam("runtime", "netcore")
.AddQueryParam("branch", _configFileProvider.Branch)
.Build();
try

View File

@@ -200,6 +200,7 @@ namespace NzbDrone.Core.History
};
history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson());
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteBook?.ParsedBookInfo?.ReleaseGroup);
_historyRepository.Insert(history);
}
}
@@ -235,6 +236,7 @@ namespace NzbDrone.Core.History
history.Data.Add("ImportedPath", message.ImportedBook.Path);
history.Data.Add("DownloadClient", message.DownloadClientInfo?.Type);
history.Data.Add("DownloadClientName", message.DownloadClientInfo?.Name);
history.Data.Add("ReleaseGroup", message.BookInfo.ReleaseGroup);
_historyRepository.Insert(history);
}
@@ -286,6 +288,7 @@ namespace NzbDrone.Core.History
};
history.Data.Add("Reason", message.Reason.ToString());
history.Data.Add("ReleaseGroup", message.BookFile.ReleaseGroup);
_historyRepository.Insert(history);
}
@@ -307,6 +310,7 @@ namespace NzbDrone.Core.History
history.Data.Add("SourcePath", sourcePath);
history.Data.Add("Path", path);
history.Data.Add("ReleaseGroup", message.BookFile.ReleaseGroup);
_historyRepository.Insert(history);
}
@@ -358,6 +362,7 @@ namespace NzbDrone.Core.History
};
history.Data.Add("DownloadClient", message.DownloadClientInfo.Name);
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteBook?.ParsedBookInfo?.ReleaseGroup);
history.Data.Add("Message", message.Message);
historyToAdd.Add(history);

View File

@@ -7,22 +7,22 @@ namespace NzbDrone.Core.IndexerSearch
{
public class AuthorSearchService : IExecute<AuthorSearchCommand>
{
private readonly ISearchForNzb _nzbSearchService;
private readonly ISearchForReleases _releaseSearchService;
private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly Logger _logger;
public AuthorSearchService(ISearchForNzb nzbSearchService,
public AuthorSearchService(ISearchForReleases releaseSearchService,
IProcessDownloadDecisions processDownloadDecisions,
Logger logger)
{
_nzbSearchService = nzbSearchService;
_releaseSearchService = releaseSearchService;
_processDownloadDecisions = processDownloadDecisions;
_logger = logger;
}
public void Execute(AuthorSearchCommand message)
{
var decisions = _nzbSearchService.AuthorSearch(message.AuthorId, false, message.Trigger == CommandTrigger.Manual, false);
var decisions = _releaseSearchService.AuthorSearch(message.AuthorId, false, message.Trigger == CommandTrigger.Manual, false);
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
_logger.ProgressInfo("Author search completed. {0} reports downloaded.", processed.Grabbed.Count);

View File

@@ -17,21 +17,21 @@ namespace NzbDrone.Core.IndexerSearch
IExecute<MissingBookSearchCommand>,
IExecute<CutoffUnmetBookSearchCommand>
{
private readonly ISearchForNzb _nzbSearchService;
private readonly ISearchForReleases _releaseSearchService;
private readonly IBookService _bookService;
private readonly IBookCutoffService _bookCutoffService;
private readonly IQueueService _queueService;
private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly Logger _logger;
public BookSearchService(ISearchForNzb nzbSearchService,
public BookSearchService(ISearchForReleases releaseSearchService,
IBookService bookService,
IBookCutoffService bookCutoffService,
IQueueService queueService,
IProcessDownloadDecisions processDownloadDecisions,
Logger logger)
{
_nzbSearchService = nzbSearchService;
_releaseSearchService = releaseSearchService;
_bookService = bookService;
_bookCutoffService = bookCutoffService;
_queueService = queueService;
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var book in books)
{
List<DownloadDecision> decisions;
decisions = _nzbSearchService.BookSearch(book.Id, false, userInvokedSearch, false);
decisions = _releaseSearchService.BookSearch(book.Id, false, userInvokedSearch, false);
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
downloadedCount += processed.Grabbed.Count;
@@ -61,7 +61,7 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var bookId in message.BookIds)
{
var decisions =
_nzbSearchService.BookSearch(bookId, false, message.Trigger == CommandTrigger.Manual, false);
_releaseSearchService.BookSearch(bookId, false, message.Trigger == CommandTrigger.Manual, false);
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
_logger.ProgressInfo("Book search completed. {0} reports downloaded.", processed.Grabbed.Count);

View File

@@ -14,13 +14,13 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.IndexerSearch
{
public interface ISearchForNzb
public interface ISearchForReleases
{
List<DownloadDecision> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
List<DownloadDecision> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
}
public class NzbSearchService : ISearchForNzb
public class ReleaseSearchService : ISearchForReleases
{
private readonly IIndexerFactory _indexerFactory;
private readonly IBookService _bookService;
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.IndexerSearch
private readonly IMakeDownloadDecision _makeDownloadDecision;
private readonly Logger _logger;
public NzbSearchService(IIndexerFactory indexerFactory,
public ReleaseSearchService(IIndexerFactory indexerFactory,
IBookService bookService,
IAuthorService authorService,
IMakeDownloadDecision makeDownloadDecision,

View File

@@ -1,17 +0,0 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public class IndexerSettingUpdatedEvent : IEvent
{
public string IndexerName { get; private set; }
public IProviderConfig IndexerSetting { get; private set; }
public IndexerSettingUpdatedEvent(string indexerName, IProviderConfig indexerSetting)
{
IndexerName = indexerName;
IndexerSetting = indexerSetting;
}
}
}

View File

@@ -4,6 +4,7 @@ using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.Indexers
{
@@ -13,7 +14,7 @@ namespace NzbDrone.Core.Indexers
TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason);
}
public class SeedConfigProvider : ISeedConfigProvider, IHandle<IndexerSettingUpdatedEvent>
public class SeedConfigProvider : ISeedConfigProvider, IHandle<ProviderUpdatedEvent<IIndexer>>
{
private readonly IIndexerFactory _indexerFactory;
private readonly ICached<SeedCriteriaSettings> _cache;
@@ -82,7 +83,7 @@ namespace NzbDrone.Core.Indexers
}
}
public void Handle(IndexerSettingUpdatedEvent message)
public void Handle(ProviderUpdatedEvent<IIndexer> message)
{
_cache.Clear();
}

View File

@@ -50,7 +50,7 @@ namespace NzbDrone.Core.Indexers
{
private static readonly SeedCriteriaSettingsValidator Validator = new SeedCriteriaSettingsValidator();
[FieldDefinition(0, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is download client's default", Advanced = true)]
[FieldDefinition(0, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is download client's default. Ratio should be at least 1.0 and follow the indexers rules")]
public double? SeedRatio { get; set; }
[FieldDefinition(1, Type = FieldType.Textbox, Label = "Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)]

View File

@@ -117,7 +117,6 @@ namespace NzbDrone.Core.Instrumentation
syslogTarget.MessageSend.Protocol = ProtocolType.Udp;
syslogTarget.MessageSend.Udp.Port = syslogPort;
syslogTarget.MessageSend.Udp.Server = syslogServer;
syslogTarget.MessageSend.Udp.ReconnectInterval = 500;
syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424;
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName;

View File

@@ -557,5 +557,6 @@
"RestartRequiredHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
"AddList": "اضف قائمة",
"RenameFiles": "إعادة تسمية الملفات",
"Test": "اختبار"
"Test": "اختبار",
"ManualImportSelectEdition": "استيراد يدوي - حدد فيلم"
}

View File

@@ -556,5 +556,6 @@
"RestartRequiredHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
"Test": "Тест",
"AddList": "Добавяне на списък",
"RenameFiles": "Преименувайте файловете"
"RenameFiles": "Преименувайте файловете",
"ManualImportSelectEdition": "Ръчно импортиране - Изберете филм"
}

View File

@@ -51,7 +51,7 @@
"Cancel": "zrušení",
"CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?",
"CertificateValidation": "Ověření certifikátu",
"CertificateValidationHelpText": "Změňte, jak přísné je ověření certifikace HTTPS",
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
"ChangeFileDate": "Změnit datum souboru",
"ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena",
"ChmodFolder": "Složka chmod",
@@ -556,5 +556,6 @@
"RestartRequiredHelpTextWarning": "Vyžaduje restart, aby se projevilo",
"RenameFiles": "Přejmenovat soubory",
"Test": "Test",
"AddList": "Přidat seznam"
"AddList": "Přidat seznam",
"ManualImportSelectEdition": "Ruční import - vyberte Film"
}

View File

@@ -556,5 +556,6 @@
"RestartRequiredHelpTextWarning": "Kræver genstart for at træde i kraft",
"AddList": "Tilføj Liste",
"Test": "Prøve",
"RenameFiles": "Omdøb filer"
"RenameFiles": "Omdøb filer",
"ManualImportSelectEdition": "Manuel import - Vælg film"
}

View File

@@ -44,7 +44,7 @@
"Cancel": "Abbrechen",
"CancelMessageText": "Diese laufende Aufgabe wirklich abbrechen?",
"CertificateValidation": "Zertifikat Validierung",
"CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist. Nicht anpassen, außer du kennst das Risiko.",
"CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist. Ändern Sie nicht wenn Ihnen die Risiken nicht bewusst sind.",
"ChangeFileDate": "Erstelldatum der Datei anpassen",
"ChangeHasNotBeenSavedYet": "Änderung wurde noch nicht gespeichert",
"ChmodFolder": "chmod Ordner",
@@ -92,8 +92,8 @@
"DeleteNotificationMessageText": "Benachrichtigung '{0}' wirklich löschen?",
"DeleteQualityProfile": "Qualitätsdefinition löschen",
"DeleteQualityProfileMessageText": "Qualitätsprofil '{0}' wirklich löschen?",
"DeleteReleaseProfile": "Verzögerungsprofil löschen",
"DeleteReleaseProfileMessageText": "Bist du sicher, dass du dieses Verzögerungs-Profil löschen willst?",
"DeleteReleaseProfile": "Release-Profil löschen",
"DeleteReleaseProfileMessageText": "Bist du sicher, dass du dieses Release-Profil löschen willst?",
"DeleteRootFolderMessageText": "Indexer '{0}' wirklich löschen?",
"DeleteSelectedBookFiles": "Ausgewählte Filmdateien löschen",
"DeleteSelectedBookFilesMessageText": "Ausgewählte Filme wirklich löschen?",
@@ -172,7 +172,7 @@
"ImportedTo": "Importiert nach",
"Importing": "Importiere",
"IncludeHealthWarningsHelpText": "Zustandswarnung",
"IncludeUnknownAuthorItemsHelpText": "Einträge ohne eine Zuordnung in der Warteschlange anzeigen. Dies könnten gelöschte Filme oder alles andere mit Radarrs Downloadkategorie sein",
"IncludeUnknownAuthorItemsHelpText": "Einträge ohne eine Zuordnung in der Warteschlange anzeigen. Dies könnten gelöschte Autoren, Bücher oder alles andere mit Readarrs Downloadkategorie sein",
"IncludeUnmonitored": "Nicht beobachtete einbeziehen",
"Indexer": "Indexer",
"IndexerPriority": "Priorität",
@@ -291,7 +291,7 @@
"RemoveFromQueue": "Aus der Warteschlage entfernen",
"RemoveHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.",
"RemoveSelected": "Auswahl entfernen",
"RemoveSelectedMessageText": "Bist du icher, dass du die ausgewählten Einträge aus der Sperrliste entfernen willst?",
"RemoveSelectedMessageText": "Sind sie sicher, dass die ausgewählten Einträge aus der Sperrliste entfernt werden sollen?",
"RemoveTagExistingTag": "Vorhandener Tag",
"RemoveTagRemovingTag": "Tag entfernen",
"RemovedFromTaskQueue": "Aus der Aufgabenwarteschlage entfernt",
@@ -301,7 +301,7 @@
"RequiredHelpText": "Das Release mus mindesten eines der Begriffe beinhalten ( Groß-/Kleinschreibung wird nicht beachtet )",
"RequiredPlaceHolder": "Neue Beschränkung hinzufügen",
"RescanAfterRefreshHelpTextWarning": "Wenn nicht \"Immer (Always)\" ausgewählt wird, werden Dateiänderungen nicht automatisch erkannt",
"RescanAuthorFolderAfterRefresh": "Nach dem aktualisieren den Filmordner neu scannen",
"RescanAuthorFolderAfterRefresh": "Nach dem Aktualisieren den Autorordner neu scannen",
"Reset": "Zurücksetzen",
"ResetAPIKey": "API-Schlüssel zurücksetzen",
"ResetAPIKeyMessageText": "Bist du sicher, dass du den API-Schlüssel zurücksetzen willst?",
@@ -349,7 +349,7 @@
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Wird in der Wochenansicht über jeder Spalte angezeigt",
"Size": " Größe",
"SkipFreeSpaceCheck": "Pürfung des freien Speichers überspringen",
"SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere dies wenn es nicht möglich ist, den freien Speicherplatz vom Stammverzeichnis zu ermitteln",
"SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere diese Option, wenn es Readarr nicht möglich ist, den freien Speicherplatz des Stammverzeichnises für Autoren zu erkennen",
"SorryThatAuthorCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.",
"SorryThatBookCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.",
"Source": "Quelle",
@@ -426,7 +426,7 @@
"UnmonitoredHelpText": "Nicht beobachtete Filme im iCal-Feed einschließen",
"UpdateAll": "Alle aktualisieren",
"UpdateAutomaticallyHelpText": "Updates automatisch herunteraden und installieren. Es kann weiterhin unter \"System -> Updates\" ein manuelles Update angestoßen werden",
"UpdateMechanismHelpText": "Benutze den Built-In Updater oder ein Script",
"UpdateMechanismHelpText": "Benutze Readarr's Built-In Updater oder ein Script",
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
"Updates": "Updates",
"UpgradeAllowedHelpText": "Wenn deaktiviert wird die Qualität nicht verbessert",
@@ -546,7 +546,7 @@
"ImportListSettings": "Allgemeine Einstellungen der Importliste",
"ImportListSpecificSettings": "Listenspezifische Einstellungen importieren",
"IndexerLongTermStatusCheckSingleClientMessage": "Indexer wegen über 6 Stunden langen bestehenden Fehlern nicht verfügbar: {0}",
"IndexerPriorityHelpText": "Indexer Priorität von 1 (höchste) bis 50 (niedrigste). Standard: 25.",
"IndexerPriorityHelpText": "Indexer Priorität von 1 (höchste) bis 50 (niedrigste). Standard: 25. Wird beim Holen von Veröffentlichungen als Tiebreaker für ansonsten gleiche Veröffentlichungen verwendet, Readarr benützt weiterhin alle aktivierten Indexer für RSS-Synchronisierung und -Suche.",
"IndexerRssHealthCheckNoAvailableIndexers": "Alle RSS-fähigen Indexer sind aufgrund der kürzlichen Indexerfehler vorübergehend nicht verfügbar",
"DiscNumber": "Plattennummer",
"Lists": "Listen",
@@ -702,7 +702,7 @@
"ImportListStatusCheckSingleClientMessage": "Listen aufgrund von Fehlern nicht verfügbar: {0}",
"ImportMechanismHealthCheckMessage": "Aktiviere die Verarbeitung der abgeschlossenen Downloads",
"IndexerRssHealthCheckNoIndexers": "Da keine Indexer mit aktivierter RSS-Synchronisierung aktiviert sind, erfasst Readarr neue Erscheinungen nicht automatisch",
"IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit interaktiver Suche aktiviert, Readarr liefert keine interaktiven Suchergebnisse",
"IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit interaktiver Suche verfügbar, Readarr liefert keine interaktiven Suchergebnisse",
"IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}",
"ChownGroup": "chown Gruppe",
"AllowFingerprintingHelpText": "Benutze Fingerabdrücke um die Genauigkeit der Buch Übereinstimmungen zu verbessern",
@@ -713,7 +713,7 @@
"PathHelpText": "Stammordner für die Musikbibliothek",
"ProxyCheckBadRequestMessage": "Proxy konnte nicht getestet werden. StatusCode: {0}",
"ProxyCheckFailedToTestMessage": "Proxy konnte nicht getestet werden: {0}",
"Queued": "Eingereiht",
"Queued": "In der Warteschlange",
"RefreshAndScan": "Aktualisieren",
"SettingsRemotePathMappingRemotePathHelpText": "Root-Pfad zum Verzeichnis, auf das der Download-Client zugreift",
"Yesterday": "Gestern",
@@ -725,7 +725,7 @@
"RemotePathMappingCheckDockerFolderMissing": "Docker erkannt; Downloader {0} speichert Downloads in {1}, aber dieser Ordner scheint nicht im Container zu existieren. Überprüfe die Remote-Pfadzuordnungen und die Container Volume Einstellungen.",
"RemotePathMappingCheckFilesGenericPermissions": "Downloader {0} meldet Dateien in {1}, aber Radarr kann dieses Verzeichnis nicht sehen.Möglicherweise müssen die Verzeichnisreche angepasst werden.",
"RemotePathMappingCheckFolderPermissions": "Radarr kann das Downloadverzeichnis sehen, aber nicht verarbeiten {0}. Möglicherwiese ein Rechteproblem.",
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möchlicherweise müssen die Verzeichnisrechte angepasst werden.",
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Readarr kann dieses Verzeichnis nicht sehen. Möglicherweise müssen die Verzeichnisrechte angepasst werden.",
"RemotePathMappingCheckLocalWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
"Monitor": "Beobachten",
"MusicBrainzAuthorID": "MusicBranz Künstler Id",
@@ -780,11 +780,102 @@
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",
"RestartRequiredHelpTextWarning": "Erfordert einen Neustart",
"UseCalibreContentServer": "Calibre-Content-Server",
"DataExistingBooks": "Beobachte Alben die Dateien haben oder noch nicht veröffentlicht wurden",
"DataFirstBook": "Beobachte die ersten Alben. Alle anderen Alben werden ignoriert",
"DataFuturebooks": "Überwachung von Alben die noch nicht veröffentlicht wurden",
"DataMissingBooks": "Beobachte Alben, die noch keine Dateien haben oder noch nicht veröffentlicht wurden",
"DataExistingBooks": "Beobachte Bücher die Dateien haben oder noch nicht veröffentlicht wurden",
"DataFirstBook": "Beobachte das erste Buch. Alle anderen Bücher werden ignoriert",
"DataFuturebooks": "Überwachung von Büchern die noch nicht veröffentlicht wurden",
"DataMissingBooks": "Beobachte Bücher, die noch keine Dateien haben oder noch nicht veröffentlicht wurden",
"Test": "Testen",
"DataNone": "Es werden keine Alben beobachtet",
"RenameFiles": "Dateien umbenennen"
"DataNone": "Es werden keine Bücher beobachtet",
"RenameFiles": "Dateien umbenennen",
"LoadingEditionsFailed": "Das Laden der Ausgaben ist fehlgeschlagen",
"MissingBooksAuthorMonitored": "Fehlende Bücher (Autoren überwacht)",
"MonitorBook": "Buch überwachen",
"MinimumPages": "Mindestseiten",
"MassBookSearch": "Massensuche nach Büchern",
"MediaManagementSettingsSummary": "Namensgebung, Dateimanagement-Einstellungen und Root-Ordner",
"MinimumPopularity": "Mindestpolularität",
"MinPagesHelpText": "Bücher mit weniger Seiten als dieses ignorieren",
"MinPopularityHelpText": "Popularität ist Durchschnittsbewertung * Anzahl der Stimmen",
"MissingBooks": "Fehlende Bücher",
"MonitorAuthor": "Autor überwachen",
"MissingBooksAuthorNotMonitored": "Fehlende Bücher (Autor nicht überwacht)",
"ConvertToFormat": "In Format umwandeln",
"EditList": "Liste bearbeiten",
"ManualImportSelectEdition": "Manueller Import - Ausgabe auswählen",
"MetadataSettingsSummary": "Metadaten-Dateien erstellen, wenn Bücher importiert oder Autoren aktualisiert werden",
"DataAllBooks": "Alle Bücher überwachen",
"DataLatestBook": "Die neusten und zukünftigen Bücher überwachen",
"Database": "Datenbank",
"DataListMonitorAll": "Autoren und alle Bücher für jeden Autor werden auf der Import-Liste miteinbezogen",
"DataListMonitorNone": "Autoren oder Bücher nicht überwachen",
"DataListMonitorSpecificBook": "Autoren überwachen aber nur Bücher überwachen, welche explizit in der Liste miteinbezogen wurden",
"DataNewAllBooks": "Alle neuen Bücher überwachen",
"DataNewBooks": "Alle neuen Bücher, welche nach dem neusten existierenden Buch veröffentlicht werden, überwachen",
"DataNewNone": "Keine neuen Bücher überwachen",
"MassBookSearchWarning": "Sind Sie sicher, dass Sie eine Massensuche für {0} Bücher starten wollen?",
"NoTagsHaveBeenAddedYet": "Es wurden bisher noch keine Tags hinzugefügt. Fügen Sie Tags hinzu um Autoren mit Verzögerunsprofilen, Einschränkungen oder Benachrichtigungen zu verknüpfen. Klicken Sie {0} um mehr über Tags in Readarr herauszufinden.",
"OutputFormatHelpText": "Optional kann Calibre aufgefordert werden, beim Import in andere Formate zu konvertieren. Kommagetrennte Liste.",
"UsernameHelpText": "Calibre Inhaltsserver Benutzername",
"MonitorExistingBooks": "Vorhandene Bücher überwachen",
"MonitoringOptionsHelpText": "Welche Bücher sollen überwacht werden nachdem der Autor hinzugefügt wurde (einmalige Anpassung)",
"MonitorNewBooks": "Neue Bücher überwachen",
"MonitorNewItems": "Neue Bücher überwachen",
"MonitorNewItemsHelpText": "Welche neuen Bücher sollen überwacht werden",
"NewBooks": "Neue Bücher",
"NoHistoryBlocklist": "Keine History Blockliste",
"NoName": "Namen nicht anzeigen",
"OnAuthorDelete": "Beim Löschen eines Autors",
"OnAuthorDeleteHelpText": "Beim Löschen eines Autors",
"OnBookDelete": "Beim Löschen eines Buches",
"OnBookRetagHelpText": "Bei neu markieren eines Buches",
"RefreshBook": "Buch aktualisieren",
"SearchBoxPlaceHolder": "z.B Krieg und Frieden, goodreads:656, isbn:067003469X, asin:B00JCDK5ME",
"SendMetadataToCalibre": "Metadaten an Calibre senden",
"SeriesTotal": "Serie ({0})",
"SetReadarrTags": "Setze Readarr Tags",
"ShouldMonitorExisting": "Existierende Bücher überwachen",
"ShouldMonitorHelpText": "Neue Autoren und Bücher aus dieser Liste überwachen",
"ShouldSearchHelpText": "Dursuche Indexer nach neu hinzugefügten Einträgen. Vorsichtig benutzen bei großen Listen.",
"SkipSecondarySeriesBooks": "Überspringe Bücher von Sekundarserien",
"SpecificBook": "Bestimmtes Buch",
"TheFollowingFilesWillBeDeleted": "Die folgenden Dateien werden gelöscht:",
"TooManyBooks": "Fehlende oder zu viele Bücher? Ändern oder erstellen Sie ein neues",
"TrackTitle": "Track Titel",
"UseSslHelpText": "Benutze SSL um mit dem Calibre Inhaltsserver zu verbinden",
"WriteMetadataTags": "Schreibe Metadaten Tags",
"WriteTagsAll": "Alle Dateien; nur erster Import",
"WriteTagsNew": "Nur für neue Downloads",
"WriteTagsSync": "Alle Dateien; mit Goodreads synchronisiert bleiben",
"SearchForAllCutoffUnmetBooks": "Suche nach allen abgeschnittenen unerfüllten Büchern",
"OnBookTagUpdate": "Bei Update eines Buch Tags",
"ProfilesSettingsSummary": "Qualität, Metadaten, Verzögerung und Release Profile",
"RenameBooks": "Bücher umbenennen",
"SearchForNewItems": "Suche nach neuen Einträgen",
"SeriesNumber": "Seriennummer",
"ShouldMonitorExistingHelpText": "Bücher in dieser Liste welche bereits in Readarr sind überwachen",
"SkipBooksWithNoISBNOrASIN": "Überspringe Bücher ohne ISBN oder ASIN",
"StatusEndedDeceased": "Verstorben",
"UpdateCovers": "Covers aktualisieren",
"UpdateCoversHelpText": "Buchcovers in Calibre so einstellen, dass sie mit denen aus Readarr übereinstimmen",
"UrlBaseHelpText": "Fügt ein Prefix zur Calibre Url hinzu, z.B http://[host]:[port]/[urlBasis]",
"UseSSL": "Benutze SSL",
"MonitorBookExistingOnlyWarning": "Dies ist eine einmalige Anpassung der Überwachungseinstellung für jedes Buch. Verwenden Sie die Option unter Autor/Bearbeiten, um festzulegen, was bei neu hinzugefügten Büchern geschieht",
"MonitoredHelpText": "Readarr wird das Buch suchen und herunterladen",
"MonitoredAuthorIsUnmonitored": "Autor wird nicht überwacht",
"MonitoredAuthorIsMonitored": "Autor wird überwacht",
"NameLastFirst": "Nachname, Vorname",
"NameFirstLast": "Vorname Nachname",
"NameStyle": "Autor Namensstil",
"NETCore": ".NET Core",
"ShowLastBook": "Zeige letztes Buch",
"SkipBooksWithMissingReleaseDate": "Überspringe Bücher ohne Erscheinungsdatum",
"Monitoring": "Überwachung",
"OnBookDeleteHelpText": "Beim Löschen eines Buches",
"RefreshAuthor": "Autor aktualisieren",
"RefreshInformation": "Informationen aktualisieren",
"SearchBook": "Buch suchen",
"ShowBookCount": "Zeige Anzahl an Büchern",
"SkipPartBooksAndSets": "Überspringe Teilbücher und Sets",
"TagsHelpText": "Gilt für Autoren mit mindestens einem passenden Tag. Leer lassen, um auf alle Autoren anzuwenden",
"TagsSettingsSummary": "Verwalten von Autoren-, Profil-, Beschränkungs- und Benachrichtigungs-Tags"
}

View File

@@ -556,5 +556,6 @@
"RestartRequiredHelpTextWarning": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ",
"Test": "Δοκιμή",
"AddList": "Προσθήκη Λίστας",
"RenameFiles": "Μετονομασία αρχείων"
"RenameFiles": "Μετονομασία αρχείων",
"ManualImportSelectEdition": "Μη αυτόματη εισαγωγή - Επιλέξτε ταινία"
}

View File

@@ -34,6 +34,8 @@
"ApiKeyHelpTextWarning": "Requires restart to take effect",
"AppDataDirectory": "AppData directory",
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
"ApplicationURL": "Application URL",
"ApplicationUrlHelpText": "This application's external URL including http(s)://, port and URL base",
"ApplyTags": "Apply Tags",
"ApplyTagsHelpTexts1": "How to apply tags to the selected author",
"ApplyTagsHelpTexts2": "Add: Add the tags the existing list of tags",

View File

@@ -216,7 +216,7 @@
"MinimumLimits": "Límites Mínimos",
"Missing": "Falta",
"Mode": "Modo",
"Monitored": "Monitoreadas",
"Monitored": "Monitoreada",
"MonoVersion": "Version de Mono",
"MoreInfo": "Más Información",
"MustContain": "Debe Contener",
@@ -235,8 +235,8 @@
"NotificationTriggers": "Desencadenantes de Notificaciones",
"OnGrabHelpText": "Al Capturar",
"OnHealthIssueHelpText": "En Problema de Salud",
"OnRenameHelpText": "En Renombrado",
"OnUpgradeHelpText": "Al Actualizar",
"OnRenameHelpText": "Al Renombrar",
"OnUpgradeHelpText": "Al Mejorar La Calidad",
"OpenBrowserOnStart": "Abrir navegador al arrancar",
"Options": "Opciones",
"Original": "Original",
@@ -269,7 +269,7 @@
"QualityProfiles": "Perfiles de Calidad",
"QualitySettings": "Ajustes de Calidad",
"Queue": "Cola",
"RSSSync": "Sincronización RSS",
"RSSSync": "Sincronizar RSS",
"RSSSyncInterval": "Intervalo de Sincronización de RSS",
"ReadTheWikiForMoreInformation": "Lee la Wiki para más información",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr soporta cualquier indexer que utilice el estandar Newznab, como también cualquiera de los indexers listados debajo.",
@@ -557,8 +557,8 @@
"OnBookFileDeleteForUpgradeHelpText": "En archivo de película Eliminar para actualizar",
"OnBookFileDeleteHelpText": "Al eliminar archivo de película",
"OnGrab": "Al Capturar",
"OnRename": "En Renombrado",
"OnUpgrade": "Al Actualizar",
"OnRename": "Al Renombrar",
"OnUpgrade": "Al Mejorar La Calidad",
"ProxyCheckBadRequestMessage": "Fallo al comprobar el proxy. StatusCode: {0}",
"ProxyCheckFailedToTestMessage": "Fallo al comprobar el proxy: {0}",
"QualitySettingsSummary": "Tamaños de calidad y nombrado",
@@ -597,5 +597,9 @@
"RestartRequiredHelpTextWarning": "Requiere reiniciar para que surta efecto",
"AddList": "Añadir lista",
"RenameFiles": "Renombrar Archivos",
"Test": "Test"
"Test": "Test",
"InstanceName": "Nombre de Instancia",
"InstanceNameHelpText": "Nombre de instancia en pestaña y para nombre de aplicación en Syslog",
"Database": "Base de Datos",
"ManualImportSelectEdition": "Importar Manualmente - Seleccionar Película"
}

View File

@@ -20,11 +20,11 @@
"Analytics": "Analytiikka",
"AnalyticsEnabledHelpText": "Lähetä nimettömiä käyttö- ja virhetietoja sovelluksen palvelimille. Tämä sisältää tietoja selaimestasi, verkkokäyttöliittymän sivujen käytöstä, virheraportoinnista sekä käyttöjärjestelmästäsi ja versiosta. Käytämme näitä tietoja ominaisuuksien ja virhekorjauksien painotukseen.",
"AppDataDirectory": "AppData-kansio",
"ApplyTags": "Toimenpide tunnisteille",
"ApplyTags": "Tunnistetoimenpide",
"ApplyTagsHelpTexts1": "Miten tunnisteita sovelletaan valittuun kirjailijaan",
"ApplyTagsHelpTexts2": " 'Lisää' syötetyt tunnisteet aiempiin tunnisteisiin",
"ApplyTagsHelpTexts3": " 'Poista' ainoastaan syötetyt tunnisteet",
"ApplyTagsHelpTexts4": " 'Korvaa' kaikki aiemmat tunnisteet tai poista kaikki tunnisteet jättämällä tyhjäksi",
"ApplyTagsHelpTexts3": "- \"Poista\" tyhjentää syötetyt tunnisteet.",
"ApplyTagsHelpTexts4": "- \"Korvaa\" nykyiset tunnisteet syötetyillä tai tyhjennä kaikki tunnisteet jättämällä tyhjäksi.",
"Authentication": "Todennus",
"AuthenticationMethodHelpText": "Vaadi käyttäjätunnus ja salasana.",
"AuthorClickToChangeBook": "Vaihda kirjaa painamalla",
@@ -36,7 +36,7 @@
"BackupRetentionHelpText": "Säilytysjaksoa vanhemmat, automaattiset varmuuskopiot poistetaan automaattisesti.",
"Backups": "Varmuuskopiointi",
"BindAddress": "Sidososoite",
"BindAddressHelpText": "Toimiva IPv4-osoite tai jokerimerkkinä '*' (tähti) kaikille yhteyksille.",
"BindAddressHelpText": "Toimiva IPv4-osoite tai '*' (tähti) kaikille yhteyksille.",
"BindAddressHelpTextWarning": "Käyttöönotto vaatii uudelleenkäynnistyksen.",
"BookIsDownloading": "Kirjaa ladataan",
"BookIsDownloadingInterp": "Kirjaa ladataan - {0} % {1}",
@@ -47,7 +47,7 @@
"Cancel": "Peruuta",
"CancelMessageText": "Haluatko varmasti perua tämän odottavan tehtävän?",
"CertificateValidation": "Varmenteen vahvistus",
"CertificateValidationHelpText": "Valitse HTTPS-varmenteen vahvistuksen tarkkuus. Älä muuta, jollet ymmärrä tähän liittyviä riskejä.",
"CertificateValidationHelpText": "Muuta HTTPS-varmennevahvistuksen tarkkuutta. Älä muuta, jollet ymmärrä tähän liittyviä riskejä.",
"ChangeFileDate": "Muuta tiedoston päiväys",
"ChangeHasNotBeenSavedYet": "Muutosta ei ole vielä tallennettu",
"ChmodFolder": "chmod-kansio",
@@ -141,7 +141,7 @@
"Fixed": "Korjattu",
"Folder": "Kansio",
"Folders": "Kansioiden käsittely",
"ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Lue lisää yksittäisistä lataustyökaluista painamalla 'Lisätietoja'.",
"ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Lataustyökalukohtaisia tietoja saat painamalla lisätietopainikkeita.",
"ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Lue lisää tietolähteestä painamalla 'Lisätietoja'.",
"ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Lue lisää tuontilistoista painamalla 'Lisätietoja'.",
"GeneralSettings": "Yleiset asetukset",
@@ -271,7 +271,7 @@
"ReadarrTags": "Radarr-tunnisteet",
"Real": "Todellinen",
"Reason": "Syy",
"RecycleBinCleanupDaysHelpText": "Älä tyhjennä automaattisesti asettamalla arvoksi '0'.",
"RecycleBinCleanupDaysHelpText": "Poista automaattinen tyhjennys käytöstä asettamalla arvoksi '0'.",
"RecycleBinCleanupDaysHelpTextWarning": "Tässä määritettyä aikaa vanhemmat tiedostot poistetaan roskakorista pysyvästi automaattisesti.",
"RecycleBinHelpText": "Pysyvän poiston sijaan kirjatiedostot siirretään tähän kansioon.",
"RecyclingBin": "Roskakori",
@@ -297,7 +297,7 @@
"RemoveTagExistingTag": "Olemassa oleva tunniste",
"RemoveTagRemovingTag": "Tunniste poistetaan",
"RemovedFromTaskQueue": "Poistettu tehtäväjonosta",
"RenameBooksHelpText": "Jos uudelleennimeäminen ei ole käytössä, käytetään olemassa olevaa tiedostonimeä.",
"RenameBooksHelpText": "Jos uudelleennimeäminen ei ole käytössä, käytetään nykyistä tiedostonimeä.",
"Reorder": "Järjestä uudelleen",
"ReplaceIllegalCharacters": "Korvaa kielletyt merkit",
"RequiredHelpText": "Julkaisun tulee sisältää ainakin yksi näistä termeistä (kirjainkokoa ei huomioida).",
@@ -349,7 +349,7 @@
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Näkyy jokaisen sarakkeen yläpuolella käytettäessä viikkonäkymää.",
"Size": " Koko",
"SkipFreeSpaceCheck": "Ohita vapaan levytilan tarkistus",
"SkipFreeSpaceCheckWhenImportingHelpText": "Käytä, kun vapaata tilaa ei tunnisteta kirjailijoidesi pääkansiosta.",
"SkipFreeSpaceCheckWhenImportingHelpText": "Käytä, kun vapaata tallennustilaa ei tunnisteta kirjailijoiden juurikansiosta.",
"SorryThatAuthorCannotBeFound": "Valitettavasti kirjailijaa ei löydy.",
"SorryThatBookCannotBeFound": "Valitettavasti elokuvaa ei löydy.",
"Source": "Lähdekoodi",
@@ -367,7 +367,7 @@
"SuccessMyWorkIsDoneNoFilesToRetag": "Menestys! Työni on valmis, ei nimettäviä tiedostoja.",
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS-syötettä ei ole käytettävissä tälle tietolähteelle",
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Hakemistoa ei tueta tällä hakemistolla",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Käytetään, kun automaattiset haut suoritetaan käyttöliittymän tai Radarrin kautta",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Profiilia käytetään automaattihaun yhteydessä, kun haku suoritetaan käyttöliittymästä tai Readarrin toimesta.",
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Profiilia käytetään vuorovaikutteisen haun yhteydessä.",
"TagIsNotUsedAndCanBeDeleted": "Tunnistetta ei ole määritetty millekään kohteelle, joten sen voi poistaa.",
"Tags": "Tunnisteet",
@@ -588,10 +588,10 @@
"MissingBooks": "Puuttuvat kirjat",
"MissingBooksAuthorMonitored": "Puuttuvat kirjat (kirjailijaa valvotaan)",
"MissingBooksAuthorNotMonitored": "Puuttuvat kirjat (kirjailijaa ei valvota)",
"MonitorBookExistingOnlyWarning": "Tämä on kirjakohtaisen valvonnan kertaluontoinen määritys. Käytä Kirjailija/Muokkaa-valintaa hallinnoidaksesi mitä uusille kirjalisäyksille tehdään.",
"MonitorBookExistingOnlyWarning": "Tämä on kirjakohtaisen valvonnan kertaluontoinen määritys. Määritä Kirjailija/Muokkaa-valinnalla mitä uusille kirjalisäyksille tehdään.",
"MonitorNewItems": "Valvo uusia kirjoja",
"MonitorNewItemsHelpText": "Uusien kirjojen valvontatapa.",
"MonitoringOptionsHelpText": "Kansiosta löydetyille kirjailijoille oletusarvoisesti asetettava kirjojen valvontataso (kertaluontoinen määritys).",
"MonitoringOptionsHelpText": "Mitkä kirjat asetetaan valvottaviksi kirjailijan lisäyksen yhteydessä (kertaluontoinen määritys).",
"OutputFormatHelpText": "Voit halutessasi pyytää Calibrea muuntamaan kirjat eri muotoihin tuonnin yhteydessä. Pilkulla eroteltu lista.",
"PasswordHelpText": "Calibre-sisältöpalvelimen salasana.",
"PortHelpText": "Calibre-sisältöpalvelimen portti.",
@@ -700,5 +700,7 @@
"RenameFiles": "Uudelleennimeä tiedostot",
"Test": "Kokeile",
"AllowFingerprintingHelpText": "Käytä piiloleimausta kirjojen täsmäyksen tarkennukseen",
"AllowFingerprinting": "Salli piiloleimaus"
"AllowFingerprinting": "Salli piiloleimaus",
"Database": "Tietokanta",
"ManualImportSelectEdition": "Manuaalinen tuonti - Valitse versio"
}

View File

@@ -666,5 +666,15 @@
"DefaultMonitorOptionHelpText": "Quels livres doivent être surveillés lors de l'ajout initial pour les auteurs détectés dans ce dossier",
"DefaultQualityProfileIdHelpText": "Profil de qualité par défaut pour les auteurs détectés dans ce dossier",
"AddMissing": "Ajouter les manquants",
"BookFileCountBookCountTotalTotalBookCountInterp": "{0} / {1} (Total : {2})"
"BookFileCountBookCountTotalTotalBookCountInterp": "{0} / {1} (Total : {2})",
"InstanceName": "Nom de l'instance",
"InstanceNameHelpText": "Nom de l'instance dans l'onglet du navigateur et pour le nom d'application dans Syslog",
"UseCalibreContentServer": "Serveur de contenu Calibre",
"DefaultTagsHelpText": "Profil de métadonnées par défaut pour les auteurs détectés dans ce dossier",
"MetadataProfiles": "profil de métadonnées",
"ManualImportSelectEdition": "Importation manuelle - Sélectionnez un film",
"Term": "Terme",
"MetadataProfile": "profil de métadonnées",
"StatusEndedContinuing": "Continuant",
"Database": "Base de données"
}

View File

@@ -568,5 +568,7 @@
"RenameFiles": "שנה את שם הקבצים",
"Test": "מִבְחָן",
"InstanceName": "שם מופע",
"RemotePathMappingCheckWrongOSPath": "אתה משתמש בדוקר; קליינט ההורדות {0} שם הורדות ב-{1} אבל הנתיב לא תקין {2}. בחן מחדש את ניתוב התיקיות והגדרות קליינט ההורדות."
"RemotePathMappingCheckWrongOSPath": "אתה משתמש בדוקר; קליינט ההורדות {0} שם הורדות ב-{1} אבל הנתיב לא תקין {2}. בחן מחדש את ניתוב התיקיות והגדרות קליינט ההורדות.",
"ManualImportSelectEdition": "ייבוא ידני - בחר סרט",
"Database": "מסד נתונים"
}

View File

@@ -556,5 +556,6 @@
"RenameFiles": "फ़ाइलों का नाम बदलें",
"RestartRequiredHelpTextWarning": "प्रभावी करने के लिए पुनरारंभ की आवश्यकता है",
"AddList": "सूची में जोड़ने",
"Test": "परीक्षा"
"Test": "परीक्षा",
"ManualImportSelectEdition": "मैनुअल आयात - मूवी का चयन करें"
}

View File

@@ -556,5 +556,6 @@
"RestartRequiredHelpTextWarning": "Krefst endurræsingar til að taka gildi",
"AddList": "Bæta við lista",
"RenameFiles": "Endurnefna skrár",
"Test": "Próf"
"Test": "Próf",
"ManualImportSelectEdition": "Handvirkur innflutningur - Veldu kvikmynd"
}

View File

@@ -619,5 +619,7 @@
"InstanceName": "Nome Istanza",
"InstanceNameHelpText": "Nome dell'istanza nella scheda e per il nome dell'applicazione Syslog",
"LogRotateHelpText": "Numero massimo di file di log da tenere salvati nella cartella log",
"LogRotation": "Rotazione Log"
"LogRotation": "Rotazione Log",
"ManualImportSelectEdition": "Importazione manuale: seleziona Film",
"Database": "Database"
}

View File

@@ -556,5 +556,6 @@
"RestartRequiredHelpTextWarning": "有効にするには再起動が必要です",
"RenameFiles": "ファイルの名前を変更する",
"Test": "テスト",
"AddList": "リストを追加"
"AddList": "リストを追加",
"ManualImportSelectEdition": "手動インポート-ムービーを選択"
}

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