1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-15 15:54:47 -04:00

Compare commits

...

54 Commits

Author SHA1 Message Date
PearsonFlyer
eb764832ed Fix notifiation in Ntfy on test from Radarr to Sonarr
(cherry picked from commit 1b599c7e765b10ce043cbf6b127143cc737b378a)
2023-01-20 00:24:26 +00:00
Qstick
685a24e476 Fixed: RemotePathMappingCheck Improvements 2023-01-16 22:45:55 -06:00
Qstick
cae4faae61 Fixed: DownloadClientRootFolderCheck Improvements 2023-01-16 22:38:05 -06:00
Qstick
5dac6badf2 Fixed: Ignore movie add errors during collection sync
Fixes #7982
2023-01-11 23:34:21 -06:00
Weblate
5948f56482 Translated using Weblate (Ukrainian) [skip ci]
Currently translated at 100.0% (1156 of 1156 strings)

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

Currently translated at 100.0% (1156 of 1156 strings)

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

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 95.5% (1105 of 1156 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 97.5% (1128 of 1156 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: aenron <1414004038@qq.com>
Co-authored-by: andrey4korop <andrey999@i.ua>
Co-authored-by: jjTogo228 <juniorbiam@gmail.com>
Co-authored-by: verhese <sean.verheyen1@telenet.be>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-01-08 22:28:28 -06:00
Mark McDowall
98ddd0386b Fixed: Trakt connection auth tokens not being refreshed
Closes #7873

(cherry picked from commit d09e5d8eb4097cbba1ee0a668dbb27f941cc4f68)
2023-01-08 22:22:01 -06:00
Mark McDowall
2947b244e4 Fixed: Quality cutoff updating in UI when adding/removing qualities
Closes #7879

(cherry picked from commit fea66cb7bccc7e94523614db38b157fa38f55ea5)
2023-01-08 21:43:55 -06:00
Mark McDowall
72552b8084 New: Option to include movie image for Gotify notifications
Closes #7920

(cherry picked from commit e57e68c97a9d24f8344623ac8f731c2da220686b)
2023-01-08 21:41:08 -06:00
Qstick
09642444d7 Switch Trakt to STJson
Fixes #7913
2023-01-08 21:11:59 -06:00
Qstick
d1080b825c Fixed: Truncate custom format card tags
Fixes #7725
Fixes #7973

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2023-01-08 21:00:43 -06:00
Qstick
001421de10 New: Improve messaging for rejected quality upgrades
Fixes #7461

Co-Authored-By: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2023-01-08 20:52:47 -06:00
Qstick
bab9b8b36a Add Volta node config
Fixes #7600
Fixes #7747

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2023-01-08 20:52:47 -06:00
Qstick
0fb738aa2e Fixed: Kodi Metadata Subtitle Language
Fixes #7962

Co-Authored-By: Stevie Robinson <stevie.robinson@gmail.com>
2023-01-08 20:52:47 -06:00
Mark McDowall
4963920a46 New: Added health check warning if SABnzbd sorting is enabled
(cherry picked from commit 61fa1e5e3f00072f0d5f59cc883fac74fe12ee9d)
2023-01-08 20:38:26 -06:00
Qstick
f0d10fe1cd Fixed: Correct messaging when release is not upgrade
Fixes #7963
2023-01-08 20:24:13 -06:00
James Hu
386b33b624 New: Include movie title and year when logging report
* Include movie title and year when logging report

* Change verbage

Co-authored-by: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com>
2023-01-05 21:07:59 +00:00
Mark McDowall
98201508f2 New: Description for indexer RSS setting
(cherry picked from commit 396406b2174c4876057175e7537a4718eee2abca)
2023-01-04 10:19:57 +00:00
Qstick
9723c569a1 Bump version to 4.4.0 2023-01-03 18:48:07 -06:00
Qstick
0584d7676c Bump FFProbe and Newtonsoft 2023-01-02 22:02:50 -06:00
Weblate
09c42530ec Translated using Weblate (Dutch) [skip ci]
Currently translated at 95.4% (1102 of 1155 strings)

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

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 94.3% (1090 of 1155 strings)

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

Currently translated at 99.9% (1154 of 1155 strings)

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 94.4% (1091 of 1155 strings)

Co-authored-by: Davide Palma <github@davidepalma.it>
Co-authored-by: Iagocds <cdsiago@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: andrey4korop <andrey999@i.ua>
Co-authored-by: lhquark <lhquark@gmail.com>
Co-authored-by: vyruz1986 <alex.goris@fastlikehell.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-01-02 21:47:27 -06:00
Mark McDowall
0697d694e0 New: Improved messaging when qBittorrent fails due to host header rejection
(cherry picked from commit 48b4cc5f3ffa0cb8eea6748db9091267216cef4f)
2023-01-02 21:46:10 -06:00
Mark McDowall
e085f6af8a Fixed: Multiple pushed releases will be processed sequentially
(cherry picked from commit 1f8e1cf582f59fe1e8dcc0fad15afeed6d9cd9d1)
2023-01-02 17:59:43 -06:00
Colin Gagnaire
7feda1c446 New: Add support for native Freebox Download Client
(cherry picked from commit fb76c237bfbb8aa43bcdd9ce34d90ea843011cee)
2022-12-27 21:20:33 +00:00
Winter
e1f83c205d Bump MonoTorrent to 2.0.7 2022-12-24 14:48:08 -06:00
Weblate
db00edd266 Translated using Weblate (Bengali) [skip ci]
Currently translated at 0.4% (5 of 1155 strings)

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

Currently translated at 99.3% (1147 of 1155 strings)

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

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 95.4% (1102 of 1155 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 94.3% (1090 of 1155 strings)

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

Currently translated at 99.3% (1148 of 1155 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 93.9% (1085 of 1155 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 93.9% (1085 of 1155 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Frank van den Bosch <frank@fbtech.nl>
Co-authored-by: Freelf <freelf.me@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mipiaceanutella <remix-polity-0l@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: benniblot <ben2004engler@gmail.com>
Co-authored-by: deepserket <deepserket@gmail.com>
Co-authored-by: hidaba <nag@hidaba.com>
Co-authored-by: ningxia <xianing7105@163.com>
Co-authored-by: saambd <me@salimrahman.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bn/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2022-12-22 20:34:42 -06:00
Mark McDowall
d699f61f5d Fixed: Prevent unexpected data breaking Series Import
(cherry picked from commit b8714d80a1ede761042ab469110edf552a74ac6b)
2022-12-22 20:33:30 -06:00
Mark McDowall
dc1b478f2c Fixed: Only log /proc/mounts exception once per process
(cherry picked from commit ce0388ca99b7f89bd9e8971777a7995c4361d268)
2022-12-22 20:32:43 -06:00
erri120
0ca665c903 New: Parse Open Matte as Edition
To make James Cameron happy.
2022-12-18 10:36:50 -06:00
Mark McDowall
111c6a743f New: Rename Emby to Emby / Jellyfin
(cherry picked from commit ee1ee8f267079e18015829065a76a628929cf4b2)
2022-12-17 18:18:52 +00:00
Qstick
d3517532a4 Update README for DigitalOcean attribution
[common]
2022-12-17 11:31:47 -06:00
Qstick
5790ebc558 Bump version to 4.3.2 2022-12-11 19:03:47 -06:00
Mark McDowall
c11f72c098 New: IPv6 support for connections/indexers/download clients
Closes #7850

(cherry picked from commit 1b90fbcf7df2c1086da4791c6491771924b1b7aa)
2022-12-10 12:05:55 -06:00
Mark McDowall
3617bef54b Fixed: Improve Bind Address validation and help text
Closes #7849

(cherry picked from commit 6bdeafcf8c78e145595f52e885356be1210abe91)
2022-12-10 12:04:04 -06:00
Zak Saunders
a5fb01f1e6 New: Auto theme option to match OS theme
Co-authored-by: Qstick <qstick@gmail.com>
(cherry picked from commit 4ca5a213fa0fc29ed93e7e31b080728d6fa7f1f3)
2022-12-09 22:11:03 -06:00
Qstick
fa6acb7497 Simplify X-Forwarded-For handling
This happens in asp.net middleware now

Co-Authored-By: ta264 <ta264@users.noreply.github.com>

(cherry picked from commit 16e2d130e6a2e7239bcfe92187a7f990f93eff00)
2022-12-09 22:05:50 -06:00
Qstick
904259df92 New: Improve IPAddress.IsLocal method
Co-Authored-By: ta264 <ta264@users.noreply.github.com>

(cherry picked from commit fd98a179ab6fed8037c99344b34593aac24a0ac0)
2022-12-09 22:05:32 -06:00
Qstick
65c316bd6d Fixed: Smb paths fail on Kodi update
Fixes #7854
2022-12-09 22:04:50 -06:00
Qstick
3b46a08606 Fix PendingRelease Tests 2022-12-05 22:12:34 -06:00
Servarr
6ad49373d4 Automated API Docs update 2022-12-05 21:52:13 -06:00
Qstick
2a1f57c085 Fixed: Sending Webhook on upgrade if media info is unavailable
Fixes #7838

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-12-05 21:34:00 -06:00
Qstick
9d9065fbcd API Updates
Fixes #7833
Fixes #6785
Fixes #6787

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-12-05 21:34:00 -06:00
Qstick
694940452c Fixed: Loading queue when there are pending items that were added before upgrading
Fixes #7823

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-12-05 21:34:00 -06:00
Qstick
f5d6a79998 Fixed: Grab/remove queue actions not showing spinner
Fixes #7821
2022-12-05 21:34:00 -06:00
Qstick
4cc98a10a0 Fixed: Use route Id for PUT requests if not passed in body
Fixes #7809
2022-12-05 21:34:00 -06:00
Qstick
1751bd1a58 Fixed: Correct Attribute compare for Id validation
(cherry picked from commit 7e48ea0231272ae56c30f5f43339f0dca7a27fb3)
2022-12-05 21:20:31 -06:00
Weblate
c0caf65b69 Translated using Weblate (Dutch) [skip ci]
Currently translated at 95.2% (1100 of 1155 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

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

Currently translated at 100.0% (1155 of 1155 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Robin Flikkema <robin@robinflikkema.nl>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-12-02 21:13:27 -06:00
Davo1624
cd889872de Properly parse H.265 4k as 4k quality (#7812)
* Update en.json

* Properly parse H265 for 4k quality

`.WEB-DL.4K.H265.AAC` parses properly for 4k quality
`.WEB-DL.4K.H.265.AAC` parses improperly as 480p
2022-11-29 22:00:36 -06:00
Servarr
6366e335bc Automated API Docs update 2022-11-29 21:57:01 -06:00
Qstick
41f10d098e Don't block task queue for queued update task when there are longer running tasks
Fixes #7538

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-29 21:44:03 -06:00
Qstick
b2c1698097 Fixed: False Positive HC in some cases
Fixes #7785
2022-11-29 20:52:23 -06:00
Mark McDowall
ed20487f30 Fixed: Handle Flood reporting errors for completed and stopped downloads
(cherry picked from commit f2b2eb69a3e8b271535bd92dc2f5cbfd45664daf)
2022-11-28 21:25:34 -06:00
Bruno Garcia
d1235adfc4 Sentry SDK v3.23.1
Co-authored-by: Bruno Garcia <bruno@Brunos-MacBook-Pro.local>
(cherry picked from commit de3cb07c57d762084c983336aa01b761a8e4b74a)
2022-11-28 21:30:06 +00:00
Qstick
561993e30c Fixed: Parse multiple languages for two letter cases
Fixes #7783
2022-11-25 19:16:29 -06:00
Weblate
14f8f89634 Translated using Weblate (Czech) [skip ci]
Currently translated at 90.9% (1051 of 1155 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

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

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
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/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-11-24 18:40:38 -06:00
112 changed files with 3840 additions and 643 deletions

View File

@@ -76,6 +76,15 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrai
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
## DigitalOcean
This project is also supported by DigitalOcean
<p>
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
</a>
</p>
### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.3.1'
majorVersion: '4.4.0'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -75,13 +75,23 @@ class Queue extends Component {
return;
}
const nextState = {};
if (prevProps.items !== items) {
nextState.items = items;
}
const selectedIds = this.getSelectedIds();
const isPendingSelected = _.some(this.props.items, (item) => {
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
});
if (isPendingSelected !== this.state.isPendingSelected) {
this.setState({ isPendingSelected });
nextState.isPendingSelected = isPendingSelected;
}
if (!_.isEmpty(nextState)) {
this.setState(nextState);
}
}
@@ -214,26 +224,29 @@ class Queue extends Component {
<PageContentBody>
{
isRefreshing && !isAllPopulated &&
<LoadingIndicator />
isRefreshing && !isAllPopulated ?
<LoadingIndicator /> :
null
}
{
!isRefreshing && hasError &&
!isRefreshing && hasError ?
<div>
{translate('FailedToLoadQueue')}
</div>
</div> :
null
}
{
isAllPopulated && !hasError && !items.length &&
isAllPopulated && !hasError && !items.length ?
<div>
{translate('QueueIsEmpty')}
</div>
</div> :
null
}
{
isAllPopulated && !hasError && !!items.length &&
isAllPopulated && !hasError && !!items.length ?
<div>
<Table
columns={columns}
@@ -268,7 +281,8 @@ class Queue extends Component {
isFetching={isRefreshing}
{...otherProps}
/>
</div>
</div> :
null
}
</PageContentBody>

View File

@@ -225,13 +225,19 @@ class ImportMovieFooter extends Component {
body={
<ul>
{
importError.responseJSON.map((error, index) => {
return (
<li key={index}>
{error.errorMessage}
</li>
);
})
Array.isArray(importError.responseJSON) ?
importError.responseJSON.map((error, index) => {
return (
<li key={index}>
{error.errorMessage}
</li>
);
}) :
<li>
{
JSON.stringify(importError.responseJSON)
}
</li>
}
</ul>
}

View File

@@ -152,13 +152,19 @@ class ImportMovieSelectFolder extends Component {
<ul>
{
saveError.responseJSON.map((e, index) => {
return (
<li key={index}>
{e.errorMessage}
</li>
);
})
Array.isArray(saveError.responseJSON) ?
saveError.responseJSON.map((e, index) => {
return (
<li key={index}>
{e.errorMessage}
</li>
);
}) :
<li>
{
JSON.stringify(saveError.responseJSON)
}
</li>
}
</ul>
</Alert> :

View File

@@ -113,10 +113,12 @@ class EnhancedSelectInput extends Component {
this._scheduleUpdate();
}
if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) {
this.setState({
selectedIndex: getSelectedIndex(this.props)
});
if (!Array.isArray(this.props.value)) {
if (prevProps.value !== this.props.value || prevProps.values !== this.props.values) {
this.setState({
selectedIndex: getSelectedIndex(this.props)
});
}
}
}
@@ -332,6 +334,11 @@ class EnhancedSelectInput extends Component {
const isMultiSelect = Array.isArray(value);
const selectedOption = getSelectedOption(selectedIndex, values);
let selectedValue = value;
if (!values.length) {
selectedValue = isMultiSelect ? [] : '';
}
return (
<div>
@@ -372,15 +379,17 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress}
>
{
isFetching &&
isFetching ?
<LoadingIndicator
className={styles.loading}
size={20}
/>
/> :
null
}
{
!isFetching &&
isFetching ?
null :
<Icon
name={icons.CARET_DOWN}
/>
@@ -400,7 +409,7 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress}
>
<SelectedValueComponent
value={value}
value={selectedValue}
values={values}
{...selectedValueOptions}
{...selectedOption}
@@ -418,15 +427,17 @@ class EnhancedSelectInput extends Component {
>
{
isFetching &&
isFetching ?
<LoadingIndicator
className={styles.loading}
size={20}
/>
/> :
null
}
{
!isFetching &&
isFetching ?
null :
<Icon
name={icons.CARET_DOWN}
/>
@@ -506,7 +517,7 @@ class EnhancedSelectInput extends Component {
</Manager>
{
isMobile &&
isMobile ?
<Modal
className={styles.optionsModal}
size={sizes.EXTRA_SMALL}
@@ -557,7 +568,8 @@ class EnhancedSelectInput extends Component {
}
</Scroller>
</ModalBody>
</Modal>
</Modal> :
null
}
</div>
);

View File

@@ -24,7 +24,7 @@ function HintedSelectInputSelectedValue(props) {
>
<div className={styles.valueText}>
{
isMultiSelect &&
isMultiSelect ?
value.map((key, index) => {
const v = valuesMap[key];
return (
@@ -32,26 +32,28 @@ function HintedSelectInputSelectedValue(props) {
{v ? v.value : key}
</Label>
);
})
}) :
null
}
{
!isMultiSelect && value
isMultiSelect ? null : value
}
</div>
{
hint != null && includeHint &&
hint != null && includeHint ?
<div className={styles.hintText}>
{hint}
</div>
</div> :
null
}
</EnhancedSelectInputSelectedValue>
);
}
HintedSelectInputSelectedValue.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
hint: PropTypes.string,
isMultiSelect: PropTypes.bool.isRequired,

View File

@@ -68,7 +68,7 @@ RootFolderSelectInputOption.propTypes = {
value: PropTypes.string.isRequired,
freeSpace: PropTypes.number,
movieFolder: PropTypes.string,
isMissing: PropTypes.boolean,
isMissing: PropTypes.bool,
isMobile: PropTypes.bool.isRequired,
isWindows: PropTypes.bool
};

View File

@@ -17,6 +17,10 @@
font-size: 24px;
}
.buttons {
flex: 0 0 auto;
}
.cloneButton {
composes: button from '~Components/Link/IconButton.css';
@@ -36,3 +40,10 @@
margin: 0;
border: none;
}
.label {
@add-mixin truncate;
composes: label from '~Components/Label.css';
max-width: 100%;
}

View File

@@ -90,7 +90,7 @@ class CustomFormat extends Component {
{name}
</div>
<div>
<div className={styles.buttons}>
<IconButton
className={styles.cloneButton}
title={translate('CloneCustomFormat')}
@@ -124,6 +124,7 @@ class CustomFormat extends Component {
return (
<Label
className={styles.label}
key={index}
kind={kind}
>

View File

@@ -89,6 +89,7 @@ function EditIndexerModalContent(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="enableRss"
helpText={supportsRss.value ? translate('RSSHelpText') : undefined}
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
isDisabled={!supportsRss.value}
{...enableRss}

View File

@@ -1,7 +1,11 @@
import * as dark from './dark';
import * as light from './light';
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const auto = defaultDark ? { ...dark } : { ...light };
export default {
auto,
light,
dark
};

View File

@@ -134,5 +134,9 @@
"webpack-cli": "4.9.1",
"webpack-livereload-plugin": "3.0.2",
"worker-loader": "3.0.8"
},
"volta": {
"node": "16.17.0",
"yarn": "1.22.19"
}
}

View File

@@ -0,0 +1,25 @@
using System.Globalization;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
{
[TestFixture]
public class IsValidIPAddressFixture
{
[TestCase("192.168.0.1")]
[TestCase("::1")]
[TestCase("2001:db8:4006:812::200e")]
public void should_validate_ip_address(string input)
{
input.IsValidIpAddress().Should().BeTrue();
}
[TestCase("sonarr.tv")]
public void should_not_parse_non_ip_address(string input)
{
input.IsValidIpAddress().Should().BeFalse();
}
}
}

View File

@@ -1,4 +1,4 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Test.Common;
@@ -10,6 +10,7 @@ namespace NzbDrone.Common.Test.Http
[TestCase("abc://my_host.com:8080/root/api/")]
[TestCase("abc://my_host.com:8080//root/api/")]
[TestCase("abc://my_host.com:8080/root//api/")]
[TestCase("abc://[::1]:8080/root//api/")]
public void should_parse(string uri)
{
var newUri = new HttpUri(uri);

View File

@@ -7,34 +7,50 @@ namespace NzbDrone.Common.Extensions
{
public static bool IsLocalAddress(this IPAddress ipAddress)
{
if (ipAddress.IsIPv6LinkLocal)
// Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
if (ipAddress.IsIPv4MappedToIPv6)
{
return true;
ipAddress = ipAddress.MapToIPv4();
}
// Checks loopback ranges for both IPv4 and IPv6.
if (IPAddress.IsLoopback(ipAddress))
{
return true;
}
// IPv4
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
byte[] bytes = ipAddress.GetAddressBytes();
switch (bytes[0])
{
case 10:
case 127:
return true;
case 172:
return bytes[1] < 32 && bytes[1] >= 16;
case 192:
return bytes[1] == 168;
default:
return false;
}
return IsLocalIPv4(ipAddress.GetAddressBytes());
}
// IPv6
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
{
return ipAddress.IsIPv6LinkLocal ||
ipAddress.IsIPv6UniqueLocal ||
ipAddress.IsIPv6SiteLocal;
}
return false;
}
private static bool IsLocalIPv4(byte[] ipv4Bytes)
{
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
// Class A private range: 10.0.0.0 10.255.255.255 (10.0.0.0/8)
bool IsClassA() => ipv4Bytes[0] == 10;
// Class B private range: 172.16.0.0 172.31.255.255 (172.16.0.0/12)
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
// Class C private range: 192.168.0.0 192.168.255.255 (192.168.0.0/16)
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
}
}
}

View File

@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
@@ -192,5 +194,30 @@ namespace NzbDrone.Common.Extensions
.Replace("'", "%27")
.Replace("%7E", "~");
}
public static bool IsValidIpAddress(this string value)
{
if (!IPAddress.TryParse(value, out var parsedAddress))
{
return false;
}
if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
{
return false;
}
if (parsedAddress.IsIPv6Multicast)
{
return false;
}
return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6;
}
public static string ToUrlHost(this string input)
{
return input.Contains(":") ? $"[{input}]" : input;
}
}
}

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
{
public class HttpUri : IEquatable<HttpUri>
{
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly string _uri;
public string FullUri => _uri;
@@ -70,6 +70,8 @@ namespace NzbDrone.Common.Http
private void Parse()
{
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
var match = RegexUri.Match(_uri);
var scheme = match.Groups["scheme"];
@@ -79,7 +81,7 @@ namespace NzbDrone.Common.Http
var query = match.Groups["query"];
var fragment = match.Groups["fragment"];
if (!match.Success || (scheme.Success && !host.Success && path.Success))
if (!parseSuccess || (scheme.Success && !host.Success && path.Success))
{
throw new ArgumentException("Uri didn't match expected pattern: " + _uri);
}

View File

@@ -11,26 +11,41 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{
try
{
sentryEvent.Message.Message = CleanseLogMessage.Cleanse(sentryEvent.Message.Message);
if (sentryEvent.Message is not null)
{
sentryEvent.Message.Formatted = CleanseLogMessage.Cleanse(sentryEvent.Message.Formatted);
sentryEvent.Message.Message = CleanseLogMessage.Cleanse(sentryEvent.Message.Message);
sentryEvent.Message.Params = sentryEvent.Message.Params?.Select(x => CleanseLogMessage.Cleanse(x switch
{
string str => str,
_ => x.ToString()
})).ToList();
}
if (sentryEvent.Fingerprint != null)
if (sentryEvent.Fingerprint.Any())
{
var fingerprint = sentryEvent.Fingerprint.Select(x => CleanseLogMessage.Cleanse(x)).ToList();
sentryEvent.SetFingerprint(fingerprint);
}
if (sentryEvent.Extra != null)
if (sentryEvent.Extra.Any())
{
var extras = sentryEvent.Extra.ToDictionary(x => x.Key, y => (object)CleanseLogMessage.Cleanse((string)y.Value));
var extras = sentryEvent.Extra.ToDictionary(x => x.Key, y => (object)CleanseLogMessage.Cleanse(y.Value as string));
sentryEvent.SetExtras(extras);
}
foreach (var exception in sentryEvent.SentryExceptions)
if (sentryEvent.SentryExceptions is not null)
{
exception.Value = CleanseLogMessage.Cleanse(exception.Value);
foreach (var frame in exception.Stacktrace.Frames)
foreach (var exception in sentryEvent.SentryExceptions)
{
frame.FileName = ShortenPath(frame.FileName);
exception.Value = CleanseLogMessage.Cleanse(exception.Value);
if (exception.Stacktrace is not null)
{
foreach (var frame in exception.Stacktrace.Frames)
{
frame.FileName = ShortenPath(frame.FileName);
}
}
}
}
}

View File

@@ -99,9 +99,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
o.Dsn = dsn;
o.AttachStacktrace = true;
o.MaxBreadcrumbs = 200;
o.SendDefaultPii = false;
o.Debug = false;
o.DiagnosticLevel = SentryLevel.Debug;
o.Release = BuildInfo.Release;
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);

View File

@@ -7,10 +7,10 @@
<PackageReference Include="DryIoc.dll" Version="5.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Sentry" Version="3.20.1" />
<PackageReference Include="Sentry" Version="3.23.1" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.5" />

View File

@@ -0,0 +1,211 @@
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class UpgradeAllowedSpecificationFixture : CoreTest<UpgradableSpecification>
{
private CustomFormat _customFormatOne;
private CustomFormat _customFormatTwo;
private Profile _qualityProfile;
[SetUp]
public void Setup()
{
_customFormatOne = new CustomFormat
{
Id = 1,
Name = "One"
};
_customFormatTwo = new CustomFormat
{
Id = 2,
Name = "Two"
};
_qualityProfile = new Profile
{
Cutoff = Quality.Bluray1080p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = false,
CutoffFormatScore = 100,
FormatItems = new List<ProfileFormatItem>
{
new ProfileFormatItem
{
Id = 1,
Format = _customFormatOne,
Score = 50
},
new ProfileFormatItem
{
Id = 1,
Format = _customFormatTwo,
Score = 100
}
}
};
}
[Test]
public void should_return_false_when_quality_is_better_custom_formats_are_the_same_and_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.Bluray1080p),
new List<CustomFormat>())
.Should().BeFalse();
}
[Test]
public void should_return_false_when_quality_is_same_and_custom_format_is_upgrade_and_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatTwo })
.Should().BeFalse();
}
[Test]
public void should_return_true_for_custom_format_upgrade_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatTwo })
.Should().BeTrue();
}
[Test]
public void should_return_true_for_same_custom_format_score_when_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne })
.Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_custom_format_score_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatTwo },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne })
.Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_language_when_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatTwo },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne })
.Should().BeTrue();
}
[Test]
public void should_return_true_for_quality_upgrade_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.Bluray1080p),
new List<CustomFormat>())
.Should().BeTrue();
}
[Test]
public void should_return_true_for_same_quality_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.DVD),
new List<CustomFormat>())
.Should().BeTrue();
}
[Test]
public void should_return_true_for_same_quality_when_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.DVD),
new List<CustomFormat>())
.Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_quality_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.SDTV),
new List<CustomFormat>())
.Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_quality_when_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.SDTV),
new List<CustomFormat>())
.Should().BeTrue();
}
}
}

View File

@@ -0,0 +1,367 @@
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.Movies;
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 RemoteMovie CreateRemoteMovie()
{
var movie = base.CreateRemoteMovie();
movie.Release.DownloadUrl = _downloadURL;
return movie;
}
[Test]
public void Download_with_DestinationDirectory_should_force_directory()
{
GivenDestinationDirectory();
GivenSuccessfulDownload();
var remoteMovie = CreateRemoteMovie();
Subject.Download(remoteMovie);
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 remoteMovie = CreateRemoteMovie();
Subject.Download(remoteMovie);
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 remoteMovie = CreateRemoteMovie();
Subject.Download(remoteMovie);
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 remoteMovie = CreateRemoteMovie();
Subject.Download(remoteMovie);
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(22, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
[TestCase(22, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, false)]
[TestCase(22, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, true)]
[TestCase(22, (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 remoteMovie = CreateRemoteMovie();
remoteMovie.Movie.MovieMetadata.Value.PhysicalRelease = DateTime.UtcNow.AddDays(-ageDay);
Subject.Download(remoteMovie);
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 remoteMovie = CreateRemoteMovie();
remoteMovie.SeedConfiguration = new TorrentSeedConfiguration();
remoteMovie.SeedConfiguration.Ratio = providerSeedRatio;
Subject.Download(remoteMovie);
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

@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Download
_downloadClients = new List<IDownloadClient>();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(v => v.GetDownloadClients())
.Setup(v => v.GetDownloadClients(It.IsAny<bool>()))
.Returns(_downloadClients);
Mocker.GetMock<IProvideDownloadClient>()

View File

@@ -89,6 +89,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
.With(h => h.Title = title)
.With(h => h.Release = release)
.With(h => h.Reason = reason)
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
.Build();
_heldReleases.AddRange(heldReleases);

View File

@@ -52,6 +52,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
_pending.Add(new PendingRelease
{
Id = id,
Title = "Movie.Title.2020.720p-Radarr",
ParsedMovieInfo = new ParsedMovieInfo { MovieTitles = new List<string> { title }, Year = year },
MovieId = _movie.Id
});

View File

@@ -93,6 +93,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
.With(h => h.MovieId = _movie.Id)
.With(h => h.Title = title)
.With(h => h.Release = release)
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
.Build();
Mocker.GetMock<IPendingReleaseRepository>()

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_warning_when_download_client_has_not_been_configured()
{
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(Array.Empty<IDownloadClient>());
Subject.Check().ShouldBeWarning();
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Throws<Exception>();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeError();
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(new List<DownloadClientItem>());
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeOk();

View File

@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(_clientStatus);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { _downloadClient.Object });
Mocker.GetMock<IDiskProvider>()

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class DownloadClientFolderCheckFixture : CoreTest<DownloadClientSortingCheck>
{
private DownloadClientInfo _clientStatus;
private Mock<IDownloadClient> _downloadClient;
private static Exception[] DownloadClientExceptions =
{
new DownloadClientUnavailableException("error"),
new DownloadClientAuthenticationException("error"),
new DownloadClientException("error")
};
[SetUp]
public void Setup()
{
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
_clientStatus = new DownloadClientInfo
{
IsLocalhost = true,
SortingMode = null
};
_downloadClient = Mocker.GetMock<IDownloadClient>();
_downloadClient.Setup(s => s.Definition)
.Returns(new DownloadClientDefinition { Name = "Test" });
_downloadClient.Setup(s => s.GetStatus())
.Returns(_clientStatus);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { _downloadClient.Object });
}
[Test]
public void should_return_ok_if_sorting_is_not_enabled()
{
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_warning_if_sorting_is_enabled()
{
_clientStatus.SortingMode = "TV";
Subject.Check().ShouldBeWarning();
}
[Test]
[TestCaseSource("DownloadClientExceptions")]
public void should_return_ok_if_client_throws_downloadclientexception(Exception ex)
{
_downloadClient.Setup(s => s.GetStatus())
.Throws(ex);
Subject.Check().ShouldBeOk();
ExceptionVerification.ExpectedErrors(0);
}
}
}

View File

@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(_clientStatus);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { _downloadClient.Object });
Mocker.GetMock<IConfigService>()

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using Moq;
using NUnit.Framework;
@@ -13,10 +14,10 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.NotificationTests
{
[TestFixture]
public class TraktServiceFixture : CoreTest<TraktService>
public class TraktServiceFixture : CoreTest<Trakt>
{
private DownloadMessage _downloadMessage;
private TraktSettings _traktSettings;
private NotificationDefinition _traktDefinition;
[SetUp]
public void Setup()
@@ -34,11 +35,17 @@ namespace NzbDrone.Core.Test.NotificationTests
}
};
_traktSettings = new TraktSettings
_traktDefinition = new NotificationDefinition
{
AccessToken = "",
RefreshToken = ""
Settings = new TraktSettings
{
AccessToken = "",
RefreshToken = "",
Expires = DateTime.Now.AddDays(1)
}
};
Subject.Definition = _traktDefinition;
}
private void GiventValidMediaInfo(Quality quality, string audioChannels, string audioFormat, string scanType)
@@ -56,7 +63,7 @@ namespace NzbDrone.Core.Test.NotificationTests
[Test]
public void should_add_collection_movie_if_null_mediainfo()
{
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
Subject.OnDownload(_downloadMessage);
Mocker.GetMock<ITraktProxy>()
.Verify(v => v.AddToCollection(It.IsAny<TraktCollectMoviesResource>(), It.IsAny<string>()), Times.Once());
@@ -67,7 +74,7 @@ namespace NzbDrone.Core.Test.NotificationTests
{
GiventValidMediaInfo(Quality.Bluray1080p, "5.1", "DTS", "Progressive");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
Subject.OnDownload(_downloadMessage);
Mocker.GetMock<ITraktProxy>()
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
@@ -83,7 +90,7 @@ namespace NzbDrone.Core.Test.NotificationTests
{
GiventValidMediaInfo(Quality.Bluray1080p, "2.0", "DTS", "Progressive");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
Subject.OnDownload(_downloadMessage);
Mocker.GetMock<ITraktProxy>()
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>

View File

@@ -49,6 +49,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.1990.Ultimate.Rekall.Edition.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA5.1-TWA", "Ultimate Rekall Edition")]
[TestCase("Movie.Title.1971.Signature.Edition.1080p.BluRay.FLAC.2.0.x264-TDD", "Signature Edition")]
[TestCase("Movie.1979.The.Imperial.Edition.BluRay.720p.DTS.x264-CtrlHD", "Imperial Edition")]
[TestCase("Movie.1997.Open.Matte.1080p.BluRay.x264.DTS-FGT", "Open Matte")]
public void should_parse_edition(string postTitle, string edition)
{
var parsed = Parser.Parser.ParseMovieTitle(postTitle);

View File

@@ -455,6 +455,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2016.1080p.KORSUBS.WEBRip.x264.AAC2.0-RADARR", "KORSUBS")]
[TestCase("Movie Title 2017 HC 720p HDRiP DD5 1 x264-LEGi0N", "Generic Hardcoded Subs")]
[TestCase("Movie.Title.2017.720p.SUBBED.HDRip.V2.XViD-26k.avi", "Generic Hardcoded Subs")]
[TestCase("Movie.Title.2000.1080p.BlueRay.x264.DTS.RoSubbed-playHD", null)]
[TestCase("Movie Title! 2018 [Web][MKV][h264][480p][AAC 2.0][Softsubs]", null)]
[TestCase("Movie Title! 2019 [HorribleSubs][Web][MKV][h264][848x480][AAC 2.0][Softsubs(HorribleSubs)]", null)]
public void should_parse_hardcoded_subs(string postTitle, string sub)

View File

@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Configuration
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string Theme => GetValue("Theme", "light", persist: false);
public string Theme => GetValue("Theme", "auto", persist: false);
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);

View File

@@ -144,7 +144,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return true;
}
return false;
if ((isQualityUpgrade || isCustomFormatUpgrade) && !qualityProfile.UpgradeAllowed)
{
_logger.Debug("Quality profile does not allow upgrades, skipping");
return false;
}
return true;
}
}
}

View File

@@ -132,22 +132,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,234 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Cache;
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.Download.Clients.QBittorrent;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
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,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ICacheManager cacheManager,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, 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(RemoteMovie remoteMovie, string hash, string magnetLink)
{
return _proxy.AddTaskFromUrl(magnetLink,
GetDownloadDirectory().EncodeBase64(),
ToBePaused(),
ToBeQueuedFirst(remoteMovie),
GetSeedRatio(remoteMovie),
Settings);
}
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
{
return _proxy.AddTaskFromFile(filename,
fileContent,
GetDownloadDirectory().EncodeBase64(),
ToBePaused(),
ToBeQueuedFirst(remoteMovie),
GetSeedRatio(remoteMovie),
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(RemoteMovie remoteMovie)
{
var isRecentMovie = remoteMovie.Movie.MovieMetadata.Value.IsRecentMovie;
if ((isRecentMovie && Settings.RecentPriority == (int)FreeboxDownloadPriority.First) ||
(!isRecentMovie && Settings.OlderPriority == (int)FreeboxDownloadPriority.First))
{
return true;
}
return false;
}
private double? GetSeedRatio(RemoteMovie remoteMovie)
{
if (remoteMovie.SeedConfiguration == null || remoteMovie.SeedConfiguration.Ratio == null)
{
return null;
}
return remoteMovie.SeedConfiguration.Ratio.Value * 100;
}
}
}

View File

@@ -45,6 +45,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
return true;
}
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new DownloadClientException("Failed to connect to qBittorrent. Check your settings and qBittorrent configuration.", new HttpException(response));
}
if (response.HasHttpError)
{
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", new HttpException(response));

View File

@@ -262,6 +262,19 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
if (category != null)
{
if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.MovieCategory))
{
status.SortingMode = "TV";
}
else if (config.Misc.enable_movie_sorting && ContainsCategory(config.Misc.movie_categories, Settings.MovieCategory))
{
status.SortingMode = "Movie";
}
else if (config.Misc.enable_date_sorting && ContainsCategory(config.Misc.date_categories, Settings.MovieCategory))
{
status.SortingMode = "Date";
}
status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Download
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download
}
public bool IsLocalhost { get; set; }
public string SortingMode { get; set; }
public List<OsPath> OutputRootFolders { get; set; }
}
}

View File

@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download
public interface IProvideDownloadClient
{
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol, int indexerId = 0);
IEnumerable<IDownloadClient> GetDownloadClients();
IEnumerable<IDownloadClient> GetDownloadClients(bool filterBlockedClients = false);
IDownloadClient Get(int id);
}
@@ -86,14 +86,39 @@ namespace NzbDrone.Core.Download
return provider;
}
public IEnumerable<IDownloadClient> GetDownloadClients()
public IEnumerable<IDownloadClient> GetDownloadClients(bool filterBlockedClients = false)
{
return _downloadClientFactory.GetAvailableProviders();
var enabledClients = _downloadClientFactory.GetAvailableProviders();
if (filterBlockedClients)
{
return FilterBlockedIndexers(enabledClients).ToList();
}
return enabledClients;
}
public IDownloadClient Get(int id)
{
return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id);
}
private IEnumerable<IDownloadClient> FilterBlockedIndexers(IEnumerable<IDownloadClient> clients)
{
var blockedClients = _downloadClientStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v);
foreach (var client in clients)
{
DownloadClientStatus blockedClientStatus;
if (blockedClients.TryGetValue(client.Definition.Id, out blockedClientStatus))
{
_logger.Debug("Temporarily ignoring client {0} till {1} due to recent failures.", client.Definition.Name, blockedClientStatus.DisabledTill.Value.ToLocalTime());
continue;
}
yield return client;
}
}
}
}

View File

@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Download
movieGrabbedEvent.DownloadId = downloadClientId;
}
_logger.ProgressInfo("Report sent to {0} from indexer {1}. {2}", downloadClient.Definition.Name, remoteMovie.Release.Indexer, downloadTitle);
_logger.ProgressInfo("Report for {0} ({1}) sent to {2} from indexer {3}. {4}", remoteMovie.Movie.Title, remoteMovie.Movie.Year, downloadClient.Definition.Name, remoteMovie.Release.Indexer, downloadTitle);
_eventAggregator.PublishEvent(movieGrabbedEvent);
}
}

View File

@@ -283,6 +283,12 @@ namespace NzbDrone.Core.Download.Pending
return null;
}
// Languages will be empty if added before upgrading to v4, reparsing the languages if they're empty will set it to Unknown or better.
if (release.ParsedMovieInfo.Languages.Empty())
{
release.ParsedMovieInfo.Languages = LanguageParser.ParseLanguages(release.Title);
}
release.RemoteMovie = new RemoteMovie
{
Movie = movie,

View File

@@ -5,5 +5,7 @@ namespace NzbDrone.Core.Download
public class ProcessMonitoredDownloadsCommand : Command
{
public override bool RequiresDiskAccess => true;
public override bool IsLongRunning => true;
}
}

View File

@@ -343,9 +343,12 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
if (movieFile.MediaInfo.Subtitles != null && movieFile.MediaInfo.Subtitles.Count > 0)
{
var subtitle = new XElement("subtitle");
subtitle.Add(new XElement("language", movieFile.MediaInfo.Subtitles));
streamDetails.Add(subtitle);
foreach (var s in movieFile.MediaInfo.Subtitles)
{
var subtitle = new XElement("subtitle");
subtitle.Add(new XElement("language", s));
streamDetails.Add(subtitle);
}
}
fileInfo.Add(streamDetails);

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Net.Http;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
@@ -37,7 +38,9 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var clients = _downloadClientProvider.GetDownloadClients();
// Only check clients not in failure status, those get another message
var clients = _downloadClientProvider.GetDownloadClients(true);
var rootFolders = _rootFolderService.All();
foreach (var client in clients)
@@ -58,6 +61,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (HttpRequestException ex)
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (Exception ex)
{
_logger.Error(ex, "Unknown error occured in DownloadClientRootFolderCheck HealthCheck");

View File

@@ -0,0 +1,62 @@
using System;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Localization;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ProviderUpdatedEvent<IDownloadClient>))]
[CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))]
[CheckOn(typeof(ModelEvent<RootFolder>))]
[CheckOn(typeof(ModelEvent<RemotePathMapping>))]
public class DownloadClientSortingCheck : HealthCheckBase
{
private readonly IProvideDownloadClient _downloadClientProvider;
private readonly Logger _logger;
public DownloadClientSortingCheck(IProvideDownloadClient downloadClientProvider,
Logger logger,
ILocalizationService localizationService)
: base(localizationService)
{
_downloadClientProvider = downloadClientProvider;
_logger = logger;
}
public override HealthCheck Check()
{
var clients = _downloadClientProvider.GetDownloadClients();
foreach (var client in clients)
{
try
{
var clientName = client.Definition.Name;
var status = client.GetStatus();
if (status.SortingMode.IsNotNullOrWhiteSpace())
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientSortingCheckMessage"), clientName, status.SortingMode), "#download-folder-and-library-folder-not-different-folders");
}
}
catch (DownloadClientException ex)
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (Exception ex)
{
_logger.Error(ex, "Unknown error occurred in DownloadClientSortingCheck HealthCheck");
}
}
return new HealthCheck(GetType());
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Net.Http;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@@ -53,7 +54,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType());
}
var clients = _downloadClientProvider.GetDownloadClients();
// Only check clients not in failure status, those get another message
var clients = _downloadClientProvider.GetDownloadClients(true);
foreach (var client in clients)
{
@@ -100,6 +102,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (HttpRequestException ex)
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (Exception ex)
{
_logger.Error(ex, "Unknown error occured in RemotePathMapping HealthCheck");
@@ -139,7 +145,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
// If the previous case did not match then the failure occured in DownloadedMovieImportService,
// while trying to locate the files reported by the download client
var client = _downloadClientProvider.GetDownloadClients().FirstOrDefault(x => x.Definition.Name == failureMessage.DownloadClientInfo.Name);
// Only check clients not in failure status, those get another message
var client = _downloadClientProvider.GetDownloadClients(true).FirstOrDefault(x => x.Definition.Name == failureMessage.DownloadClientInfo.Name);
if (client == null)
{
return new HealthCheck(GetType());
}
try
{
var status = client.GetStatus();
@@ -192,6 +205,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (HttpRequestException ex)
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (Exception ex)
{
_logger.Error(ex, "Unknown error occured in RemotePathMapping HealthCheck");

View File

@@ -36,7 +36,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.List
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim(), true, "-", "-");
link += $"users/{Settings.Username.Trim()}/lists/{listName}/items/movies?limit={Settings.Limit}";
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
yield return request;
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ImportLists.ImportListMovies;
using NzbDrone.Core.Notifications.Trakt.Resource;
@@ -31,11 +31,11 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
if (_settings.TraktListType == (int)TraktPopularListType.Popular)
{
jsonResponse = JsonConvert.DeserializeObject<List<TraktMovieResource>>(_importResponse.Content);
jsonResponse = STJson.Deserialize<List<TraktMovieResource>>(_importResponse.Content);
}
else
{
jsonResponse = JsonConvert.DeserializeObject<List<TraktListResource>>(_importResponse.Content).SelectList(c => c.Movie);
jsonResponse = STJson.Deserialize<List<TraktListResource>>(_importResponse.Content).SelectList(c => c.Movie);
}
// no movies were return

View File

@@ -71,7 +71,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
link += filtersAndLimit;
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
yield return request;
}

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ImportLists.Exceptions;
using NzbDrone.Core.ImportLists.ImportListMovies;
using NzbDrone.Core.Notifications.Trakt.Resource;
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.ImportLists.Trakt
return movies;
}
var jsonResponse = JsonConvert.DeserializeObject<List<TraktListResource>>(_importResponse.Content);
var jsonResponse = STJson.Deserialize<List<TraktListResource>>(_importResponse.Content);
// no movies were return
if (jsonResponse == null)

View File

@@ -42,7 +42,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
break;
}
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
yield return request;
}

View File

@@ -1,9 +1,11 @@
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Indexers
{
public class RssSyncCommand : Command
{
public override bool SendUpdatesToClient => true;
public override bool IsLongRunning => true;
}
}

View File

@@ -1 +1,7 @@
{}
{
"About": "সম্পর্কিত",
"AcceptConfirmationModal": "নিশ্চিতকরণ মডেল গ্রহণ করুন",
"Actions": "ক্রিয়াকাণ্ড",
"Activity": "কার্যকলাপ",
"Add": "যোগ করুন"
}

View File

@@ -1,9 +1,9 @@
{
"EditDelayProfile": "Upravit profil zpoždění",
"AcceptConfirmationModal": "Přijměte potvrzení Modal",
"AcceptConfirmationModal": "Přijměte potvrzovací modální okno",
"Automatic": "Automatický",
"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.",
"Cast": "Obsazení",
"CheckDownloadClientForDetails": "zkontrolujte stahování klienta pro více informací",
"CheckForFinishedDownloadsInterval": "Zkontrolujte interval dokončených stahování",
@@ -137,7 +137,7 @@
"Ungroup": "Oddělit skupinu",
"Unlimited": "Neomezený",
"UnsavedChanges": "Neuložené změny",
"UpdateMechanismHelpText": "Použijte vestavěný aktualizátor Radarr nebo skript",
"UpdateMechanismHelpText": "Použijte vestavěný aktualizační program Radarr nebo skript",
"UpgradeUntilQuality": "Upgradujte až do kvality",
"UseHardlinksInsteadOfCopy": "Místo kopírování použijte pevné odkazy",
"UsenetDelayTime": "Usenet Zpoždění: {0}",
@@ -157,12 +157,12 @@
"Announced": "Oznámeno",
"AvailabilityDelayHelpText": "Množství času před nebo po dostupném datu pro vyhledání filmu",
"ImportExistingMovies": "Importovat existující filmy",
"AddedToDownloadQueue": "Přidáno do stažené fronty",
"AddedToDownloadQueue": "Přidáno do fronty ke stažení",
"AddNotification": "Přidat oznámení",
"Add": "Přidat",
"AddCustomFormat": "Přidat vlastní formát",
"AddDelayProfile": "Přidat profil zpoždění",
"AddDownloadClient": "Přidat staženého klienta",
"AddDownloadClient": "Přidat klienta pro stahování",
"AddRootFolder": "Přidat kořenovou složku",
"Always": "Vždy",
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Radarru. To zahrnuje informace o vašem prohlížeči, které stránky Radarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.",
@@ -677,7 +677,7 @@
"IMDb": "IMDb",
"ImportCustomFormat": "Importujte vlastní formát",
"ImportedTo": "Importováno do",
"ImportRootPath": "Namiřte Radarr na složku obsahující všechny vaše filmy, nikoli konkrétní film. např. {0} a ne {1}. Každý film musí být navíc ve své vlastní složce v kořenové složce / složce knihovny.",
"ImportRootPath": "Nasměrujte Radarr na složku obsahující všechny vaše filmy, ne na konkrétní film. Např. {0} a ne {1}. Kromě toho musí být každý film ve vlastní složce v rámci kořenové složky nebo knihovny.",
"ImportTipsMessage": "Několik tipů, jak zajistit bezproblémový import:",
"InCinemasDate": "V kinech",
"InCinemasMsg": "Film je v kinech",
@@ -687,13 +687,13 @@
"IncludeUnmonitored": "Zahrnout Nesledováno",
"ImportMovies": "Importovat filmy",
"IndexerPriority": "Priorita indexování",
"IndexerPriorityHelpText": "Priorita indexování od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 25.",
"IndexerPriorityHelpText": "Priorita indexovacího modulu od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 25. Používá se při získávání verzí jako rozhodující prvek pro jinak stejné verze, Radarr bude stále používat všechny povolené indexovací moduly pro Synchronizaci RSS a vyhledávání",
"IndexerRssHealthCheckNoAvailableIndexers": "Všechny indexery podporující rss jsou dočasně nedostupné kvůli nedávným chybám indexeru",
"IndexerRssHealthCheckNoIndexers": "Nejsou k dispozici žádné indexery se zapnutou synchronizací RSS, Radarr nové verze automaticky nezachytí",
"Indexers": "Indexery",
"IndexerSearchCheckNoAutomaticMessage": "Nejsou k dispozici žádné indexery se zapnutým automatickým vyhledáváním, Radarr neposkytne žádné automatické výsledky hledání",
"IndexerSearchCheckNoAvailableIndexersMessage": "Všechny indexery podporující vyhledávání jsou dočasně nedostupné kvůli nedávným chybám indexeru",
"IndexerSearchCheckNoInteractiveMessage": "Pokud je povoleno interaktivní vyhledávání, nejsou k dispozici žádné indexery, Radarr neposkytne žádné interaktivní výsledky hledání",
"IndexerSearchCheckNoInteractiveMessage": "Při povoleném interaktivním vyhledávání, nejsou dostupné žádné indexovací moduly, Radarr neposkytne žádné interaktivní výsledky hledání",
"IndexerSettings": "Nastavení indexeru",
"IndexerStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání: {0}",
"InstallLatest": "Nainstalujte nejnovější",
@@ -1061,6 +1061,6 @@
"AllCollectionsHiddenDueToFilter": "Všechny filmy jsou skryty kvůli použitému filtru.",
"Collections": "Sbírka",
"MonitorMovies": "Monitorujte film",
"NoCollections": "Nebyly nalezeny žádné filmy. Chcete-li začít, budete chtít přidat nový film nebo importovat některé stávající.",
"NoCollections": "Nebyly nalezeny žádné kolekce, pro začátek budete chtít přidat nový film nebo importovat některé stávající",
"RssSyncHelpText": "Interval v minutách. Nastavením na nulu deaktivujete (tím se zastaví veškeré automatické uvolnění uvolnění)"
}

View File

@@ -329,17 +329,17 @@
"BackupRetentionHelpText": "Automatische Backups, die älter als die Aufbewahrungsfrist sind, werden automatisch gelöscht",
"Backups": "Backups",
"BindAddress": "Adresse binden",
"BindAddressHelpText": "Gültige IPv4 Adresse oder \"*\" für alle Netzwerke",
"BindAddressHelpText": "Gültige IP Adresse oder \"*\" für alle Netzwerke",
"Branch": "Git-Branch",
"BypassProxyForLocalAddresses": "Proxy für lokale Adressen umgehen",
"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.",
"CertificationCountry": "Zertifizierungs Land",
"ChangeFileDate": "Erstelldatum der Datei anpassen",
"ChangeHasNotBeenSavedYet": "Änderung wurde noch nicht gespeichert",
"CheckForFinishedDownloadsInterval": "Intervall zum prüfen von fertigen Downloads",
"CleanLibraryLevel": "Mediathek aufräumen",
"ClickToChangeLanguage": "Sprache ändern ...",
"ClickToChangeLanguage": "Sprache ändern",
"ClickToChangeQuality": "Hier klicken um die Qualität zu ändern",
"ClientPriority": "Priorität",
"CloneFormatTag": "Format Tag kopieren",
@@ -516,7 +516,7 @@
"ShowTitleHelpText": "Filmtitel unter dem Plakat anzeigen",
"ShowUnknownMovieItems": "Unzugeordente Filmeinträge anzeigen",
"SkipFreeSpaceCheck": "Pürfung des freien Speichers überspringen",
"SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere diese Option, wenn es nicht möglich ist, den freien Speicherplatz des Stammverzeichnisses für Filme zu erkennen",
"SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere diese Option, wenn es Radarr nicht möglich ist, den freien Speicherplatz des Stammverzeichnisses für Filme zu erkennen",
"SorryThatMovieCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.",
"SourcePath": "Quellpfad",
"SourceRelativePath": "Relativer Quellpfad",
@@ -531,7 +531,7 @@
"TestAllIndexers": "Alle testen",
"TestAllLists": "Alle testen",
"TimeFormat": "Zeitformat",
"UpdateMechanismHelpText": "Benutze den Build-In Updater oder ein Script",
"UpdateMechanismHelpText": "Benutze Radarr's Build-In Updater oder ein Script",
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
"UpgradeAllowedHelpText": "Wenn deaktiviert wird die Qualität nicht verbessert",
"Uptime": "Laufzeit",
@@ -979,7 +979,7 @@
"EditCustomFormat": "Eigenes Format bearbeiten",
"DoNotUpgradeAutomatically": "Nicht automatisch upgraden",
"DoNotPrefer": "Nicht bevorzugen",
"DoneEditingGroups": "Gruppen bearbeiten abgechlossen",
"DoneEditingGroups": "Editieren der Gruppen abschließen",
"Donations": "Spenden",
"DockerUpdater": "aktualisiere den Docker Container um das Update zu erhalten",
"Discord": "Discord",
@@ -1063,7 +1063,7 @@
"ImportListMissingRoot": "Fehlendes Stammverzeichnis für Importlist(en): {0}",
"BypassDelayIfHighestQualityHelpText": "Verzögerung ignorieren wenn das Release die höchste aktivierte Qualität des Qualitätsprofils mit dem bevorzugtem Protokoll ist",
"BypassDelayIfHighestQuality": "Ignoriere wenn höchste Qualität",
"TaskUserAgentTooltip": "UserAgent von der App die die API aufgerufen hat",
"TaskUserAgentTooltip": "UserAgent von der App welche die API aufgerufen hat",
"Letterboxd": "Letterboxd",
"From": "von",
"NotificationTriggersHelpText": "Auslöser für diese Benachrichtigung auswählen",
@@ -1146,5 +1146,12 @@
"TotalMovies": "Filme insgesamt",
"ApplicationURL": "Anwendungs-URL",
"ApplicationUrlHelpText": "Die externe URL der Anwendung inklusive http(s)://, Port und URL-Basis",
"PreferredProtocol": "Bevorzugtes Protokoll"
"PreferredProtocol": "Bevorzugtes Protokoll",
"AreYouSureYouWantToResetQualityDefinitions": "Sicher, dass die Qualitätsdefinitionen zurückgesetzt werden sollen?",
"ResetDefinitions": "Definitionen zurücksetzen",
"ResetQualityDefinitions": "Qualitätsdefinitionen zurücksetzen",
"SettingsThemeHelpText": "Anwendungsdesign ändern, das 'Auto' Design passt sich an den Light/Dark-Mode deines Systems an. Inspiriert von Theme.Park",
"ResetTitles": "Titel zurücksetzen",
"ResetTitlesHelpText": "Definitionstitel und Werte zurücksetzen",
"SettingsTheme": "Design"
}

View File

@@ -89,7 +89,7 @@
"Backups": "Backups",
"BeforeUpdate": "Before update",
"BindAddress": "Bind Address",
"BindAddressHelpText": "Valid IPv4 address or '*' for all interfaces",
"BindAddressHelpText": "Valid IP address, localhost or '*' for all interfaces",
"Blocklist": "Blocklist",
"Blocklisted": "Blocklisted",
"BlocklistHelpText": "Prevents Radarr from automatically grabbing this release again",
@@ -258,6 +258,7 @@
"DownloadClients": "Download Clients",
"DownloadClientSettings": "Download Client Settings",
"DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings",
"DownloadClientSortingCheckMessage": "Download client {0} has {1} sorting enabled for Radarr's category. You should disable sorting in your download client to avoid import issues.",
"DownloadClientStatusCheckAllClientMessage": "All download clients are unavailable due to failures",
"DownloadClientStatusCheckSingleClientMessage": "Download clients unavailable due to failures: {0}",
"DownloadClientUnavailable": "Download client is unavailable",
@@ -869,6 +870,7 @@
"RootFolders": "Root Folders",
"RottenTomatoesRating": "Tomato Rating",
"RSS": "RSS",
"RSSHelpText": "Will be used when Radarr periodically looks for releases via RSS Sync",
"RSSIsNotSupportedWithThisIndexer": "RSS is not supported with this indexer",
"RSSSync": "RSS Sync",
"RssSyncHelpText": "Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)",
@@ -928,7 +930,7 @@
"SettingsShowRelativeDates": "Show Relative Dates",
"SettingsShowRelativeDatesHelpText": "Show relative (Today/Yesterday/etc) or absolute dates",
"SettingsTheme": "Theme",
"SettingsThemeHelpText": "Change Application UI Theme, Inspired by Theme.Park",
"SettingsThemeHelpText": "Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by Theme.Park",
"SettingsTimeFormat": "Time Format",
"SettingsWeekColumnHeader": "Week Column Header",
"SettingsWeekColumnHeaderHelpText": "Shown above each column when week is the active view",

View File

@@ -100,7 +100,7 @@
"BackupIntervalHelpText": "Prowlarrin tietokannan ja asetusten automaattisen varmuuskopioinnin suoritusaikaväli.",
"AppDataDirectory": "AppData-kansio",
"BackupNow": "Varmuuskopioi nyt",
"BindAddressHelpText": "Toimiva IPv4-osoite tai '*' (tähti) kaikille yhteyksille.",
"BindAddressHelpText": "Toimiva IP-osoite, localhost tai '*' (tähti) kaikille yhteyksille.",
"Branch": "Kehityshaara",
"BuiltIn": "Sisäänrakennettu",
"CalendarOptions": "Kalenterin asetukset",
@@ -252,7 +252,7 @@
"Profiles": "Profiilit",
"ProxyType": "Välityspalvelimen tyyppi",
"PtpOldSettingsCheckMessage": "Seuraavat PassThePopcorn-tietolähteet sisältävät vanhentuneita asetuksia, jotka olisi syytä päivittää: {0}",
"QualitiesHelpText": "Listalla ylempänä olevia laatuja painotetaan. Saman ryhmän laadut ovat tasaveroisia. Valitse vain haluamasi laadut.",
"QualitiesHelpText": "Listalla ylempänä olevia laatuja painotetaan enemmän vaikkei niitä ole valittu. Samoissa ryhmissä olevat laadut ovat tasaveroisia. Valitse vain halutut laadut.",
"QualityProfileInUse": "Elokuvaan, listaan tai kokelmaan liitettyä laatuprofiilia ei voida poistaa.",
"QueueIsEmpty": "Jono on tyhjä",
"RadarrCalendarFeed": "Radarr-kalenterisyöte",
@@ -333,7 +333,7 @@
"CreateEmptyMovieFoldersHelpText": "Luo puuttuvat elokuvakansiot levyn tarkistuksen yhteydessä.",
"DeleteDownloadClient": "Poista lataustyökalu",
"ImportHeader": "Lisää Radarriin elokuvia tuomalla olemassa oleva, järjestetty kirjasto.",
"ImportMechanismHealthCheckMessage": "Ota valmiiden latausten käsittely käyttöön",
"ImportMechanismHealthCheckMessage": "Käytä valmiiden latausten käsittelyä",
"MinAvailability": "Pienin saatavuus",
"MovieIsUnmonitored": "Elokuvaa ei valvota",
"MovieNaming": "Elokuvien nimeäminen",
@@ -435,7 +435,7 @@
"StartTypingOrSelectAPathBelow": "Aloita kirjoitus tai valitse sijainti alta",
"StartupDirectory": "Käynnistyskansio",
"System": "Järjestelmä",
"SystemTimeCheckMessage": "Järjestelmän aika on heittä yli vuorokauden verran. Ajoitetut tehtävät eivät luultavasti toimi oikein ennen ajan korjausta.",
"SystemTimeCheckMessage": "Järjestelmän aika on pielessä yli vuorokauden. Ajoitetut tehtävät eivät luultavasti toimi oikein ennen sen korjausta.",
"Posters": "Julisteet",
"PosterSize": "Julisteen koko",
"TagCannotBeDeletedWhileInUse": "Tunnistetta ei voi poistaa, koska se on käytössä",
@@ -653,9 +653,9 @@
"IndexerPriority": "Tietolähteiden painotus",
"IndexerPriorityHelpText": "Tietolähteen painotus: 1 (korkein) - 50 (matalin). Oletusarvo on 25. Käytetään muutoin tasaveroisten julkaisujen sieppauspäätökseen. Kaikkia käytössä olevia tietolähteitä käytetään edelleen RSS-synkronointiin ja hakuun.",
"IndexerRssHealthCheckNoAvailableIndexers": "Kaikki RSS-tietolähteet ovat tilapaisesti tavoittamattomissa viimeaikaisten tietolähdevirheiden vuoksi.",
"IndexerRssHealthCheckNoIndexers": "Yhtään RSS-synkronointia käyttävää tietolähdettä ei ole käytettävissä, eikä Radarr tämän vuoksi sieppaa uusia julkaisuja automaattisesti.",
"IndexerRssHealthCheckNoIndexers": "Yhtään RSS-synkronointia käyttävää tietolähdettä ei ole käytettävissä, eikä uusia julkaisuja sen vuoksi siepata automaattisesti.",
"Indexers": "Tietolähteet",
"IndexerSearchCheckNoAutomaticMessage": "Ei hakemistoja, joissa automaattinen haku on käytössä, Radarr ei tarjoa automaattisia hakutuloksia",
"IndexerSearchCheckNoAutomaticMessage": "Automaattihaussa käytettäviä tietolähteitä ei ole käytettävissä, eikä automaattisia hakutuloksia ole tämän vuoksi saatavilla.",
"IndexerSearchCheckNoAvailableIndexersMessage": "Kaikki hakukelpoiset tietolähteet ovat tilapaisesti tavoittamattomissa viimeaikaisten tietolähdevirheiden vuoksi.",
"IndexerSettings": "Tietolähteiden asetukset",
"IndexerStatusCheckSingleClientMessage": "Tietolähteet eivät ole käytettävissä virheiden vuoksi: {0}",
@@ -750,7 +750,7 @@
"QualityDefinitions": "Laatumääritykset",
"QualityLimitsHelpText": "Rajoitukset suhteutetaan automaattisesti elokuvan kestoon.",
"QualityProfileDeleteConfirm": "Haluatko varmasti poistaa laatuprofiilin {0}",
"QualitySettingsSummary": "Laatumääritykset erilaisia sisältömuotoja ja teidostokokoja varten.",
"QualitySettingsSummary": "Laatumääritykset erilaisia sisältömuotoja ja tiedostokokoja varten.",
"RadarrSupportsAnyIndexer": "Radarr tukee Newznab- ja Torznab-yhteensopivien tietolähteiden ohella myös monia muita alla lueteltuja tietolähteitä.",
"RadarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow": "Radarr tukee mukautettuja ehtoja perustuen julkaisujen alla oleviin ominaisuuksiin.",
"Ratings": "Arviot",
@@ -1146,5 +1146,13 @@
"TotalMovies": "Elokuvia yhteensä",
"ApplicationURL": "Sovelluksen URL-osoite",
"ApplicationUrlHelpText": "Sovelluksen ulkoinen URL-osoite, johon sisältyy http(s)://, portti ja URL-perusta.",
"PreferredProtocol": "Ensisijainen protokolla"
"PreferredProtocol": "Ensisijainen protokolla",
"ResetQualityDefinitions": "Palauta laatumääritykset",
"ResetTitles": "Palauta nimet",
"SettingsTheme": "Teema",
"SettingsThemeHelpText": "Vaihda sovelluksen käyttöliittymän ulkoasua. \"Automaattinen\" vaihtaa vaalean ja tumman tilan käyttöjärjestelmäsi teemaa vastaavaksi. Innoittanut Theme.Park.",
"AreYouSureYouWantToResetQualityDefinitions": "Haluatko varmasti palauttaa laatumääritykset?",
"ResetDefinitions": "Palauta määritykset",
"ResetTitlesHelpText": "Palauta määritysten nimet ja arvot.",
"RSSHelpText": "Käytetään etsittäessä julkaisuja RSS-syötteistä ajoitetusti."
}

View File

@@ -1113,7 +1113,7 @@
"SelectReleaseGroup": "Sélectionner le groupe de publication",
"SetReleaseGroup": "Régler le groupe de publication",
"RefreshMonitoredIntervalHelpText": "Intervalle en minutes entre chaque vérification des téléchargements, minimum 1 minute",
"AllCollectionsHiddenDueToFilter": "Tous les films sont masqués en raison du filtre appliqué.",
"AllCollectionsHiddenDueToFilter": "Toutes les collections sont masquées en raison du filtre appliqué.",
"Collections": "Collections",
"MonitorMovies": "Surveiller le film",
"NoCollections": "Aucun film trouvé, pour commencer, vous voudrez ajouter un nouveau film ou importer des films existants.",

View File

@@ -159,7 +159,7 @@
"BranchUpdateMechanism": "A külső frissítési mechanizmus által használt ágazat",
"BranchUpdate": "Ágazattípus a Radarr frissítéseihez",
"Branch": "Ágazat",
"BindAddressHelpText": "Érvényes IPv4-cím, vagy „*” minden interfészhez",
"BindAddressHelpText": "Érvényes IP-cím, localhost vagy '*' minden interfészhez",
"BindAddress": "Kapcsolási Cím",
"BeforeUpdate": "Alkalmazásfrissítés előtt",
"Backups": "Biztonsági Mentés",
@@ -838,7 +838,7 @@
"EditCustomFormat": "Egyéni Formátumok szerkesztése",
"DoNotUpgradeAutomatically": "Ne frissítsen automatikusan",
"DoNotPrefer": "Nem preferált",
"DoneEditingGroups": "Kész szerkesztő csoportok",
"DoneEditingGroups": "A csoportok szerkesztése kész",
"Donations": "Adományok",
"DockerUpdater": "A Frissítéshez frissítenie kell a Docker tárolót",
"Discord": "Discord",
@@ -965,7 +965,7 @@
"QueueIsEmpty": "A várakozási sor üres",
"QualityProfileInUse": "A filmhez, listához vagy gyűjteményhez csatolt minőségi profil nem törölhető",
"QualityLimitsHelpText": "A korlátozások automatikusan beállítódnak a film hossza szerint.",
"QualitiesHelpText": "A jobb minőség a listában jobban preferált. Ugyanazon minőségek a csoportban egyenlőek. Csak a bejelölt minőségek a kívánt minőségek",
"QualitiesHelpText": "A listán magasabb minőségek előnyösebbek, még akkor is, ha nincs bejelölve. Ugyanazon csoporton belül a tulajdonságok egyenlőek. Csak ellenőrzött minőségek szükségesek",
"Qualities": "Minőségek",
"PrioritySettings": "Prioritás: {0}",
"PreviewRenameHelpText": "Tipp: Átnevezés előnézetéhez... válassza a 'Visszavonást' majd kattintson a film címére és használja a",
@@ -1144,7 +1144,15 @@
"InstanceNameHelpText": "Példánynév a böngésző lapon és a syslog alkalmazás neve",
"RottenTomatoesRating": "Tomato Értékelés",
"TotalMovies": "Összes film",
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a \"http(s)://\"-t, a \"Portot\" és az \"URL-alapot\" is",
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a http(s)://-t, a portot és az URL-alapot",
"ApplicationURL": "Alkalmazás URL-je",
"PreferredProtocol": "Preferált protokoll"
"PreferredProtocol": "Preferált protokoll",
"AreYouSureYouWantToResetQualityDefinitions": "Biztos visszaállítod a minőségi definíciókat?",
"SettingsTheme": "Téma",
"SettingsThemeHelpText": "Változtasd meg az alkalmazás felhasználói felület témáját, az „Auto” téma az operációs rendszer témáját használja a Világos vagy Sötét mód beállításához. A Theme.Park ihlette",
"ResetDefinitions": "Definíciók visszaállítása",
"ResetQualityDefinitions": "Állítsd vissza a minőségi meghatározásokat",
"ResetTitles": "Címek visszaállítása",
"ResetTitlesHelpText": "A definíciócímek és értékek visszaállítása",
"RSSHelpText": "Akkor használatos, amikor a Radarr rendszeresen keres kiadásokat az RSS Sync segítségével"
}

View File

@@ -59,7 +59,7 @@
"AppDataLocationHealthCheckMessage": "L'aggiornamento non sarà possibile per evitare la cancellazione di AppData durante l'aggiornamento",
"Analytics": "Analitica",
"Added": "Aggiunto",
"About": "Informazioni",
"About": "Info",
"Year": "Anno",
"Week": "Settimana",
"Updates": "Aggiornamenti",
@@ -189,7 +189,7 @@
"ChooseAnotherFolder": "Scegli un'altra Cartella",
"Cast": "Cast",
"Calendar": "Calendario",
"BackupNow": "Effettua il Backup adesso",
"BackupNow": "Esegui backup ora",
"Backup": "Backup",
"All": "Tutti",
"Agenda": "Agenda",
@@ -220,12 +220,12 @@
"ChangeFileDate": "Cambiare la Data del File",
"CertificationCountryHelpText": "Seleziona il Paese per le Certificazioni dei Film",
"CertificationCountry": "Paese di Certificazione",
"CertificateValidationHelpText": "Cambia quanto è rigorosa la convalida del certificato HTTPS. Non cambiare a meno che tu non comprenda i rischi.",
"CertificateValidationHelpText": "Cambia quanto rigorosamente vengono validati i certificati HTTPS. Non cambiare senza conoscerne i rischi.",
"CertificateValidation": "Convalida del Certificato",
"Cancel": "Annulla",
"BypassProxyForLocalAddresses": "Evita il Proxy per gli Indirizzi Locali",
"Branch": "Ramo",
"BindAddressHelpText": "Indirizzo IPv4 valido o '*' per tutte le interfacce",
"BindAddressHelpText": "Indirizzo IPV4 valido o '*' per tutte le interfacce di rete",
"BindAddress": "Indirizzo di Bind",
"Backups": "I Backup",
"BackupRetentionHelpText": "I backup automatici più vecchi del periodo di conservazione verranno eliminati automaticamente",
@@ -261,7 +261,7 @@
"TotalSpace": "Spazio Totale",
"Title": "Titolo",
"Time": "Ora",
"TestAll": "Testa Tutti",
"TestAll": "Prova Tutti",
"Test": "Test",
"TableOptionsColumnsMessage": "Scegli quali colonne rendere visibili ed il loro ordine",
"TableOptions": "Opzioni della tabella",
@@ -452,7 +452,7 @@
"RadarrSupportsAnyRSSMovieListsAsWellAsTheOneStatedBelow": "Radarr supporta qualunque Lista di film RSS, cosi come le altre sotto.",
"RadarrSupportsAnyDownloadClient": "Radarr supporta qualunque client di download che usi gli standard Newznab, cosi come gli altri client sotto.",
"QuickImport": "Sposta automaticamente",
"Queued": "Messo in coda",
"Queued": "In coda",
"QualitySettings": "Impostazione di Qualità",
"QualityProfileDeleteConfirm": "Sicuro di voler cancellare il profilo di qualità {0}",
"QualityCutoffHasNotBeenMet": "Il qualità di taglio non è stata raggiunta",
@@ -713,8 +713,8 @@
"TimeFormat": "Formato orario",
"ThisConditionMatchesUsingRegularExpressions": "Questa condizione si applica usando espressione regolari. Nota che i caratteri {0} hanno significati speciali e devono essere evitati con un {1}",
"TestAllLists": "Testa tutte le liste",
"TestAllIndexers": "Testa tutti gli Indicizzatori",
"TestAllClients": "Testa Tutti i Client",
"TestAllIndexers": "Prova tutti gli indicizzatori",
"TestAllClients": "Testa tutti i client",
"TagsHelpText": "Si applica ai film con almeno un tag corrispondente",
"TagIsNotUsedAndCanBeDeleted": "L'etichetta non è in uso e può essere eliminata",
"TagCannotBeDeletedWhileInUse": "Non può essere cancellato mentre è in uso",
@@ -793,7 +793,7 @@
"MovieDetailsPreviousMovie": "Dettagli del film: film precedente",
"FocusSearchBox": "Evidenzia casella di ricerca",
"CloseCurrentModal": "Chiudi la Modale Attuale",
"AcceptConfirmationModal": "Acetta Conferma Modale",
"AcceptConfirmationModal": "Accetta Conferma Modale",
"StartSearchForMissingMovie": "Avvia ricerca film mancanti",
"StartProcessing": "Avvia Lavorazione",
"StartImport": "Avvia Importazione",
@@ -842,7 +842,7 @@
"AddQualityProfile": "Aggiungi Profilo Qualità",
"AddNotification": "Aggiungi Notifica",
"AddedToDownloadQueue": "Aggiunto alla coda di download",
"AddDownloadClient": "Aggiungi Client di Download",
"AddDownloadClient": "Aggiungi Downloader",
"AddCustomFormat": "Aggiungi Formato Personalizzato",
"Add": "Aggiungi",
"ImportLibrary": "Importazione della libreria",
@@ -1091,11 +1091,18 @@
"ClickToChangeReleaseGroup": "Clicca per cambiare gruppo di rilascio",
"DiscordUrlInSlackNotification": "Hai una notifica Discord configurata come notifica Slack. Configurala come notifica Discord per una miglior funzionalità. Le notifiche interessate sono: {0}",
"Duration": "Durata",
"InstanceNameHelpText": "Nome dell'istanza nella scheda e per il nome dell'applicazione Syslog",
"InstanceNameHelpText": "Nome istanza nella scheda e per il nome dell'app nel Syslog",
"InstanceName": "Nome Istanza",
"AllCollectionsHiddenDueToFilter": "Tutti i film sono nascosti a causa del filtro applicato.",
"Collections": "Collezione",
"MonitorMovies": "Monitora Film",
"NoCollections": "Nessun film trovato, per iniziare ti consigliamo di aggiungere un nuovo film o importarne alcuni esistenti.",
"RssSyncHelpText": "Intervallo in minuti. Imposta zero per disabilitarlo (ciò fermerà il recupero automatico di tutte le release)"
"RssSyncHelpText": "Intervallo in minuti. Imposta zero per disabilitarlo (ciò fermerà il recupero automatico di tutte le release)",
"ApplicationURL": "URL Applicazione",
"ApplicationUrlHelpText": "L'URL esterno di questa applicazione, incluso http(s)://, porta e URL base",
"CollectionOptions": "Opzioni della Collezione",
"CollectionShowDetailsHelpText": "Mostra lo stato e le proprietà della collezione",
"Started": "Iniziato",
"AreYouSureYouWantToResetQualityDefinitions": "Sei sicuro di voler ripristinare le definizioni della qualità?",
"ChooseImportMode": "Selezionare Metodo di Importazione"
}

View File

@@ -368,11 +368,11 @@
"ChangeFileDate": "Wijzig Bestandsdatum",
"CertificationCountryHelpText": "Selecteer Land voor Film Certificatie",
"CertificationCountry": "Certificatie Land",
"CertificateValidationHelpText": "Wijzig hoe strikt HTTPS certificaat validatie is",
"CertificateValidationHelpText": "Wijzig hoe strict HTTPS certificaat validatie is. Wijzig dit niet behalve als je de risico's begrijpt.",
"CertificateValidation": "Certificaat Validatie",
"BypassProxyForLocalAddresses": "Omzeil Proxy voor Lokale Adressen",
"Branch": "Branch",
"BindAddressHelpText": "Geldig IPv4 adres of '*' voor alle interfaces",
"BindAddressHelpText": "Geldig IP-adres, localhost of '*' voor alle interfaces",
"DeleteBackup": "Verwijder Veiligheidskopie",
"BackupIntervalHelpText": "Tussentijd voor automatische back-up",
"Backups": "Veiligheidskopieën",
@@ -723,7 +723,7 @@
"HaveNotAddedMovies": "U heeft nog geen films toegevoegd, wilt u eerst enkele of al uw films importeren?",
"ForMoreInformationOnTheIndividualIndexers": "Voor meer informatie over de individuele indexeerders, klik op de info knoppen.",
"ForMoreInformationOnTheIndividualImportListsClinkOnTheInfoButtons": "Voor meer informatie over de individuele lijsten, klik op de info knoppen.",
"ForMoreInformationOnTheIndividualDownloadClients": "Voor meer informatie over de individuele downloaders, klik op de info knoppen.",
"ForMoreInformationOnTheIndividualDownloadClients": "Voor meer informatie over de individuele download applicaties, klik op de info knoppen.",
"FailedLoadingSearchResults": "Laden van zoekresultaten is mislukt, gelieve opnieuw te proberen.",
"ExtraFileExtensionsHelpTexts1": "Komma gescheiden lijst met extra bestanden om te importeren (.nfo zal als .nfo-orig worden geïmporteerd)",
"CouldNotFindResults": "Kon geen resultaten vinden voor '{0}'",
@@ -806,10 +806,10 @@
"AllMoviesInPathHaveBeenImported": "Alle films in {0} zijn geïmporteerd",
"AllFiles": "Alle bestanden",
"AfterManualRefresh": "Na handmatig verversen",
"AddToDownloadQueue": "Toegevoegd aan download wachtrij",
"AddToDownloadQueue": "Toegevoegd aan downloadwachtrij",
"AddQualityProfile": "Kwaliteitsprofiel toevoegen",
"AddNotification": "Notificatie toevoegen",
"AddedToDownloadQueue": "Aan de download wachtrij toegevoegd",
"AddedToDownloadQueue": "Aan de download wachtrijtoegevoegd",
"AddDownloadClient": "Download Client Toevoegen",
"Add": "Toevoegen",
"ChmodFolderHelpTextWarning": "Dit werkt alleen als de gebruiker die Radarr draait de eigenaar is van het bestand. Het is beter om zeker te zijn dat de downloader de juiste rechten zet.",
@@ -1045,7 +1045,7 @@
"DeleteFileLabel": "Verwijder {0} Film Bestanden",
"UnableToAddRootFolder": "Kon hoofdmappen niet inladen",
"UpdateAvailable": "Nieuwe update is beschikbaar",
"From": "Van",
"From": "van",
"RemotePathMappingCheckDownloadPermissions": "Radarr kan gedownloade film {0} zien, maar niet openen. Waarschijnlijk fout met machtigingen.",
"RemotePathMappingCheckFileRemoved": "Bestand {0} is halverwege de verwerking verwijderd.",
"RemotePathMappingCheckFilesBadDockerPath": "U gebruikt docker; download client {0} gerapporteerde bestanden in {1} maar dit is geen geldig {2} pad. Controleer uw externe padtoewijzingen en download clientinstellingen.",
@@ -1107,5 +1107,14 @@
"Collections": "Collectie",
"MonitorMovies": "Bewaak Film",
"ApplicationURL": "Applicatie URL",
"ApplicationUrlHelpText": "De externe URL van deze applicatie inclusief http(s)://,Port en URL base"
"ApplicationUrlHelpText": "De externe URL van deze applicatie inclusief http(s)://,Port en URL base",
"CollectionsSelectedInterp": "{0} Collectie(s) geselecteerd",
"AreYouSureYouWantToResetQualityDefinitions": "Weet je zeker dat je de kwaliteitsdefinities wilt resetten?",
"ChooseImportMode": "Kies Importmodus",
"CollectionOptions": "Collectieopties",
"CollectionShowDetailsHelpText": "Collectie status en details weergeven",
"CollectionShowOverviewsHelpText": "Collectieoverzicht weergeven",
"EditCollection": "Bewerk collectie",
"BypassDelayIfHighestQualityHelpText": "Vertraging negeren wanneer een release de hoogst mogelijke kwaliteit heeft in het kwaliteitsprofiel van het geprefereerde protocol",
"CollectionShowPostersHelpText": "Posters van de collectie tonen"
}

View File

@@ -19,7 +19,7 @@
"IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas",
"IndexersSettingsSummary": "Indexadores e restrições de lançamento",
"IndexerSettings": "Configurações do indexador",
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa ativada, o Radarr não fornecerá nenhum resultado de pesquisa interativa",
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa habilitada, o Radarr não fornecerá nenhum resultado de pesquisa interativo",
"IndexerSearchCheckNoAvailableIndexersMessage": "Todos os indexadores com capacidade de pesquisa estão temporariamente indisponíveis devido a erros recentes do indexador",
"IndexerSearchCheckNoAutomaticMessage": "Nenhum indexador disponível com a Pesquisa automática habilitada, o Radarr não fornecerá nenhum resultado de pesquisa automática",
"Indexers": "Indexadores",
@@ -84,7 +84,7 @@
"HealthNoIssues": "Nenhum problema com sua configuração",
"Health": "Integridade",
"HaveNotAddedMovies": "Você ainda não adicionou nenhum filme, deseja importar alguns ou todos os seus filmes primeiro?",
"HardlinkCopyFiles": "Vínculo real/Copiar arquivos",
"HardlinkCopyFiles": "Hardlink/Copiar arquivos",
"Group": "Grupo",
"GrabSelected": "Obter selecionado",
"GrabReleaseMessageText": "O Radarr não conseguiu determinar a qual filme este lançamento está relacionado. O Radarr pode não conseguir importar automaticamente este lançamento. Quer obter \"{0}\"?",
@@ -174,7 +174,7 @@
"EditListExclusion": "Editar exclusão da lista",
"Edition": "Edição",
"EditIndexer": "Editar indexador",
"EditGroups": "Editar grupos",
"EditGroups": "Editar Grupos",
"EditDelayProfile": "Editar perfil de atraso",
"EditCustomFormat": "Editar formato personalizado",
"Edit": "Editar",
@@ -202,7 +202,7 @@
"DownloadClient": "Cliente de download",
"DoNotUpgradeAutomatically": "Não atualizar automaticamente",
"DoNotPrefer": "Não preferir",
"DoneEditingGroups": "Edição de grupos concluída",
"DoneEditingGroups": "Concluído a Edição de Grupos",
"Donations": "Doações",
"DockerUpdater": "atualizar o contêiner do Docker para receber a atualização",
"Docker": "Docker",
@@ -340,7 +340,7 @@
"BranchUpdateMechanism": "Ramificação usada pelo mecanismo de atualização externo",
"BranchUpdate": "Ramificação para atualização do Radarr",
"Branch": "Ramo",
"BindAddressHelpText": "Endereço IPv4 Válido ou '*' para todas as interfaces",
"BindAddressHelpText": "Endereço IP válido, localhost ou '*' para todas as interfaces",
"BindAddress": "Fixar Endereço",
"BeforeUpdate": "Antes da atualização",
"Backups": "Backups",
@@ -484,7 +484,7 @@
"AddMovies": "Adicionar filmes",
"AddMovie": "Adicionar filme",
"AddListExclusion": "Adicionar exclusão à lista",
"AddList": "Adicionar à Lista",
"AddList": "Adicionar Lista",
"AddingTag": "Adicionando etiqueta",
"AddIndexer": "Adicionar indexador",
"AddImportExclusionHelpText": "Impedir a adição do filme ao Radarr por listas",
@@ -752,7 +752,7 @@
"Restrictions": "Restrições",
"RestoreBackup": "Restaurar backup",
"Restore": "Restaurar",
"RestartRequiredHelpTextWarning": "Requer reinicio para fazer efeito",
"RestartRequiredHelpTextWarning": "Requer reinicialização para ter efeito",
"RestartRadarr": "Reiniciar o Radarr",
"RestartNow": "Reiniciar agora",
"Restart": "Reiniciar",
@@ -854,7 +854,7 @@
"QualityDefinitions": "Definições de qualidade",
"QualityCutoffHasNotBeenMet": "Limite de qualidade não atingido",
"Quality": "Qualidade",
"QualitiesHelpText": "As qualidades mais altas na lista são mais preferidas, mesmo que não estejam marcadas. As qualidades dentro do mesmo grupo são iguais. Apenas qualidades marcadas são desejadas",
"QualitiesHelpText": "As qualidades mais altas na lista são mais preferidas, mesmo que não sejam verificadas. As qualidades dentro do mesmo grupo são iguais. Somente qualidades verificadas são desejadas",
"Qualities": "Qualidades",
"PublishedDate": "Data de publicação",
"PtpOldSettingsCheckMessage": "As configurações dos seguintes indexadores do PassThePopcorn são obsoletas e devem ser atualizadas: {0}",
@@ -868,7 +868,7 @@
"Proxy": "Proxy",
"ProtocolHelpText": "Escolha que protocolo(s) utilizar e qual o preferido ao escolher entre versões iguais",
"Protocol": "Protocolo",
"Proper": "Proper",
"Proper": "Apropriado",
"Progress": "Progresso",
"ProfilesSettingsSummary": "Perfis de qualidade, idioma e atraso",
"Profiles": "Perfis",
@@ -923,7 +923,7 @@
"OnRenameHelpText": "Ao renomear",
"OnRename": "Ao Renomear",
"OnlyUsenet": "Apenas Usenet",
"OnlyTorrent": "Apenas torrents",
"OnlyTorrent": "Apenas Torrents",
"OnLatestVersion": "A versão mais recente do Radarr já está instalada",
"OnImport": "Ao importar",
"OnHealthIssueHelpText": "Ao ter problema de integridade",
@@ -961,7 +961,7 @@
"UpdateSelected": "Atualizar selecionado(s)",
"UpdateScriptPathHelpText": "Caminho para um script personalizado que usa um pacote de atualização extraído e lida com o restante do processo de atualização",
"Updates": "Atualizações",
"UpdateMechanismHelpText": "Use o atualizador integrado do Radarr ou um script",
"UpdateMechanismHelpText": "Usar o atualizador integrado do Radarr ou um script",
"UpdateCheckUINotWritableMessage": "Não é possível instalar a atualização porque a pasta de interface do usuário '{0}' não é gravável pelo usuário '{1}'.",
"UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta App Translocation.",
"UpdateCheckStartupNotWritableMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.",
@@ -1053,7 +1053,7 @@
"RemotePathMappingCheckImportFailed": "O Radarr não conseguiu importar um filme. Verifique os logs para saber mais.",
"RemotePathMappingCheckFileRemoved": "O arquivo {0} foi removido no meio do processamento.",
"RemotePathMappingCheckDownloadPermissions": "O Radarr pode ver, mas não pode acessar o filme baixado {0}. Provável erro de permissões.",
"RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca downloads em {1} mas o Radarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
"RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca os downloads em {1}, mas o Radarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
"RemotePathMappingCheckWrongOSPath": "O cliente de download remoto {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.",
"RemotePathMappingCheckLocalWrongOSPath": "O cliente de download local {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise as configurações do seu cliente de download.",
"RemotePathMappingCheckLocalFolderMissing": "O cliente de download remoto {0} coloca downloads em {1}, mas esse diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.",
@@ -1078,7 +1078,7 @@
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Você tem certeza de que deseja remover {0} item{1} da fila?",
"BlocklistReleases": "Lançamento na lista de bloqueio",
"RemoveSelectedItems": "Remover Itens Selecionados",
"IndexerTagHelpText": "Só use este indexador para filmes com ao menos uma etiqueta correspondente. Deixe em branco para usar com todos os filmes.",
"IndexerTagHelpText": "Use este indexador apenas para filmes com pelo menos uma etiqueta correspondente. Deixe em branco para usar com todos os filmes.",
"RemoveSelectedItem": "Remover Item Selecionado",
"RemoveFailed": "Falha na Remoção",
"RemoveCompleted": "Remover Completos",
@@ -1087,7 +1087,7 @@
"OnApplicationUpdateHelpText": "Na Atualização do Aplicativo",
"DiscordUrlInSlackNotification": "Você tem uma notificação do Discord configurado como uma notificação do Slack. Definir isso como uma notificação do Discord para melhor funcionalidade. Com efeito, notificações são: {0}",
"AnnouncedMsg": "Filme foi anunciado",
"IndexerDownloadClientHelpText": "Especificar em que cliente de download é usado para baixar deste indexador",
"IndexerDownloadClientHelpText": "Especificar qual cliente de download é utilizado para baixar a partir deste indexador",
"LocalPath": "Caminho Local",
"ManualImportSetReleaseGroup": "Importar Manual - Definir Grupo de Lançamento",
"SelectLanguages": "Selecionar Idiomas",
@@ -1144,7 +1144,15 @@
"CollectionShowPostersHelpText": "Mostrar pôsteres de itens da coleção",
"RottenTomatoesRating": "Avaliação Tomato",
"TotalMovies": "Total de Filmes",
"ApplicationURL": "URL da Aplicação",
"ApplicationUrlHelpText": "O URL externa deste aplicativo, incluindo http(s)://, porta e base de URL",
"PreferredProtocol": "Protocolo Preferido"
"ApplicationURL": "URL do Aplicativo",
"ApplicationUrlHelpText": "A URL externa deste aplicativo, incluindo http(s)://, porta e base da URL",
"PreferredProtocol": "Protocolo Preferido",
"SettingsThemeHelpText": "Alterar o tema da interface do usuário do aplicativo, o tema 'Auto' usará o tema do sistema operacional para definir o modo Claro ou Escuro. Inspirado por Theme.Park",
"ResetDefinitions": "Redefinir definições",
"ResetQualityDefinitions": "Redefinir Definições de Qualidade",
"ResetTitles": "Redefinir Títulos",
"ResetTitlesHelpText": "Redefinir títulos de definição, bem como valores",
"SettingsTheme": "Tema",
"AreYouSureYouWantToResetQualityDefinitions": "Tem certeza de que deseja redefinir as definições de qualidade?",
"RSSHelpText": "Será usado quando o Radarr procurar periodicamente lançamentos via RSS Sync"
}

View File

@@ -170,7 +170,7 @@
"ExcludeTitle": "Исключить {0}? Radarr не будет автоматически добавлять сканируя лист.",
"InCinemasMsg": "Фильмы в кинотеатрах",
"IncludeRecommendationsHelpText": "Включить в отображении найденного фильмы рекомендованные Radar",
"IndexerPriorityHelpText": "Приоритет индексатора от 1 (самый высокий) до 50 (самый низкий). По умолчанию: 25. Используется при захвате выпусков в качестве средства разрешения конфликтов для равных в остальном выпусков, Radarr по-прежнему будет использовать все включенные индексаторы для синхронизации и поиска RSS.",
"IndexerPriorityHelpText": "Приоритет индексатора от 1 (самый высокий) до 50 (самый низкий). По умолчанию: 25. Используется при захвате выпусков в качестве средства разрешения конфликтов для равных в остальном выпусков, Radarr по-прежнему будет использовать все включенные индексаторы для синхронизации и поиска RSS",
"IndexerSearchCheckNoAvailableIndexersMessage": "Все индексаторы с возможностью поиска временно выключены из-за ошибок",
"KeyboardShortcuts": "Горячие клавиши",
"CantFindMovie": "Почему я не могу найти фильм?",
@@ -330,7 +330,7 @@
"Connect": "Подключить",
"Connections": "Соединения",
"CompletedDownloadHandling": "Обработка завершенных скачиваний",
"IndexerSearchCheckNoInteractiveMessage": "Нет доступных индексаторов с интерактивным поиском, Radarr не будет предоставлять результаты интерактивного поиска",
"IndexerSearchCheckNoInteractiveMessage": "Нет доступных индексаторов с включенным интерактивным поиском, Radarr не будет предоставлять результаты интерактивного поиска",
"IndexersSettingsSummary": "Ограничения для индексаторов и релизов",
"Language": "Язык",
"ImportListStatusCheckAllClientMessage": "Все листы недоступны из-за ошибок",
@@ -421,7 +421,7 @@
"QualitySettingsSummary": "Качественные размеры и наименования",
"QualitySettings": "Настройки качества",
"QualityProfiles": "Профили качества",
"QualityProfileInUse": "Невозможно удалить профиль качества прикрепленный к фильму",
"QualityProfileInUse": "Невозможно удалить профиль качества, прикрепленный к фильму, списку или коллекции",
"QualityProfileDeleteConfirm": "Вы действительно хотите удалить профиль качества {0}",
"QualityProfile": "Профиль качества",
"QualityOrLangCutoffHasNotBeenMet": "Не соблюдено ограничение по качеству или языку",
@@ -429,7 +429,7 @@
"QualityDefinitions": "Определения качества",
"QualityCutoffHasNotBeenMet": "Порог качества не соблюден",
"Quality": "Качество",
"QualitiesHelpText": "Качества, расположенные выше в списке, более предпочтительны. Качества внутри одной группы равны. Требуются только отмеченные качества",
"QualitiesHelpText": "Качества, расположенные выше в списке, являются более предпочтительными, даже если они не отмечены. Качества внутри одной группы равны. Требуются только отмеченные качества",
"Qualities": "Качества",
"PublishedDate": "Дата публикации",
"PtpOldSettingsCheckMessage": "Следующие индексаторы PassThePopcorn устарели и должны быть обновлены: {0}",
@@ -747,7 +747,7 @@
"BranchUpdateMechanism": "Ветвь, используемая внешним механизмом обновления",
"BranchUpdate": "Ветвь для обновления Radarr",
"Branch": "Ветка",
"BindAddressHelpText": "Действительный IPv4-адрес или '*' для всех интерфейсов",
"BindAddressHelpText": "Действительный IP-адрес, локальный адрес или '*' для всех интерфейсов",
"BackupFolderHelpText": "Относительные пути будут в каталоге AppData Radarr",
"AvailabilityDelayHelpText": "Время до или после доступной даты для поиска фильма",
"AvailabilityDelay": "Задержка доступности",
@@ -1047,7 +1047,7 @@
"RemotePathMappingCheckRemoteDownloadClient": "Удалённый клиент загрузки {0} сообщил о файлах в {1}, но эта директория, похоже, не существует. Вероятно, отсутствует сопоставление удаленных путей.",
"RemotePathMappingCheckLocalWrongOSPath": "Локальный клиент загрузки {0} загружает файлы в {1}, но это не правильный путь {2}. Проверьте настройки клиента загрузки.",
"RemotePathMappingCheckLocalFolderMissing": "Удалённый клиент загрузки {0} загружает файлы в {1}, но эта директория, похоже, не существует. Вероятно, отсутствует или неправильно указан удаленный путь.",
"RemotePathMappingCheckGenericPermissions": "Клиент загрузки {0} загружает файлы в {1}, но Radarr не может найти эту директорию. Возможно, вам нужно настроить права доступа к данной директории.",
"RemotePathMappingCheckGenericPermissions": "Клиент загрузки {0} загружает файлы в {1}, но Radarr не может видеть этот каталог. Возможно, вам нужно настроить права доступа к данной директории.",
"RemotePathMappingCheckFilesBadDockerPath": "Вы используете docker; клиент загрузки {0} сообщил о файлах в {1}, но это не корректный путь {2}. Проверьте правильность указанного пути и настройки клиента загрузки.",
"RemotePathMappingCheckImportFailed": "Radarr не удалось импортировать фильм. Проверьте ваши логи для более подробной информации.",
"RemotePathMappingCheckFolderPermissions": "Radarr видит директорию загрузки {0}, но не имеет доступа к ней. Возможно, ошибка в правах доступа.",
@@ -1108,7 +1108,7 @@
"AllCollectionsHiddenDueToFilter": "Все фильмы скрыты в соответствии с фильтром.",
"Collections": "Коллекции",
"MonitorMovies": "Отслеживать фильм",
"NoCollections": "Коллекции не найдены. Для начала вам нужно добавить новый фильм или импортировать несколько существующих.",
"NoCollections": "Коллекции не найдены. Для начала вам нужно добавить новый фильм или импортировать несколько существующих",
"CollectionOptions": "Параметры коллекции",
"CollectionShowDetailsHelpText": "Показать статус и свойства коллекции",
"CollectionShowOverviewsHelpText": "Показать обзоры коллекций",
@@ -1142,5 +1142,17 @@
"RottenTomatoesRating": "Tomato рейтинг",
"Started": "Запущено",
"Database": "База данных",
"From": "из"
"From": "из",
"AreYouSureYouWantToResetQualityDefinitions": "Вы уверены, что хотите сбросить определения качества?",
"ResetDefinitions": "Сбросить определения",
"ResetQualityDefinitions": "Сбросить определения качества",
"ResetTitles": "Сбросить заголовки",
"ResetTitlesHelpText": "Сбросить заголовки определений, а также значения",
"RSSHelpText": "Будет использоваться, когда Radarr будет периодически искать выпуски через RSS Sync",
"SettingsTheme": "Тема",
"SettingsThemeHelpText": "Измените тему пользовательского интерфейса приложения, тема «Авто» будет использовать тему вашей ОС для установки светлого или темного режима. Вдохновленный Theme.Park",
"PreferredProtocol": "Предпочтительный протокол",
"ApplicationURL": "URL-адрес приложения",
"ApplicationUrlHelpText": "Внешний URL-адрес этого приложения, включая http(s)://, порт и базовый URL-адрес",
"ScrollMovies": "Прокрутите фильмы"
}

View File

@@ -36,7 +36,7 @@
"Backups": "Резервні копії",
"BeforeUpdate": "Перед оновленням",
"BindAddress": "Прив'язувати адресу",
"BindAddressHelpText": "Дійсна адреса IPv4 або '*' для всіх інтерфейсів",
"BindAddressHelpText": "Дійсна адреса IP або '*' для всіх інтерфейсів",
"Blocklist": "Чорний список",
"Blocklisted": "Заблокований",
"BlocklistRelease": "Реліз із чорного списку",
@@ -72,7 +72,7 @@
"CloneProfile": "Клонувати профіль",
"Close": "Закрити",
"CloseCurrentModal": "Закрити поточне вікно",
"Collection": "Колекція",
"Collection": "Колекції",
"ColonReplacement": "Заміна двокрапок",
"ColonReplacementFormatHelpText": "Змінити як Radarr обробляє заміну двокрапок",
"Columns": "Колонки",
@@ -218,5 +218,941 @@
"ImportCustomFormat": "Додати свій формат",
"DeleteRestrictionHelpText": "Ви впевнені, що хочете видалити цей профіль затримки?",
"AllCollectionsHiddenDueToFilter": "Всі фільми заховані відповідно до фільтра.",
"Collections": "Колекція"
"Collections": "Колекція",
"CollectionOptions": "Параметри колекції",
"CollectionShowDetailsHelpText": "Показати статус і властивості колекції",
"CollectionShowOverviewsHelpText": "Показати огляд колекції",
"CollectionShowPostersHelpText": "Показати плакати предметів колекції",
"DeleteList": "Видалити список",
"DeleteMovieFolderHelpText": "Видалити папку з фільмами та її вміст",
"DeleteImportListExclusion": "Видалити виключення зі списку імпорту",
"DeleteIndexer": "Видалити індексатор",
"DeleteMovieFolderLabel": "Видалити папку з фільмами",
"DeleteSelectedMovie": "Видалити вибрані фільм(и)",
"Enable": "Увімкнути",
"EnableAutomaticAdd": "Увімкнути автоматичне додавання",
"EnableAutomaticSearchHelpText": "Використовуватиметься, коли автоматичний пошук виконується через інтерфейс користувача або Radarr",
"Ended": "Завершено",
"Fixed": "Виправлено",
"FocusSearchBox": "Перейти до вікна пошуку",
"Filters": "Фільтри",
"FirstDayOfWeek": "Перший день тижня",
"IgnoreDeletedMovies": "Відключити моніторинг видалених фільмів",
"IgnoredPlaceHolder": "Додайте нове обмеження",
"ImportedTo": "Імпортовано в",
"ImportErrors": "Помилки імпорту",
"Imported": "Імпортні",
"IndexersSettingsSummary": "Індексатори та обмеження випуску",
"LogLevelTraceHelpTextWarning": "Журнал трасування слід увімкнути лише тимчасово",
"LogLevel": "Рівень журналу",
"Monday": "Понеділок",
"MonitoredHelpText": "Завантажте фільм, якщо є",
"MoreDetails": "Детальніше",
"MountCheckMessage": "Монтування, що містить шлях до фільму, монтується лише для читання: ",
"MovieIsRecommend": "Фільм рекомендовано на основі останнього додавання",
"MovieIsOnImportExclusionList": "Фільм у списку винятків для імпорту",
"MultiLanguage": "Багатомовність",
"NoLeaveIt": "Ні, залиште це",
"NoLimitForAnyRuntime": "Немає обмежень для будь-якого часу виконання",
"NoCollections": "Колекції не знайдено. Щоб почати, ви захочете додати новий фільм або імпортувати кілька наявних",
"NoEventsFound": "Подій не знайдено",
"NoHistory": "Без історії",
"None": "Жодного",
"OnHealthIssue": "Про питання здоров'я",
"RemotePathMappingCheckDownloadPermissions": "Radarr може бачити, але не має доступу до завантаженого фільму {0}. Ймовірна помилка дозволів.",
"Remove": "Видалити",
"RemotePathMappingCheckWrongOSPath": "Клієнт віддаленого завантаження {0} розміщує завантаження в {1}, але це недійсний шлях {2}. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
"ReplaceWithSpaceDashSpace": "Замінити на Пробіл Тире Пробіл",
"RequiredHelpText": "Ця умова {0} має збігатися, щоб користувацький формат застосовувався. В іншому випадку достатньо одного збігу {1}.",
"RequiredRestrictionHelpText": "Реліз має містити принаймні один із цих термінів (незалежно від регістру)",
"RequiredRestrictionPlaceHolder": "Додайте нове обмеження",
"Required": "Обов'язковий",
"SelectLanguage": "Оберіть мову",
"UnableToLoadDownloadClients": "Не вдалося завантажити клієнти для завантаження",
"OnApplicationUpdate": "Оновлення програми",
"OutputPath": "Вихідний шлях",
"Overview": "Огляд",
"ProxyType": "Тип проксі",
"RecyclingBin": "Сміттєвий кошик",
"Result": "Результат",
"Retention": "Утримання",
"RetryingDownloadInterp": "Повторна спроба завантажити {0} о {1}",
"Settings": "Налаштування",
"ShowSizeOnDisk": "Показати розмір на диску",
"ShowTitle": "Показати назву",
"ShowStudio": "Показати Студію",
"UnableToLoadCustomFormats": "Не вдалося завантажити спеціальні формати",
"Auto": "Авто",
"Downloading": "Завантаження",
"EnableColorImpairedMode": "Увімкнути режим із порушенням кольору",
"Duration": "Тривалість",
"EnableHelpText": "Увімкнути створення файлу метаданих для цього типу метаданих",
"Error": "Помилка",
"ErrorLoadingContents": "Помилка завантаження вмісту",
"ErrorLoadingPreviews": "Помилка завантаження попереднього перегляду",
"ErrorRestoringBackup": "Помилка відновлення резервної копії",
"Events": "Події",
"FailedLoadingSearchResults": "Не вдалося завантажити результати пошуку, повторіть спробу.",
"FailedToLoadMovieFromAPI": "Не вдалося завантажити фільм із API",
"FileDateHelpText": "Змінити дату файлу під час імпорту/повторного сканування",
"Files": "Файли",
"Filter": "Фільтр",
"Folders": "Папки",
"FollowPerson": "Слідкуйте за особою",
"From": "від",
"HideAdvanced": "Сховати додаткові",
"History": "Історія",
"MinimumAge": "Мінімальний вік",
"MinimumAvailability": "Мінімальна доступність",
"MinimumCustomFormatScore": "Мінімальна оцінка спеціального формату",
"MovieDetailsPreviousMovie": "Відомості про фільм: попередній фільм",
"MovieEditor": "Редактор фільмів",
"PtpOldSettingsCheckMessage": "Наведені нижче індексатори PassThePopcorn мають застарілі налаштування та їх потрібно оновити: {0}",
"ProxyPasswordHelpText": "Вам потрібно лише ввести ім’я користувача та пароль, якщо вони потрібні. В іншому випадку залиште їх порожніми.",
"PublishedDate": "Дата публікації",
"QualityDefinitions": "Визначення якості",
"QualityLimitsHelpText": "Обмеження автоматично регулюються для тривалості фільму.",
"RemotePathMappingCheckFilesGenericPermissions": "Завантажте файли звітів клієнта {0} в {1}, але Radarr не бачить цей каталог. Можливо, вам знадобиться налаштувати дозволи для папки.",
"RescanMovieFolderAfterRefresh": "Перескануйте папку фільму після оновлення",
"Reset": "Скинути",
"ResetAPIKey": "Скинути ключ API",
"Restart": "Перезавантажити",
"RestartRadarr": "Перезавантажити Radarr",
"RestartRequiredHelpTextWarning": "Щоб набуло чинності, потрібно перезапустити",
"RestoreBackup": "Відновлення резервної копії",
"Restrictions": "Обмеження",
"RootFolder": "Коренева папка",
"RootFolderCheckMultipleMessage": "Відсутні кілька кореневих папок: {0}",
"Runtime": "Час виконання",
"Search": "Пошук",
"SearchAll": "Пошук у всіх",
"UsenetDelay": "Затримка Usenet",
"Downloaded": "Завантажено",
"Existing": "Існуючий",
"ImportFailed": "Помилка імпорту: {0}",
"Importing": "Імпорт",
"InCinemas": "У кінотеатрах",
"IncludeRadarrRecommendations": "Включити рекомендації Radarr",
"Indexer": "Індексатор",
"Language": "Мова",
"MetadataSettingsSummary": "Створюйте файли метаданих, коли фільми імпортуються або оновлюються",
"Month": "Місяць",
"NoMoveFilesSelf": " Ні, я сам перенесу файли",
"Sunday": "Неділя",
"Tasks": "Задачі",
"Today": "Сьогодні",
"Tomorrow": "Завтра",
"TorrentDelay": "Затримка торрента",
"OnMovieAddedHelpText": "Фільм додано",
"OnMovieAdded": "У фільмі додано",
"OnMovieFileDelete": "Видаленні відеофайлу",
"OnMovieFileDeleteForUpgrade": "Видалити відеофайл для оновлення",
"OnMovieDelete": "Фільм видалено",
"OnRename": "При перейменуванні",
"OnMovieFileDeleteHelpText": "Видаленні відеофайлу",
"OnRenameHelpText": "При перейменуванні",
"OpenThisModal": "Відкрийте цей модальний вікно",
"PageSize": "Розмір сторінки",
"PageSizeHelpText": "Кількість елементів для показу на кожній сторінці",
"Password": "Пароль",
"Path": "Шлях",
"OriginalTitle": "Оригінальна назва",
"Presets": "Предустановки",
"PreviewRename": "Попередній перегляд Перейменування",
"PreviewRenameHelpText": "Порада: щоб переглянути перейменування... виберіть «Скасувати», потім клацніть назву будь-якого фільму та скористайтеся",
"Priority": "Пріоритет",
"Profiles": "Профілі",
"ProcessingFolders": "Обробка папок",
"Proxy": "Проксі",
"ProxyBypassFilterHelpText": "Використовуйте «,» як роздільник і «*». як символ підстановки для субдоменів",
"ProtocolHelpText": "Виберіть протокол(и) для використання та який із них є кращим під час вибору між однаковими випусками",
"ProxyUsernameHelpText": "Вам потрібно лише ввести ім’я користувача та пароль, якщо вони потрібні. В іншому випадку залиште їх порожніми.",
"QualitySettings": "Налаштування якості",
"QualitySettingsSummary": "Якісні розміри та найменування",
"QualityProfiles": "Профілі якості",
"Queue": "Черга",
"Queued": "У черзі",
"RadarrSupportsAnyIndexer": "Radarr підтримує будь-який індексатор, який використовує стандарт Newznab, а також інші індексатори, перелічені нижче.",
"RadarrSupportsAnyDownloadClient": "Radarr підтримує багато популярних торрент-клієнтів і клієнтів для завантаження через Usenet.",
"RadarrSupportsAnyRSSMovieListsAsWellAsTheOneStatedBelow": "Radarr підтримує будь-які списки фільмів RSS, а також наведений нижче.",
"RecentChanges": "Останні зміни",
"Reason": "Причина",
"RecentFolders": "Останні папки",
"RecycleBinCleanupDaysHelpText": "Встановіть значення 0, щоб вимкнути автоматичне очищення",
"RecyclingBinCleanup": "Очищення сміттєвого кошика",
"RefreshInformationAndScanDisk": "Оновити інформацію та сканувати диск",
"RefreshCollections": "Оновити колекції",
"RefreshLists": "Оновити списки",
"RefreshMonitoredIntervalHelpText": "Як часто оновлювати відстежувані завантаження з клієнтів завантаження, мінімум 1 хвилина",
"ReleaseDates": "Дати випуску",
"ReleasedMsg": "Фільм виходить",
"ReleaseGroup": "Група випуску",
"ReleaseRejected": "Реліз відхилено",
"RemoveFailed": "Не вдалося видалити",
"RemoveDownloadsAlert": "Параметри видалення переміщено до окремих налаштувань клієнта завантаження в таблиці вище.",
"RetentionHelpText": "Лише Usenet: встановіть нуль, щоб налаштувати необмежену утримку",
"Seconds": "Секунди",
"Security": "Безпека",
"SelectMovie": "Виберіть Фільм",
"SetPermissionsLinuxHelpTextWarning": "Якщо ви не впевнені, що ці налаштування роблять, не змінюйте їх.",
"SetTags": "Встановити теги",
"SettingsFirstDayOfWeek": "Перший день тижня",
"SettingsRemotePathMappingRemotePathHelpText": "Кореневий шлях до каталогу, до якого має доступ клієнт завантаження",
"SettingsRuntimeFormat": "Формат виконання",
"SettingsWeekColumnHeader": "Заголовок стовпця тижня",
"SettingsShowRelativeDatesHelpText": "Показати відносні (сьогодні/вчора/тощо) або абсолютні дати",
"ShowMovieInformation": "Показати інформацію про фільм",
"ShowMovieInformationHelpText": "Показати жанри фільмів і сертифікати",
"ShownClickToHide": "Показано, натисніть, щоб приховати",
"ShowOverview": "Показати огляд",
"ShowMonitoredHelpText": "Показати відстежуваний статус під плакатом",
"ShowSearchHelpText": "Показувати кнопку пошуку при наведенні",
"Size": "Розмір",
"StartupDirectory": "Каталог запуску",
"ThisConditionMatchesUsingRegularExpressions": "Ця умова відповідає використанню регулярних виразів. Зауважте, що символи {0} мають особливі значення та потребують екранування за допомогою {1}",
"UILanguage": "Мова інтерфейсу користувача",
"UnableToAddANewCustomFormatPleaseTryAgain": "Не вдалося додати новий спеціальний формат, спробуйте ще раз.",
"UnableToAddANewDownloadClientPleaseTryAgain": "Не вдається додати новий клієнт для завантаження, повторіть спробу.",
"UnableToAddANewIndexerPleaseTryAgain": "Не вдалося додати новий індексатор, спробуйте ще раз.",
"UnableToAddANewListExclusionPleaseTryAgain": "Не вдається додати нове виключення зі списку, повторіть спробу.",
"UnableToAddANewListPleaseTryAgain": "Не вдалося додати новий список, спробуйте ще раз.",
"UnableToLoadIndexers": "Не вдалося завантажити індексатори",
"UnableToLoadQualities": "Неможливо завантажити якості",
"UpperCase": "Великі літери",
"TotalMovies": "Всього фільмів",
"TotalSpace": "Загальний простір",
"UISettings": "Налаштування інтерфейсу користувача",
"UILanguageHelpText": "Мова, яку Radarr використовуватиме для інтерфейсу користувача",
"UILanguageHelpTextWarning": "Потрібно перезавантажити браузер",
"UnableToLoadBackups": "Не вдалося завантажити резервні копії",
"UnableToLoadBlocklist": "Не вдалося завантажити список блокувань",
"UnableToLoadCollections": "Не вдалося завантажити колекції",
"UnableToLoadAltTitle": "Не вдалося завантажити альтернативні назви.",
"UnableToLoadNamingSettings": "Не вдалося завантажити налаштування імен",
"UnableToLoadRestrictions": "Не вдалося завантажити обмеження",
"UnableToLoadTheCalendar": "Неможливо завантажити календар",
"UnableToLoadTags": "Не вдалося завантажити теги",
"Unreleased": "Недоступний",
"UnsavedChanges": "Незбережені зміни",
"Indexers": "Індексатори",
"ListTagsHelpText": "Буде додано елементи списку тегів",
"ListUpdateInterval": "Інтервал оновлення списку",
"LoadingMovieCreditsFailed": "Не вдалося завантажити титри фільму",
"MustContain": "Має містити",
"PreferUsenet": "Віддавайте перевагу Usenet",
"SSLCertPathHelpText": "Шлях до файлу pfx",
"StartProcessing": "Почати обробку",
"Waiting": "Очікування",
"WaitingToImport": "Очікування імпорту",
"WaitingToProcess": "Очікування обробки",
"VisitGithubCustomFormatsAphrodite": "Відвідайте вікі для отримання додаткової інформації: ",
"EnableAutoHelpText": "Якщо ввімкнено, фільми автоматично додаватимуться до Radarr із цього списку",
"Enabled": "Увімкнено",
"EnabledHelpText": "Увімкнути цей список для використання в Radarr",
"EnableMediaInfoHelpText": "Отримайте з файлів інформацію про відео, таку як роздільна здатність, час виконання та кодек. Це вимагає, щоб Radarr читав частини файлу, що може спричинити високу дискову або мережеву активність під час сканування.",
"ExtraFileExtensionsHelpTexts1": "Розділений комами список додаткових файлів для імпорту (.nfo буде імпортовано як .nfo-orig)",
"FileNameTokens": "Маркери імен файлів",
"FolderMoveRenameWarning": "Це також перейменує папку з фільмами відповідно до формату папки з фільмами в налаштуваннях.",
"IndexerSearchCheckNoAutomaticMessage": "Немає доступних індексаторів із увімкненим автоматичним пошуком, Radarr не надаватиме автоматичних результатів пошуку",
"HiddenClickToShow": "Приховано, натисніть, щоб показати",
"HomePage": "Домашня сторінка",
"IgnoredHelpText": "Випуск буде відхилено, якщо він містить один або кілька термінів (незалежно від регістру)",
"ImportExtraFilesHelpText": "Імпортуйте відповідні додаткові файли (субтитри, nfo тощо) після імпортування файлу фільму",
"ImportListMissingRoot": "Відсутня коренева папка для списків імпорту: {0}",
"IncludeRecommendationsHelpText": "Включіть рекомендовані Radarr фільми в режим Discovery",
"IndexerLongTermStatusCheckAllClientMessage": "Усі індексатори недоступні через збої більше 6 годин",
"DestinationRelativePath": "Відносний шлях призначення",
"EditCollection": "Редагувати колекцію",
"ForMoreInformationOnTheIndividualDownloadClients": "Щоб отримати додаткові відомості про окремі клієнти для завантаження, натисніть кнопки додаткових відомостей.",
"IndexerTagHelpText": "Використовуйте цей індексатор лише для фільмів із принаймні одним відповідним тегом. Залиште поле порожнім для використання з усіма фільмами.",
"Info": "Інформація",
"InstanceName": "Ім'я екземпляра",
"InstanceNameHelpText": "Ім’я екземпляра на вкладці та ім’я програми Syslog",
"KeepAndUnmonitorMovie": "Зберегти та скасувати моніторинг фільму",
"LaunchBrowserHelpText": " Відкрийте веб-браузер і перейдіть на домашню сторінку Radarr під час запуску програми.",
"ListSyncLevelHelpText": "Фільми з бібліотеки буде видалено або відстежуватись не буде, якщо їх немає у вашому списку",
"LocalPath": "Місцевий шлях",
"LowerCase": "Малі літери",
"MaximumLimits": "Максимальні обмеження",
"MinimumAgeHelpText": "Тільки Usenet: мінімальний вік NZB у хвилинах до їх захоплення. Використовуйте це, щоб дати новим випускам час для поширення до вашого провайдера usenet.",
"MinimumFreeSpace": "Мінімальний вільний простір",
"MinimumLimits": "Мінімальні обмеження",
"MovieCollectionMissingRoot": "Відсутня коренева папка для колекції фільмів: {0}",
"MoveFolders1": "Бажаєте перемістити папки з фільмами до \"{0}\"?",
"MoveFolders2": "Бажаєте перемістити файли фільму з \"{0}\" до \"{1}\"?",
"MovieFolderFormat": "Формат папки фільму",
"MovieIndexScrollTop": "Індекс фільму: прокрутка вгору",
"MovieInfoLanguageHelpText": "Мова, яку Radarr використовуватиме для інформації про фільм в інтерфейсі користувача",
"MovieIsDownloadingInterp": "Фільм завантажується - {0}% {1}",
"NextExecution": "Наступне виконання",
"OnMovieFileDeleteForUpgradeHelpText": "Видалити відеофайл для оновлення",
"OrganizeModalDisabled": "Перейменування вимкнено, перейменовувати нічого",
"OrganizeModalSuccess": "Успіх! Мою роботу виконано, немає файлів для перейменування.",
"OrganizeSelectedMovies": "Упорядкуйте вибрані фільми",
"PendingChangesMessage": "У вас є незбережені зміни. Ви впевнені, що бажаєте залишити цю сторінку?",
"PosterSize": "Розмір плаката",
"PreferredProtocol": "Переважний протокол",
"PriorityHelpText": "Надайте пріоритет кільком клієнтам завантаження. Круговий алгоритм використовується для клієнтів з таким же пріоритетом.",
"ProxyCheckBadRequestMessage": "Не вдалося перевірити проксі. Код стану: {0}",
"ProxyCheckResolveIpMessage": "Не вдалося визначити IP-адресу для налаштованого проксі-сервера {0}",
"QualitiesHelpText": "Якості, які стоять вище в списку, є кращими, навіть якщо не позначено. Якості в одній групі рівні. Потрібні тільки перевірені якості",
"QualityProfileInUse": "Неможливо видалити профіль якості, доданий до фільму, списку або колекції",
"RemotePathMappingCheckBadDockerPath": "Ви використовуєте docker; клієнт завантаження {0} розміщує завантаження в {1}, але це недійсний шлях {2}. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
"RemotePathMappingCheckRemoteDownloadClient": "Клієнт віддаленого завантаження {0} повідомив про файли в {1}, але цей каталог, здається, не існує. Ймовірно, відсутнє відображення віддаленого шляху.",
"RemoveSelected": "Видалити вибране",
"ReplaceIllegalCharactersHelpText": "Замінити недопустимі символи. Якщо не позначено, Radarr видалить їх",
"RescanAfterRefreshHelpTextWarning": "Radarr не визначатиме автоматично зміни файлів, якщо не встановлено значення «Завжди»",
"RestartReloadNote": "Примітка: Radarr автоматично перезапуститься та перезавантажить інтерфейс під час процесу відновлення.",
"RottenTomatoesRating": "Рейтинг томатів",
"RssSyncHelpText": "Інтервал у хвилинах. Встановіть нуль, щоб вимкнути (це зупинить автоматичне захоплення звільнення)",
"ScriptPath": "Шлях сценарію",
"SearchFailedPleaseTryAgainLater": "Помилка пошуку, спробуйте пізніше.",
"SearchOnAddCollectionHelpText": "Шукайте фільми в цій колекції після додавання в бібліотеку",
"SelectReleaseGroup": "Виберіть Release Group",
"SendAnonymousUsageData": "Надсилати анонімні дані про використання",
"SettingsEnableColorImpairedMode": "Увімкнути режим із порушенням кольору",
"SettingsEnableColorImpairedModeHelpText": "Змінений стиль, щоб користувачі з вадами кольору могли краще розрізняти кольорову кодовану інформацію",
"SettingsThemeHelpText": "Змініть тему інтерфейсу додатка, тема «Авто» використовуватиме вашу тему ОС, щоб установити світлий або темний режим. Натхненний Theme.Park",
"SettingsWeekColumnHeaderHelpText": "Відображається над кожним стовпцем, коли тиждень є активним переглядом",
"ShowCertification": "Показати сертифікат",
"ShowCollectionDetails": "Показати статус колекції",
"ShowPath": "Показати шлях",
"ShowQualityProfileHelpText": "Покажіть якісний профіль під плакатом",
"ShowTitleHelpText": "Показати назву фільму під постером",
"SkipFreeSpaceCheck": "Пропустити перевірку вільного місця",
"SkipFreeSpaceCheckWhenImportingHelpText": "Використовуйте, коли Radarr не може виявити вільне місце в кореневій папці фільму",
"SqliteVersionCheckUpgradeRequiredMessage": "Наразі встановлена версія SQLite {0} більше не підтримується. Оновіть SQLite принаймні до версії {1}.",
"TheLogLevelDefault": "Рівень журналу за замовчуванням має значення «Інформація», і його можна змінити",
"ThisCannotBeCancelled": "Це не можна скасувати після запуску без вимкнення всіх ваших індексаторів.",
"TorrentDelayHelpText": "Затримка в хвилинах, щоб зачекати, перш ніж захопити торрент",
"UISettingsSummary": "Параметри календаря, дати та кольору",
"UnableToAddANewNotificationPleaseTryAgain": "Не вдалося додати нове сповіщення, спробуйте ще раз.",
"UnableToAddANewRemotePathMappingPleaseTryAgain": "Не вдалося додати нове зіставлення віддаленого шляху, спробуйте ще раз.",
"UnableToLoadDownloadClientOptions": "Не вдалося завантажити параметри клієнта для завантаження",
"UnableToLoadListExclusions": "Неможливо завантажити список винятків",
"UnableToLoadManualImportItems": "Не вдалося завантажити елементи ручного імпорту",
"UnableToLoadMovies": "Неможливо завантажити фільми",
"UnableToLoadQualityProfiles": "Не вдалося завантажити профілі якості",
"UnableToLoadResultsIntSearch": "Неможливо завантажити результати для цього пошуку фільмів. Спробуйте ще раз пізніше",
"Unmonitored": "Неконтрольований",
"UnmonitoredHelpText": "Включайте неконтрольовані фільми в канал iCal",
"IndexerJackettAll": "Індексатори, які використовують непідтримувану кінцеву точку Jackett 'all': {0}",
"UpdateAutomaticallyHelpText": "Автоматичне завантаження та встановлення оновлень. Ви все ще зможете встановити з System: Updates",
"UpdateCheckStartupNotWritableMessage": "Неможливо встановити оновлення, оскільки папка запуску \"{0}\" не може бути записана користувачем \"{1}\".",
"UpdateCheckUINotWritableMessage": "Неможливо встановити оновлення, оскільки папка інтерфейсу користувача \"{0}\" не може бути записана користувачем \"{1}\".",
"UpgradeUntilCustomFormatScore": "Оновлення до оцінки спеціального формату",
"UpgradeUntilThisQualityIsMetOrExceeded": "Оновлюйте, поки ця якість не буде досягнута або перевищена",
"UsenetDelayHelpText": "Затримка в хвилинах, щоб зачекати, перш ніж отримати випуск від Usenet",
"VersionUpdateText": "Встановлено версію Radarr {0}. Щоб отримати останні зміни, потрібно перезавантажити Radarr.",
"WhitelistedHardcodedSubsHelpText": "Встановлені тут теги субтитрів не вважатимуться жорстко закодованими",
"DockerUpdater": "оновіть контейнер docker, щоб отримати оновлення",
"EnableAutomaticSearch": "Увімкнути автоматичний пошук",
"Images": "Зображення",
"IMDb": "IMDb",
"Import": "Імпорт",
"LastUsed": "Останнє використання",
"Lists": "Списки",
"NoVideoFilesFoundSelectedFolder": "У вибраній папці не знайдено відеофайлів",
"Released": "Випуск",
"SourcePath": "Вихідний шлях",
"UnableToLoadUISettings": "Не вдалося завантажити налаштування інтерфейсу користувача",
"WouldYouLikeToRestoreBackup": "Бажаєте відновити резервну копію {0}?",
"IndexerFlags": "Прапори індексатора",
"OnMovieDeleteHelpText": "Фільм видалено",
"TagDetails": "Деталі тегу - {0}",
"DiscordUrlInSlackNotification": "Ви налаштували сповіщення Discord як сповіщення Slack. Налаштуйте це як сповіщення Discord для кращої роботи. Сповіщення, на які впливає: {0}",
"Missing": "Відсутня",
"SettingsTimeFormat": "Формат часу",
"iCalLink": "Посилання iCal",
"InCinemasDate": "У кінотеатрах Дата",
"IndexerSettings": "Налаштування індексатора",
"List": "Список",
"ListExclusions": "Список виключень",
"ListSettings": "Параметри списку",
"LinkHere": "тут",
"Links": "Посилання",
"ListsSettingsSummary": "Імпорт списків, список виключень",
"OrganizeConfirm": "Ви впевнені, що бажаєте впорядкувати всі файли у {0} вибраних фільмах?",
"OrganizeModalAllPathsRelative": "Усі шляхи відносяться до:",
"Options": "Опції",
"Organize": "Організувати",
"OrganizeAndRename": "Організувати та перейменувати",
"OverviewOptions": "Параметри огляду",
"PackageVersion": "Версія пакета",
"Quality": "Якість",
"Time": "Час",
"UsenetDelayTime": "Затримка Usenet: {0}",
"ChooseImportMode": "Виберіть режим імпорту",
"DeleteHeader": "Видалити - {0}",
"IncludeCustomFormatWhenRenaming": "Включати спеціальний формат під час перейменування",
"IncludeCustomFormatWhenRenamingHelpText": "Включіть у формат перейменування {Custom Formats}",
"MaximumSizeHelpText": "Максимальний розмір випуску для захоплення в МБ. Встановіть нуль, щоб встановити необмежений",
"Ungroup": "Розгрупувати",
"InteractiveImport": "Інтерактивний імпорт",
"Large": "Великий",
"LastDuration": "Остання тривалість",
"Scheduled": "За розкладом",
"UpdateSelected": "Оновити вибране",
"PreferTorrent": "Віддаю перевагу Torrent",
"PrioritySettings": "Пріоритет: {0}",
"ProxyCheckFailedToTestMessage": "Не вдалося перевірити проксі: {0}",
"Qualities": "Якості",
"QualityCutoffHasNotBeenMet": "Порогова якість не досягнута",
"QualityOrLangCutoffHasNotBeenMet": "Обрізання якості чи мови не виконано",
"QualityProfile": "Профіль якості",
"QueueIsEmpty": "Черга порожня",
"QuickImport": "Рухатися автоматично",
"RadarrCalendarFeed": "Стрічка календаря Radarr",
"RadarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow": "Radarr підтримує спеціальні умови щодо наведених нижче властивостей випуску.",
"RadarrTags": "Теги Radar",
"RadarrUpdated": "Radarr оновлено",
"Rating": "Рейтинг",
"Ratings": "Рейтинги",
"Real": "Справжня",
"Refresh": "Оновити",
"RefreshAndScan": "Оновити та сканувати",
"RelativePath": "Відносний шлях",
"SearchForMissing": "Розшук зниклих",
"SearchForMovie": "Пошук фільму",
"SearchMissing": "Пошук відсутній",
"StartImport": "Розпочати імпорт",
"System": "Система",
"AreYouSureYouWantToResetQualityDefinitions": "Ви впевнені, що бажаєте скинути визначення якості?",
"CollectionsSelectedInterp": "Вибрано колекцій: {0}",
"DeleteNotification": "Видалити сповіщення",
"DeleteFileLabel": "Видалити файл фільму {0}",
"DeleteFilesHelpText": "Видалити файли фільмів і папку фільмів",
"DeleteFilesLabel": "Видалити файли фільмів {0}",
"DeleteQualityProfile": "Видалити профіль якості",
"DeleteRestriction": "Видалити обмеження",
"DownloadedAndMonitored": "Завантажено (Відстежується)",
"DownloadClientUnavailable": "Клієнт завантажувача недоступний",
"DownloadedButNotMonitored": "Завантажено (Не відстежується)",
"DownloadFailed": "Помилка завантаження",
"DownloadFailedCheckDownloadClientForMoreDetails": "Помилка завантаження: перевірте клієнт завантаження, щоб дізнатися більше",
"DownloadFailedInterp": "Помилка завантаження: {0}",
"DownloadWarning": "Попередження про завантаження: {0}",
"DownloadWarningCheckDownloadClientForMoreDetails": "Попередження про завантаження: перевірте клієнт завантаження, щоб дізнатися більше",
"Edit": "Редагувати",
"EditGroups": "Редагувати групи",
"EditIndexer": "Редагувати індексатор",
"Edition": "Видання",
"EditMovie": "Редагувати фільм",
"EditMovieFile": "Редагувати файл фільму",
"EditPerson": "Редагувати особу",
"DeleteSelectedMovieFiles": "Видалити вибрані файли фільмів",
"DeleteSelectedMovieFilesMessage": "Ви впевнені, що бажаєте видалити вибрані файли фільмів?",
"DeleteTag": "Видалити тег",
"DeleteTheMovieFolder": "Папку фільму \"{0}\" і весь її вміст буде видалено.",
"DestinationPath": "Шлях призначення",
"DetailedProgressBar": "Детальний індикатор прогресу",
"DetailedProgressBarHelpText": "Показати текст на панелі виконання",
"Details": "Подробиці",
"Donations": "Пожертви",
"EnableCompletedDownloadHandlingHelpText": "Автоматично імпортувати завершені завантаження з клієнта завантажень",
"EnableAutomaticSearchHelpTextWarning": "Буде використано, коли використовується інтерактивний пошук",
"EnableColorImpairedModeHelpText": "Змінений стиль, щоб користувачі з вадами кольору могли краще розрізняти кольорову кодовану інформацію",
"EnableInteractiveSearch": "Увімкнути інтерактивний пошук",
"EnableInteractiveSearchHelpText": "Буде використано, коли використовується інтерактивний пошук",
"EnableInteractiveSearchHelpTextWarning": "Пошук не підтримується цим індексатором",
"EnableRSS": "Увімкнути RSS",
"EnableSSL": "Увімкнути SSL",
"EnableSslHelpText": " Щоб набуло чинності, потрібно перезапустити роботу від імені адміністратора",
"EventType": "Тип події",
"Exception": "Виняток",
"Excluded": "Виключено",
"ExcludeMovie": "Виключити фільм",
"ExcludeTitle": "Виключити {0}? Це не дозволить Radarr автоматично додавати через синхронізацію списку.",
"ExistingMovies": "Існуючі фільми",
"ExistingTag": "Існуючий тег",
"Extension": "Розширення",
"ExternalUpdater": "Radarr налаштовано на використання зовнішнього механізму оновлення",
"ExtraFileExtensionsHelpTexts2": "Приклади: '.sub, .nfo' або 'sub,nfo'",
"Failed": "Не вдалося",
"FailedDownloadHandling": "Помилка обробки завантаження",
"FailedToLoadQueue": "Не вдалося завантажити чергу",
"FeatureRequests": "Майбутні запити",
"FileManagement": "Керування файлами",
"Filename": "Ім'я файлу",
"FileNames": "Імена файлів",
"FileWasDeletedByUpgrade": "Файл видалено, щоб імпортувати оновлення",
"FileWasDeletedByViaUI": "Файл видалено через інтерфейс користувача",
"FilterPlaceHolder": "Пошук фільмів",
"Folder": "Папка",
"Forecast": "Прогноз",
"Formats": "Формати",
"ForMoreInformationOnTheIndividualImportListsClinkOnTheInfoButtons": "Щоб отримати додаткову інформацію про окремі списки імпорту, натисніть інформаційні кнопки.",
"ForMoreInformationOnTheIndividualIndexers": "Щоб отримати додаткову інформацію про окремих індексаторів, натисніть кнопки інформації.",
"FreeSpace": "Вільний простір",
"General": "Загальний",
"Host": "Хост",
"Hostname": "Ім'я хоста",
"Hours": "Години",
"HttpHttps": "HTTP(S)",
"ICalFeed": "Канал iCal",
"ICalHttpUrlHelpText": "Скопіюйте цю URL-адресу до своїх клієнтів або натисніть, щоб підписатися, якщо ваш браузер підтримує веб-канал",
"Ignored": "Ігнорується",
"IgnoredAddresses": "Ігноровані адреси",
"IllRestartLater": "Я перезапущу пізніше",
"ImportExistingMovies": "Імпорт наявних фільмів",
"ImportExtraFiles": "Імпорт додаткових файлів",
"ImportFailedInterp": "Помилка імпорту: {0}",
"ImportHeader": "Імпортуйте існуючу організовану бібліотеку, щоб додати фільми до Radarr",
"ImportIncludeQuality": "Переконайтеся, що назви ваших файлів містять якість. напр. {0}",
"ImportLibrary": "Імпорт бібліотеки",
"ImportListMultipleMissingRoots": "Кілька кореневих папок відсутні для списків імпорту: {0}",
"IndexerLongTermStatusCheckSingleClientMessage": "Індексатори недоступні через збої більше 6 годин: {0}",
"ImdbRating": "Рейтинг IMDb",
"ImdbVotes": "Голоси IMDb",
"IndexerDownloadClientHelpText": "Укажіть, який клієнт завантаження використовується для захоплення з цього індексатора",
"IndexerRssHealthCheckNoAvailableIndexers": "Усі індексатори з підтримкою rss тимчасово недоступні через нещодавні помилки індексатора",
"IndexerPriorityHelpText": "Пріоритет індексатора від 1 (найвищий) до 50 (найнижчий). За замовчуванням: 25. Використовується під час захоплення випусків як тай-брейку для інших однакових випусків, Radarr все одно використовуватиме всі ввімкнені індексатори для RSS-синхронізації та пошуку",
"IndexerRssHealthCheckNoIndexers": "Немає доступних індексаторів із увімкненою синхронізацією RSS, Radarr не збиратиме нові випуски автоматично",
"IndexerSearchCheckNoInteractiveMessage": "Немає доступних індексаторів, коли ввімкнено інтерактивний пошук, Radarr не надаватиме жодних інтерактивних результатів пошуку",
"IndexerStatusCheckAllClientMessage": "Усі індексатори недоступні через збої",
"IndexerStatusCheckSingleClientMessage": "Індексатори недоступні через помилки: {0}",
"InteractiveImportErrLanguage": "Для кожного вибраного файлу необхідно вибрати мову",
"InteractiveImportErrMovie": "Фільм потрібно вибрати для кожного вибраного файлу",
"InteractiveImportErrQuality": "Для кожного вибраного файлу необхідно вибрати якість",
"InteractiveSearch": "Інтерактивний пошук",
"Interval": "Інтервал",
"InvalidFormat": "Недійсний формат",
"KeyboardShortcuts": "Гарячі клавіши",
"LanguageHelpText": "Мова для релізів",
"Languages": "Мови",
"LastExecution": "Останнє виконання",
"LastWriteTime": "Час останнього запису",
"Level": "Рівень",
"ListSyncLevelHelpTextWarning": "Файли фільмів буде остаточно видалено, це може призвести до видалення вашої бібліотеки, якщо ваші списки порожні",
"LoadingMovieExtraFilesFailed": "Не вдалося завантажити додаткові файли фільму",
"LoadingMovieFilesFailed": "Не вдалося завантажити файли фільму",
"Local": "Місцевий",
"Location": "Місцезнаходження",
"LogFiles": "Файли журналів",
"LogOnly": "Лише журнал",
"Logs": "Журнали",
"Manual": "Інструкція",
"ManualImport": "Імпорт вручну",
"ManualImportSelectLanguage": "Імпорт вручну - виберіть мову",
"ManualImportSelectMovie": "Імпорт вручну - виберіть фільм",
"ManualImportSelectQuality": " Імпорт вручну - Виберіть якість",
"ManualImportSetReleaseGroup": "Імпорт вручну - встановити групу випуску",
"MappedDrivesRunningAsService": "Підключені мережеві диски недоступні під час роботи як служби Windows. Щоб отримати додаткову інформацію, перегляньте FAQ",
"MarkAsFailed": "Позначити як помилку",
"MarkAsFailedMessageText": "Ви впевнені, що бажаєте позначити \"{0}\" як невдале?",
"MassMovieSearch": "Масовий пошук фільмів",
"Max": "Максимальний",
"MaximumSize": "Максимальний розмір",
"Mechanism": "Механізм",
"MediaInfo": "Медіа інформація",
"MediaManagementSettingsSummary": "Налаштування іменування та керування файлами",
"Medium": "Середній",
"MIA": "MIA",
"MinAvailability": "Мінімальна доступність",
"MinFormatScoreHelpText": "Мінімальна оцінка спеціального формату, дозволена для завантаження",
"MinimumFreeSpaceWhenImportingHelpText": "Заборонити імпорт, якщо він залишить менше, ніж цей обсяг доступного дискового простору",
"Minutes": "Хвилин",
"MinutesHundredTwenty": "120 хвилин: {0}",
"MinutesNinety": "90 хвилин: {0}",
"MinutesSixty": "60 хвилин: {0}",
"MissingFromDisk": "Radarr не зміг знайти файл на диску, тому файл було від’єднано від фільму в базі даних",
"MissingMonitoredAndConsideredAvailable": "Відсутній (відстежується)",
"Mode": "Режим",
"Months": "Місяці",
"More": "Більше",
"MoveFiles": "Перемістити файли",
"MovieChat": "Кіночат",
"MovieCollectionMultipleMissingRoots": "Кілька кореневих папок відсутні для колекцій фільмів: {0}",
"MovieDetailsNextMovie": "Подробиці про фільм: наступний фільм",
"MovieAlreadyExcluded": "Фільм уже виключено",
"MovieAndCollection": "Фільм і колекція",
"MovieExcludedFromAutomaticAdd": "Фільм виключено з автоматичного додавання",
"MovieFiles": "Файли фільмів",
"MovieFilesTotaling": "Підсумок файлів фільмів",
"MovieID": "ID фільму",
"MovieIndex": "Індекс фільму",
"MovieIndexScrollBottom": "Індекс фільму: прокрутка внизу",
"MovieInfoLanguage": "Мова інформації про фільм",
"MovieInfoLanguageHelpTextWarning": "Потрібно перезавантажити браузер",
"MovieInvalidFormat": "Фільм: недійсний формат",
"MovieIsDownloading": "Фільм завантажується",
"MovieIsMonitored": "Фільм відстежується",
"MovieIsUnmonitored": "Фільм без моніторингу",
"MovieNaming": "Назва фільму",
"MovieOnly": "Тільки кіно",
"MoviesSelectedInterp": "Вибрано фільмів: {0}",
"MovieTitle": "Назва фільму",
"MustNotContain": "Не повинен містити",
"Name": "Ім'я",
"NamingSettings": "Налаштування імен",
"Negate": "Заперечувати",
"Negated": "Заперечено",
"NoLogFiles": "Немає файлів журналу",
"NoMatchFound": "Збігів не знайдено!",
"NoResultsFound": "Нічого не знайдено",
"NoTagsHaveBeenAddedYet": "Теги ще не додано",
"NotAvailable": "Недоступний",
"NotificationTriggers": "Тригери сповіщень",
"NotificationTriggersHelpText": "Виберіть, які події мають викликати це сповіщення",
"NotMonitored": "Не контролюється",
"OAuthPopupMessage": "Ваш браузер блокує спливаючі вікна",
"Ok": "Гаразд",
"OnApplicationUpdateHelpText": "Оновлення програми",
"OnDownloadHelpText": "При імпорті",
"OnHealthIssueHelpText": "Про питання здоров'я",
"OnImport": "При імпорті",
"OnLatestVersion": "Остання версія Radarr вже встановлена",
"OnlyTorrent": "Тільки торрент",
"OnlyUsenet": "Тільки Usenet",
"OnUpgrade": "При оновленні",
"OnUpgradeHelpText": "При оновленні",
"OpenBrowserOnStart": "Відкрийте браузер при запуску",
"OrganizeModalNamingPattern": "Шаблон іменування:",
"Original": "Оригінал",
"OriginalLanguage": "Мова оригіналу",
"Paused": "Призупинено",
"Pending": "В очікуванні",
"PendingChangesDiscardChanges": "Відкинути зміни та залишити",
"PendingChangesStayReview": "Залишайтеся та переглядайте зміни",
"Permissions": "Дозволи",
"PhysicalRelease": "Фізичний реліз",
"PhysicalReleaseDate": "Дата фізичного випуску",
"Port": "Порт",
"PortNumber": "Номер порту",
"PosterOptions": "Параметри плаката",
"Posters": "Плакати",
"PreferAndUpgrade": "Віддайте перевагу та оновіть",
"PreferIndexerFlags": "Віддавати перевагу прапорцям індексатора",
"PreferIndexerFlagsHelpText": "Пріоритезуйте випуски за допомогою спеціальних прапорців",
"Preferred": "Бажано",
"PreferredSize": "Бажаний розмір",
"ProfilesSettingsSummary": "Профілі якості, мови та затримки",
"Progress": "Прогрес",
"Proper": "Належний",
"Protocol": "Протокол",
"ReadTheWikiForMoreInformation": "Читайте Wiki для отримання додаткової інформації",
"RecycleBinCleanupDaysHelpTextWarning": "Файли в кошику, старші за вибрану кількість днів, будуть очищені автоматично",
"RecycleBinHelpText": "Файли фільмів потраплять сюди після видалення, а не назавжди",
"Redownload": "Повторне завантаження",
"RejectionCount": "Кількість відмов",
"ReleaseBranchCheckOfficialBranchMessage": "Гілка {0} не є дійсною гілкою випуску Radarr, ви не отримуватимете оновлення",
"ReleaseStatus": "Статус випуску",
"ReleaseTitle": "Назва випуску",
"ReleaseWillBeProcessedInterp": "Випуск буде оброблено {0}",
"Reload": "Перезавантажити",
"RemotePath": "Віддалений шлях",
"RemotePathMappingCheckDockerFolderMissing": "Ви використовуєте docker; клієнт завантаження {0} розміщує завантаження в {1}, але цей каталог не існує всередині контейнера. Перегляньте свої віддалені відображення шляхів і налаштування обсягу контейнера.",
"RemotePathMappingCheckFileRemoved": "Файл {0} видалено під час обробки.",
"RemotePathMappingCheckFilesBadDockerPath": "Ви використовуєте docker; завантажити клієнтські {0} звітні файли в {1}, але це недійсний {2} шлях. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
"RemotePathMappingCheckFilesLocalWrongOSPath": "Локальний клієнт завантаження {0} повідомив про файли в {1}, але це недійсний шлях {2}. Перегляньте налаштування клієнта завантаження.",
"RemotePathMappingCheckFilesWrongOSPath": "Клієнт віддаленого завантаження {0} повідомив про файли в {1}, але це недійсний шлях {2}. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
"RemotePathMappingCheckFolderPermissions": "Radarr може бачити, але не має доступу до каталогу завантажень {0}. Ймовірна помилка дозволів.",
"RemotePathMappingCheckGenericPermissions": "Клієнт завантаження {0} розміщує завантаження в {1}, але Radarr не бачить цей каталог. Можливо, вам знадобиться налаштувати дозволи для папки.",
"RemotePathMappingCheckImportFailed": "Radarr не вдалося імпортувати фільм. Подробиці перевірте у своїх журналах.",
"RemotePathMappingCheckLocalFolderMissing": "Клієнт віддаленого завантаження {0} розміщує завантаження в {1}, але цей каталог не існує. Ймовірно, віддалений шлях відсутній або неправильний.",
"RemotePathMappingCheckLocalWrongOSPath": "Локальний клієнт завантаження {0} розміщує завантаження в {1}, але це недійсний шлях {2}. Перегляньте налаштування клієнта завантаження.",
"RemotePathMappings": "Віддалені відображення шляхів",
"RemoveCompleted": "Видалення завершено",
"RemoveCompletedDownloadsHelpText": "Видалити імпортовані завантаження з історії клієнта завантажень",
"RemovedFromTaskQueue": "Видалено з черги завдань",
"RemovedMovieCheckMultipleMessage": "Фільми {0} видалено з TMDb",
"RemovedMovieCheckSingleMessage": "Фільм {0} видалено з TMDb",
"RemoveFromDownloadClient": "Видалити з клієнта завантаження",
"Reorder": "Змінити порядок",
"Replace": "Замінити",
"ReplaceIllegalCharacters": "Замінити неприпустимі символи",
"ReplaceWithDash": "Замінити тире",
"ReplaceWithSpaceDash": "Замінити пробілом",
"RescanAfterRefreshHelpText": "Перескануйте папку фільму після оновлення фільму",
"ResetDefinitions": "Скинути визначення",
"ResetQualityDefinitions": "Скинути визначення якості",
"ResetTitles": "Скинути заголовки",
"ResetTitlesHelpText": "Скинути заголовки визначень, а також значення",
"RestartNow": "Перезавантажити зараз",
"Restore": "Відновлення",
"RootFolderCheckSingleMessage": "Відсутня коренева папка: {0}",
"RootFolders": "Кореневі папки",
"RSSIsNotSupportedWithThisIndexer": "Цей індексатор не підтримує RSS",
"RSSSync": "Синхронізація RSS",
"RSSSyncInterval": "Інтервал синхронізації RSS",
"RSSSyncIntervalHelpTextWarning": "Це стосується всіх індексаторів, будь ласка, дотримуйтеся встановлених ними правил",
"Save": "Зберегти",
"SaveChanges": "Зберегти зміни",
"SaveSettings": "Зберегти зміни",
"Score": "Оцінка",
"Script": "Сценарій",
"ScrollMovies": "Прокрутка фільмів",
"SearchCutoffUnmet": "Обрізка пошуку не виконана",
"SearchFiltered": "Пошук відфільтровано",
"SearchMovie": "Пошук фільму",
"SearchOnAdd": "Шукати при додаванні",
"SearchOnAddHelpText": "Шукайте фільми в цьому списку після додавання в бібліотеку",
"SearchSelected": "Пошук вибрано",
"SelectAll": "Вибрати все",
"SelectDotDot": "Виберіть...",
"SelectFolder": "Виберіть папку",
"SelectLanguages": "Виберіть Мови",
"SelectQuality": "Виберіть Якість",
"SetPermissions": "Встановити дозволи",
"SetPermissionsLinuxHelpText": "Чи слід запускати chmod, коли файли імпортуються/перейменовуються?",
"SetReleaseGroup": "Встановити групу випуску",
"SettingsLongDateFormat": "Довгий формат дати",
"SettingsRemotePathMappingLocalPathHelpText": "Шлях, який має використовувати Radarr для локального доступу до віддаленого шляху",
"SettingsRemotePathMappingRemotePath": "Віддалений шлях",
"SettingsShortDateFormat": "Короткий формат дати",
"SettingsShowRelativeDates": "Показати відносні дати",
"SettingsTheme": "Тема",
"ShouldMonitorHelpText": "Чи слід додавати фільми чи колекції, додані до цього списку, як контрольовані",
"ShowAdvanced": "Показати Додатково",
"ShowAsAllDayEvents": "Показати як події на весь день",
"ShowCinemaRelease": "Показати дату виходу в кіно",
"showCinemaReleaseHelpText": "Покажіть дату виходу в кіно під афішею",
"ShowCutoffUnmetIconHelpText": "Показувати піктограму для файлів, коли обмеження не досягнуто",
"ShowDateAdded": "Показати дату додавання",
"ShowMonitored": "Показати Моніторинг",
"ShowPosters": "Показати плакати",
"ShowRatings": "Показати рейтинги",
"ShowReleaseDate": "Показати дату випуску",
"ShowReleaseDateHelpText": "Показати дату випуску під плакатом",
"ShowSearch": "Показати пошук",
"ShowUnknownMovieItems": "Показати невідомі елементи фільму",
"ShowYear": "Показати рік",
"Shutdown": "Вимкнення",
"SizeLimit": "Обмеження розміру",
"SizeOnDisk": "Розмір на диску",
"Small": "Маленький",
"SorryThatMovieCannotBeFound": "Вибачте, цей фільм неможливо знайти.",
"Sort": "Сортування",
"Source": "Джерело",
"SourceRelativePath": "Відносний шлях джерела",
"SourceTitle": "Назва джерела",
"SSLCertPasswordHelpText": "Пароль для файлу pfx",
"SSLPort": "Порт SSL",
"StandardMovieFormat": "Стандартний формат фільму",
"Started": "Розпочато",
"StartSearchForMissingMovie": "Почніть пошук зниклого фільму",
"StartTypingOrSelectAPathBelow": "Почніть вводити текст або виберіть шлях нижче",
"Status": "Статус",
"Studio": "Студія",
"Style": "Стиль",
"SubfolderWillBeCreatedAutomaticallyInterp": "Вкладена папка \"{0}\" буде створена автоматично",
"SuggestTranslationChange": "Запропонуйте зміну перекладу",
"SystemTimeCheckMessage": "Системний час вимкнено більш ніж на 1 день. Заплановані завдання можуть не працювати належним чином, доки час не буде виправлено",
"Table": "Таблиця",
"TableOptions": "Параметри таблиці",
"TableOptionsColumnsMessage": "Виберіть, які стовпці відображаються та в якому порядку вони відображаються",
"TagCannotBeDeletedWhileInUse": "Неможливо видалити під час використання",
"TagIsNotUsedAndCanBeDeleted": "Тег не використовується і може бути видалений",
"Tags": "Теги",
"TagsHelpText": "Застосовується до фільмів із принаймні одним відповідним тегом",
"TagsSettingsSummary": "Перегляньте всі теги та те, як вони використовуються. Невикористані теги можна видалити",
"TestAll": "Перевірити все",
"TestAllClients": "Перевірте всіх клієнтів",
"TestAllIndexers": "Перевірити всі індексатори",
"TestAllLists": "Перевірити всі списки",
"TimeFormat": "Формат часу",
"Timeleft": "Час залишився",
"Title": "Назва",
"Titles": "Назви",
"TmdbIdHelpText": "Ідентифікатор TMDb фільму, який потрібно виключити",
"TmdbRating": "Рейтинг TMDb",
"TmdbVotes": "Голоси TMDb",
"TorrentDelayTime": "Затримка торрента: {0}",
"Torrents": "Торренти",
"Trace": "Трасувати",
"Trailer": "Трейлер",
"Trigger": "Тригер",
"Type": "Тип",
"UnableToAddANewConditionPleaseTryAgain": "Не вдалося додати нову умову, спробуйте ще раз.",
"UnableToAddANewQualityProfilePleaseTryAgain": "Не вдалося додати новий профіль якості, спробуйте ще раз.",
"UnableToAddRootFolder": "Не вдалося додати кореневу папку",
"UnableToImportCheckLogs": "Завантажено неможливо імпортувати: подробиці перевірте в журналах",
"UnableToLoadDelayProfiles": "Неможливо завантажити профілі затримки",
"UnableToLoadGeneralSettings": "Не вдалося завантажити загальні налаштування",
"UnableToLoadHistory": "Не вдалося завантажити історію",
"UnableToLoadIndexerOptions": "Не вдалося завантажити параметри індексатора",
"UnableToLoadLanguages": "Не вдалося завантажити мови",
"UnableToLoadListOptions": "Не вдалося завантажити параметри списку",
"UnableToLoadLists": "Не вдалося завантажити списки",
"UnableToLoadMediaManagementSettings": "Не вдалося завантажити налаштування керування медіафайлами",
"UnableToLoadMetadata": "Не вдалося завантажити метадані",
"UnableToLoadNotifications": "Не вдалося завантажити сповіщення",
"UnableToLoadQualityDefinitions": "Не вдалося завантажити визначення якості",
"UnableToLoadRemotePathMappings": "Неможливо завантажити віддалені відображення шляхів",
"UnableToLoadRootFolders": "Не вдалося завантажити кореневі папки",
"UnableToUpdateRadarrDirectly": "Неможливо оновити Radarr безпосередньо,",
"Unavailable": "Недоступний",
"Unlimited": "Необмежений",
"UnmappedFilesOnly": "Лише незіставлені файли",
"UnmappedFolders": "Невідповідні папки",
"UnselectAll": "Скасувати вибір усіх",
"UpdateAll": "Оновити все",
"UpdateCheckStartupTranslocationMessage": "Неможливо встановити оновлення, оскільки папка запуску \"{0}\" знаходиться в папці переміщення програми.",
"UpdateMechanismHelpText": "Використовуйте вбудований засіб оновлення Radarr або скрипт",
"UpdateScriptPathHelpText": "Шлях до спеціального сценарію, який приймає витягнутий пакет оновлення та обробляє решту процесу оновлення",
"UpgradeAllowedHelpText": "Якщо відключені якості не будуть покращені",
"UpgradesAllowed": "Якщо відключені якості не будуть покращені",
"UpgradeUntilQuality": "Оновлення до якості",
"Uptime": "Час роботи",
"URLBase": "URL-адреса",
"UrlBaseHelpText": "Для підтримки зворотного проксі-сервера значення за умовчанням порожнє",
"UseHardlinksInsteadOfCopy": "Використовуйте жорсткі посилання замість копіювати",
"UsenetDisabled": "Usenet вимкнено",
"UseProxy": "Використовуйте проксі",
"Username": "Ім'я користувача",
"Version": "Версія",
"VideoCodec": "Відеокодек",
"View": "Переглянути",
"Wanted": "Розшукується",
"Warn": "Попередити",
"Week": "Тиждень",
"WeekColumnHeader": "Заголовок стовпця тижня",
"Weeks": "Тижнів",
"WhatsNew": "Що нового?",
"WhitelistedSubtitleTags": "Теги субтитрів із білого списку",
"Year": "Рік",
"YesCancel": "Так, скасувати",
"YesMoveFiles": "Так, перемістити файли",
"Yesterday": "Вчора",
"ApplicationURL": "URL програми",
"ApplicationUrlHelpText": "Зовнішня URL-адреса цієї програми, включаючи http(s)://, порт і базу URL-адрес",
"ImportRootPath": "Наведіть Radarr на папку з усіма вашими фільмами, а не на певний фільм. напр. {0}, а не {1}. Крім того, кожен фільм має бути у власній папці в кореневій папці/теці бібліотеки.",
"ImportTipsMessage": "Кілька порад, щоб забезпечити безперешкодне імпортування:",
"InCinemasMsg": "Фільм у кінотеатрах",
"IncludeHealthWarningsHelpText": "Включайте попередження про здоров’я",
"IncludeUnknownMovieItemsHelpText": "Показати елементи без фільму в черзі. Це може включати видалені фільми чи будь-що інше в категорії Radarr",
"IndexerPriority": "Пріоритет індексатора",
"ImportListStatusCheckAllClientMessage": "Усі списки недоступні через помилки",
"ImportListStatusCheckSingleClientMessage": "Списки недоступні через помилки: {0}",
"ImportListSyncIntervalHelpText": "Як часто Radarr синхронізує ваші списки. Мінімальне значення 6 годин",
"ImportMechanismHealthCheckMessage": "Увімкнути обробку завершених завантажень",
"ImportMovies": "Імпорт фільмів",
"ImportNotForDownloads": "Не використовуйте для імпортування завантажень із вашого клієнта завантаження, це лише для існуючих упорядкованих бібліотек, а не для несортованих файлів.",
"IndexerSearchCheckNoAvailableIndexersMessage": "Усі індексатори з можливістю пошуку тимчасово недоступні через нещодавні помилки індексатора",
"InstallLatest": "Встановити останній",
"MegabytesPerMinute": "Мегабайт за хвилину",
"Message": "Повідомлення",
"MovieTitleHelpText": "Назва фільму, який потрібно виключити (може бути будь-яким значущим)",
"MovieYear": "Фільм рік",
"MovieYearHelpText": "Рік фільму виключити",
"NegateHelpText": "Якщо позначено, настроюваний формат не застосовуватиметься, якщо ця умова {0} збігається.",
"Never": "Ніколи",
"New": "Новий",
"NoAltTitle": "Без альтернативних назв.",
"NoBackupsAreAvailable": "Немає резервних копій",
"NoChange": "Без змін",
"NoChanges": "Жодних змін",
"NoLinks": "Немає посилань",
"NoListRecommendations": "Елементів списку чи рекомендацій не знайдено. Щоб почати, ви захочете додати новий фільм, імпортувати кілька наявних або додати список.",
"NoMinimumForAnyRuntime": "Немає мінімуму для будь-якого часу виконання",
"NoMoviesExist": "Фільми не знайдено. Щоб почати, ви захочете додати новий фільм або імпортувати кілька наявних.",
"NoUpdatesAreAvailable": "Немає оновлень",
"Test": "Тест",
"TorrentsDisabled": "Торренти вимкнено",
"TotalFileSize": "Загальний розмір файлу",
"DownloadClientCheckDownloadingToRoot": "Клієнт завантаження {0} розміщує завантаження в кореневій папці {1}. Ви не повинні завантажувати в кореневу папку.",
"DigitalRelease": "Цифровий випуск",
"Disabled": "Вимкнено",
"DeleteEmptyFoldersHelpText": "Видаляти порожні папки фільмів під час сканування диска та під час видалення файлів фільмів",
"DeleteFile": "Видалити файл",
"Discord": "Discord",
"Docker": "Docker",
"DownloadClientCheckNoneAvailableMessage": "Немає доступного клієнта для завантаження",
"DownloadClientCheckUnableToCommunicateMessage": "Неможливо зв'язатися з {0}.",
"DiskSpace": "Дисковий простір",
"DoneEditingGroups": "Редагування груп завершено",
"DoNotPrefer": "Не віддавати перевагу",
"DoNotUpgradeAutomatically": "Не оновлювати автоматично",
"Download": "Завантажити",
"DownloadClient": "Клієнт завантажувача",
"DownloadClients": "Клієнти завантажувачів",
"DownloadClientSettings": "Налаштування клієнта завантажувача",
"DownloadClientsSettingsSummary": "Клієнти завантаження, обробка завантажень і віддалені відображення шляхів",
"DownloadClientStatusCheckAllClientMessage": "Усі клієнти завантаження недоступні через збої",
"DownloadClientStatusCheckSingleClientMessage": "Завантаження клієнтів недоступне через помилки: {0}",
"Genres": "Жанри",
"Global": "Глобальний",
"GoToInterp": "Перейти до {0}",
"Group": "Група",
"HardlinkCopyFiles": "Жорстке посилання/Копіювати файли",
"HaveNotAddedMovies": "Ви ще не додали жодного фільму. Бажаєте спершу імпортувати деякі або всі свої фільми?",
"Health": "Здоров'я",
"HealthNoIssues": "Немає проблем із вашою конфігурацією",
"GeneralSettings": "Загальні налаштування",
"GeneralSettingsSummary": "Порт, SSL, ім’я користувача/пароль, проксі, аналітика та оновлення",
"MoreInfo": "Більше інформації",
"RefreshMovie": "Оновити фільм",
"RegularExpressionsCanBeTested": "Регулярні вирази можна перевірити ",
"RemoveSelectedItems": "Видалити вибрані елементи",
"RemovingTag": "Видалення мітки",
"Renamed": "Перейменовано",
"RenameFiles": "Перейменування файлів",
"RenameMovies": "Перейменувати фільми",
"RenameMoviesHelpText": "Radarr використовуватиме існуючу назву файлу, якщо перейменування вимкнено",
"RemoveFailedDownloadsHelpText": "Видаліть невдалі завантаження з історії завантажень клієнта",
"RemoveFilter": "Видалити фільтр",
"RemoveFromBlocklist": "Видалити зі списку блокувань",
"RemoveFromQueue": "Видалити з черги",
"RemoveHelpTextWarning": "Видалення видалить завантаження та файл(и) із клієнта завантаження.",
"RemoveMovieAndDeleteFiles": "Видалити фільм і видалити файли",
"RemoveMovieAndKeepFiles": "Видалити фільм і зберегти файли",
"RemoveRootFolder": "Видалити кореневу папку",
"RemoveSelectedItem": "Видалити вибраний елемент",
"SettingsRemotePathMappingHostHelpText": "Той самий хост, який ви вказали для віддаленого клієнта завантаження",
"SettingsRemotePathMappingLocalPath": "Місцевий шлях",
"ShowGenres": "Показати жанри",
"UpdateAvailable": "Доступне нове оновлення",
"Updates": "Оновлення",
"Monitor": "Контрольований",
"MonitorCollection": "Контрольовані Колекції",
"Monitored": "Відстежується",
"MonitoredCollectionHelpText": "Відстежуйте, щоб фільми з цієї колекції автоматично додавалися до бібліотеки",
"MonitoredOnly": "Тільки під контролем",
"MonitoredStatus": "Відстежується/Стан",
"MoreControlCFText": "Хочете більше контролювати, яким завантаженням віддавати перевагу? Додайте",
"DownloadPropersAndRepacks": "Пропери та Репаки",
"LookingForReleaseProfiles2": "замість цього.",
"Usenet": "Usenet",
"Grab": "Захопити",
"IncludeUnmonitored": "Включити неконтрольований",
"Letterboxd": "Letterboxd",
"Reddit": "Reddit",
"Socks5": "Socks5 (Підтримка TOR)",
"Socks4": "Socks4",
"SSLCertPath": "Шлях сертифіката SSL",
"Wiki": "Wiki",
"RSSHelpText": "Використовуватиметься, коли Radarr періодично шукатиме випуски через RSS Sync",
"DownloadPropersAndRepacksHelpText1": "Автоматичне оновлення до Propers/Repacks чи ні",
"DownloadPropersAndRepacksHelpText2": "Використовуйте «Не віддавати перевагу», щоб відсортувати за користувальницьким форматом оцінки над Propers/Repacks",
"DownloadPropersAndRepacksHelpTextWarning": "Використовуйте спеціальні формати для автоматичного оновлення до Propers/Repacks",
"IconForCutoffUnmet": "Значок \"Не виконано відсікання\"",
"Logging": "Журналування",
"LookingForReleaseProfiles1": "Шукайте профілі релізів? Спробуйте",
"MaintenanceRelease": "Випуск для обслуговування: виправлення помилок та інші покращення. Щоб отримати докладнішу інформацію, перегляньте історію фіксації Github",
"MediaManagement": "Управління медіа",
"MediaManagementSettings": "Налаштування Управління медіа",
"MetadataSettings": "Налаштування метаданих",
"Min": "Мінімум",
"MissingNotMonitored": "Відсутній (неконтрольований)",
"MonitorMovie": "Відстежувати фільм",
"MonitorMovies": "Відстежувати фільми",
"NetCore": ".NET",
"OnGrabHelpText": "При захопленні",
"Peers": "Піри",
"RSS": "RSS",
"Seeders": "Сиди",
"SSLCertPassword": "Пароль SSL сертифіката",
"TaskUserAgentTooltip": "Агент користувача, наданий програмою, яка викликала API",
"TMDb": "TMDb",
"TMDBId": "Ідентифікатор TMDb",
"Trakt": "Trakt",
"UI": "Інтерфейс користувача",
"Metadata": "Метадані",
"OnGrab": "При захопленні",
"Discover": "Відкрийте для себе",
"Grabbed": "Захоплений",
"GrabID": "Захопити ID",
"GrabRelease": "Захопити реліз",
"GrabReleaseMessageText": "Radarr не зміг визначити, для якого фільму цей випуск. Можливо, Radarr не зможе автоматично імпортувати цей випуск. Ви хочете взяти '{0}'?",
"GrabSelected": "Захопити вибране"
}

View File

@@ -636,7 +636,7 @@
"InstallLatest": "安装最新版",
"IndexerStatusCheckSingleClientMessage": "搜刮器因错误不可用:{0}",
"IndexerStatusCheckAllClientMessage": "所有搜刮器都因错误不可用",
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索因此Radarr 不会提供任何手动搜索的结果",
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,因此 Radarr 不会提供任何手动搜索的结果",
"IndexerRssHealthCheckNoIndexers": "没有任何索引器开启了RSS同步Radarr不会自动抓取新发布的影片",
"IndexerLongTermStatusCheckSingleClientMessage": "由于故障6小时下列索引器都已不可用{0}",
"IndexerLongTermStatusCheckAllClientMessage": "由于故障超过6小时所有索引器均不可用",
@@ -1016,7 +1016,7 @@
"ChmodGroupHelpText": "组名称或GID。对于远程文件系统请使用GID。",
"MinAvailability": "最小可用性",
"RequiredHelpText": "此{0}条件必须匹配才能应用自定义格式。否则,单个{1}匹配就足够了。",
"QualitiesHelpText": "列表中较高的质量更可取。同组内的质量相同。只需要检查质量",
"QualitiesHelpText": "即使未选中,列表中的质量排序越高优先级也越高。同组内的质量优先级相同。质量只有选中才标记为需要",
"DownloadPropersAndRepacksHelpText2": "使用“不喜欢”按Propers / Repacks中的自定义格式分数排序",
"LookingForReleaseProfiles1": "寻找发行档案?尝试",
"QualityLimitsHelpText": "影片运行时会自动调整限制。",
@@ -1072,7 +1072,7 @@
"IndexerTagHelpText": "仅对至少有一个匹配标记的电影使用此索引器。留空则适用于所有电影。",
"RemotePathMappingCheckFileRemoved": "文件{0} 在处理的过程中被部分删除。",
"RemotePathMappingCheckFilesGenericPermissions": "下载{1}中客户端{0}报告的文件但Radarr无法看到此目录。您可能需要调整文件夹的权限。",
"RemotePathMappingCheckGenericPermissions": "下载客户端{0}将下载放置在{1}中但Radarr无法看到此目录。您可能需要调整文件夹的权限。",
"RemotePathMappingCheckGenericPermissions": "下载客户端{0}将下载放置在{1}中,但 Radarr 无法看到此目录。您可能需要调整文件夹的权限。",
"UpdateAvailable": "有新的更新可用",
"Letterboxd": "Letterboxd",
"RemoveSelectedItem": "删除所选项目",
@@ -1143,8 +1143,16 @@
"OnMovieAdded": "电影添加时",
"OnMovieAddedHelpText": "电影添加时",
"TotalMovies": "电影总数",
"ApplicationUrlHelpText": "此应用的外部URL包含 http(s)://端口和基本URL",
"ApplicationUrlHelpText": "此应用的外部URL包含 http(s)://端口和基本URL",
"ApplicationURL": "程序URL",
"BindAddressHelpText": "有效的 IPv4 地址或以'*'代表所有接口",
"PreferredProtocol": "首选协议"
"BindAddressHelpText": "有效的 IP 地址localhost或以'*'代表所有地址",
"PreferredProtocol": "首选协议",
"AreYouSureYouWantToResetQualityDefinitions": "确定要重置质量定义吗?",
"SettingsThemeHelpText": "更改程序界面主题“自动”主题将根据您的操作系统主题来设置浅色或深色模式。灵感来自Theme.Park",
"ResetDefinitions": "重置定义",
"ResetQualityDefinitions": "重置质量定义",
"ResetTitles": "重置标题",
"ResetTitlesHelpText": "重置定义标题与参数值",
"SettingsTheme": "主题",
"RSSHelpText": "当Radarr定期通过RSS同步查找发布时使用"
}

View File

@@ -34,8 +34,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Specifications
if (qualityCompare < 0)
{
_logger.Debug("This file isn't a quality upgrade for movie. Skipping {0}", localMovie.Path);
return Decision.Reject("Not a quality upgrade for existing movie file(s)");
_logger.Debug("This file isn't a quality upgrade for movie. New Quality is {0}. Skipping {1}", localMovie.Quality.Quality, localMovie.Path);
return Decision.Reject("Not an upgrade for existing movie file. New Quality is {0}", localMovie.Quality.Quality);
}
}

View File

@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Messaging.Commands
public virtual bool RequiresDiskAccess => false;
public virtual bool IsExclusive => false;
public virtual bool IsTypeExclusive => false;
public virtual bool IsLongRunning => false;
public string Name { get; private set; }
public DateTime? LastExecutionTime { get; set; }

View File

@@ -176,6 +176,11 @@ namespace NzbDrone.Core.Messaging.Commands
queuedCommands = queuedCommands.Where(c => !exclusiveTypes.Any(x => x == c.Body.Name));
}
if (startedCommands.Any(x => x.Body.IsLongRunning))
{
queuedCommands = queuedCommands.Where(c => !c.Body.IsExclusive);
}
var localItem = queuedCommands.OrderByDescending(c => c.Priority)
.ThenBy(c => c.QueuedAt)
.FirstOrDefault();

View File

@@ -140,7 +140,7 @@ namespace NzbDrone.Core.Movies
AddMethod = AddMovieMethod.Collection
},
Monitored = true
}).ToList());
}).ToList(), true);
}
}
}

View File

@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentValidation.Results;
using NLog;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Movies;
namespace NzbDrone.Core.Notifications.Gotify
@@ -20,39 +23,39 @@ namespace NzbDrone.Core.Notifications.Gotify
public override string Name => "Gotify";
public override string Link => "https://gotify.net/";
public override void OnGrab(GrabMessage grabMessage)
public override void OnGrab(GrabMessage message)
{
_proxy.SendNotification(MOVIE_GRABBED_TITLE, grabMessage.Message, Settings);
SendNotification(MOVIE_GRABBED_TITLE, message.Message, message.Movie);
}
public override void OnDownload(DownloadMessage message)
{
_proxy.SendNotification(MOVIE_DOWNLOADED_TITLE, message.Message, Settings);
SendNotification(MOVIE_DOWNLOADED_TITLE, message.Message, message.Movie);
}
public override void OnMovieAdded(Movie movie)
{
_proxy.SendNotification(MOVIE_ADDED_TITLE, $"{movie.Title} added to library", Settings);
SendNotification(MOVIE_ADDED_TITLE, $"{movie.Title} added to library", movie);
}
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
{
_proxy.SendNotification(MOVIE_FILE_DELETED_TITLE, deleteMessage.Message, Settings);
SendNotification(MOVIE_FILE_DELETED_TITLE, deleteMessage.Message, deleteMessage.Movie);
}
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
{
_proxy.SendNotification(MOVIE_DELETED_TITLE, deleteMessage.Message, Settings);
SendNotification(MOVIE_DELETED_TITLE, deleteMessage.Message, deleteMessage.Movie);
}
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
{
_proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings);
SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, null);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
public override void OnApplicationUpdate(ApplicationUpdateMessage message)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings);
SendNotification(APPLICATION_UPDATE_TITLE, message.Message, null);
}
public override ValidationResult Test()
@@ -61,10 +64,29 @@ namespace NzbDrone.Core.Notifications.Gotify
try
{
var isMarkdown = false;
const string title = "Test Notification";
const string body = "This is a test message from Radarr";
_proxy.SendNotification(title, body, Settings);
var sb = new StringBuilder();
sb.AppendLine("This is a test message from Radarr");
if (Settings.IncludeMoviePoster)
{
isMarkdown = true;
sb.AppendLine("\r![](https://raw.githubusercontent.com/Radarr/Radarr/develop/Logo/128.png)");
}
var payload = new GotifyMessage
{
Title = title,
Message = sb.ToString(),
Priority = Settings.Priority
};
payload.SetContentType(isMarkdown);
_proxy.SendNotification(payload, Settings);
}
catch (Exception ex)
{
@@ -74,5 +96,35 @@ namespace NzbDrone.Core.Notifications.Gotify
return new ValidationResult(failures);
}
private void SendNotification(string title, string message, Movie movie)
{
var isMarkdown = false;
var sb = new StringBuilder();
sb.AppendLine(message);
if (Settings.IncludeMoviePoster && movie != null)
{
var poster = movie.MovieMetadata.Value.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Poster)?.Url;
if (poster != null)
{
isMarkdown = true;
sb.AppendLine($"\r![]({poster})");
}
}
var payload = new GotifyMessage
{
Title = title,
Message = sb.ToString(),
Priority = Settings.Priority
};
payload.SetContentType(isMarkdown);
_proxy.SendNotification(payload, Settings);
}
}
}

View File

@@ -0,0 +1,40 @@
using Newtonsoft.Json;
namespace NzbDrone.Core.Notifications.Gotify
{
public class GotifyMessage
{
public string Title { get; set; }
public string Message { get; set; }
public int Priority { get; set; }
public GotifyExtras Extras { get; set; }
public GotifyMessage()
{
Extras = new GotifyExtras();
}
public void SetContentType(bool isMarkdown)
{
var contentType = isMarkdown ? "text/markdown" : "text/plain";
Extras.ClientDisplay = new GotifyClientDisplay(contentType);
}
}
public class GotifyExtras
{
[JsonProperty("client::display")]
public GotifyClientDisplay ClientDisplay { get; set; }
}
public class GotifyClientDisplay
{
public string ContentType { get; set; }
public GotifyClientDisplay(string contentType)
{
ContentType = contentType;
}
}
}

View File

@@ -1,11 +1,12 @@
using System.Net;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Notifications.Gotify
{
public interface IGotifyProxy
{
void SendNotification(string title, string message, GotifySettings settings);
void SendNotification(GotifyMessage payload, GotifySettings settings);
}
public class GotifyProxy : IGotifyProxy
@@ -17,16 +18,20 @@ namespace NzbDrone.Core.Notifications.Gotify
_httpClient = httpClient;
}
public void SendNotification(string title, string message, GotifySettings settings)
public void SendNotification(GotifyMessage payload, GotifySettings settings)
{
try
{
var request = new HttpRequestBuilder(settings.Server).Resource("message").Post()
.AddQueryParam("token", settings.AppToken)
.AddFormParameter("title", title)
.AddFormParameter("message", message)
.AddFormParameter("priority", settings.Priority)
.Build();
var request = new HttpRequestBuilder(settings.Server)
.Resource("message")
.Post()
.AddQueryParam("token", settings.AppToken)
.Build();
request.Headers.ContentType = "application/json";
var json = payload.ToJson();
request.SetContent(payload.ToJson());
_httpClient.Execute(request);
}

View File

@@ -32,6 +32,9 @@ namespace NzbDrone.Core.Notifications.Gotify
[FieldDefinition(2, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(GotifyPriority), HelpText = "Priority of the notification")]
public int Priority { get; set; }
[FieldDefinition(3, Label = "Include Movie Poster", Type = FieldType.Checkbox, HelpText = "Include movie poster in message")]
public bool IncludeMoviePoster { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Notifications.Emby
}
public override string Link => "https://emby.media/";
public override string Name => "Emby";
public override string Name => "Emby / Jellyfin";
public override void OnGrab(GrabMessage grabMessage)
{

View File

@@ -1,5 +1,6 @@
using FluentValidation;
using Newtonsoft.Json;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -43,7 +44,7 @@ namespace NzbDrone.Core.Notifications.Emby
public bool UpdateLibrary { get; set; }
[JsonIgnore]
public string Address => $"{Host}:{Port}";
public string Address => $"{Host.ToUrlHost()}:{Port}";
public bool IsValid => !string.IsNullOrWhiteSpace(Host) && Port > 0;

View File

@@ -70,9 +70,9 @@ namespace NzbDrone.Core.Notifications.Ntfy
{
try
{
const string title = "Radarr - Test Notification";
const string title = "Sonarr - Test Notification";
const string body = "This is a test message from Radarr";
const string body = "This is a test message from Sonarr";
SendNotification(title, body, settings);
}

View File

@@ -163,7 +163,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server
{
var scheme = settings.UseSsl ? "https" : "http";
var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host}:{settings.Port}")
var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host.ToUrlHost()}:{settings.Port}")
.Accept(HttpAccept.Json)
.AddQueryParam("X-Plex-Client-Identifier", _configService.PlexClientIdentifier)
.AddQueryParam("X-Plex-Product", BuildInfo.AppName)

View File

@@ -1,22 +1,28 @@
using System;
using System.Collections.Generic;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Notifications.Trakt.Resource;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Trakt
{
public class Trakt : NotificationBase<TraktSettings>
{
private readonly ITraktService _traktService;
private readonly ITraktProxy _proxy;
private readonly INotificationRepository _notificationRepository;
private readonly Logger _logger;
public Trakt(ITraktService traktService, INotificationRepository notificationRepository, Logger logger)
public Trakt(ITraktProxy proxy, INotificationRepository notificationRepository, Logger logger)
{
_traktService = traktService;
_proxy = proxy;
_notificationRepository = notificationRepository;
_logger = logger;
}
@@ -26,27 +32,53 @@ namespace NzbDrone.Core.Notifications.Trakt
public override void OnDownload(DownloadMessage message)
{
_traktService.AddMovieToCollection(Settings, message.Movie, message.MovieFile);
RefreshTokenIfNecessary();
AddMovieToCollection(Settings, message.Movie, message.MovieFile);
}
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
{
_traktService.RemoveMovieFromCollection(Settings, deleteMessage.Movie);
RefreshTokenIfNecessary();
RemoveMovieFromCollection(Settings, deleteMessage.Movie);
}
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
{
if (deleteMessage.DeletedFiles)
{
_traktService.RemoveMovieFromCollection(Settings, deleteMessage.Movie);
}
RefreshTokenIfNecessary();
RemoveMovieFromCollection(Settings, deleteMessage.Movie);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_traktService.Test(Settings));
RefreshTokenIfNecessary();
try
{
_proxy.GetUserName(Settings.AccessToken);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.Error(ex, "Access Token is invalid: " + ex.Message);
failures.Add(new ValidationFailure("Token", "Access Token is invalid"));
}
else
{
_logger.Error(ex, "Unable to send test message: " + ex.Message);
failures.Add(new ValidationFailure("Token", "Unable to send test message"));
}
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message: " + ex.Message);
failures.Add(new ValidationFailure("", "Unable to send test message"));
}
return new ValidationResult(failures);
}
@@ -55,7 +87,7 @@ namespace NzbDrone.Core.Notifications.Trakt
{
if (action == "startOAuth")
{
var request = _traktService.GetOAuthRequest(query["callbackUrl"]);
var request = _proxy.GetOAuthRequest(query["callbackUrl"]);
return new
{
@@ -69,14 +101,22 @@ namespace NzbDrone.Core.Notifications.Trakt
accessToken = query["access_token"],
expires = DateTime.UtcNow.AddSeconds(int.Parse(query["expires_in"])),
refreshToken = query["refresh_token"],
authUser = _traktService.GetUserName(query["access_token"])
authUser = _proxy.GetUserName(query["access_token"])
};
}
return new { };
}
public void RefreshToken()
private void RefreshTokenIfNecessary()
{
if (Settings.Expires < DateTime.UtcNow.AddMinutes(5))
{
RefreshToken();
}
}
private void RefreshToken()
{
_logger.Trace("Refreshing Token");
@@ -84,11 +124,12 @@ namespace NzbDrone.Core.Notifications.Trakt
try
{
var response = _traktService.RefreshAuthToken(Settings.RefreshToken);
var response = _proxy.RefreshAuthToken(Settings.RefreshToken);
if (response != null)
{
var token = response;
Settings.AccessToken = token.AccessToken;
Settings.Expires = DateTime.UtcNow.AddSeconds(token.ExpiresIn);
Settings.RefreshToken = token.RefreshToken ?? Settings.RefreshToken;
@@ -99,10 +140,193 @@ namespace NzbDrone.Core.Notifications.Trakt
}
}
}
catch (HttpException)
catch (HttpException ex)
{
_logger.Warn($"Error refreshing trakt access token");
_logger.Warn(ex, "Error refreshing trakt access token");
}
}
private void AddMovieToCollection(TraktSettings settings, Movie movie, MovieFile movieFile)
{
var payload = new TraktCollectMoviesResource
{
Movies = new List<TraktCollectMovie>()
};
var traktResolution = MapResolution(movieFile.Quality.Quality.Resolution, movieFile.MediaInfo?.ScanType);
var mediaType = MapMediaType(movieFile.Quality.Quality.Source);
var audio = MapAudio(movieFile);
var audioChannels = MapAudioChannels(movieFile);
payload.Movies.Add(new TraktCollectMovie
{
Title = movie.Title,
Year = movie.Year,
CollectedAt = DateTime.Now,
Resolution = traktResolution,
MediaType = mediaType,
AudioChannels = audioChannels,
Audio = audio,
Ids = new TraktMovieIdsResource
{
Tmdb = movie.MovieMetadata.Value.TmdbId,
Imdb = movie.MovieMetadata.Value.ImdbId ?? "",
}
});
_proxy.AddToCollection(payload, settings.AccessToken);
}
private void RemoveMovieFromCollection(TraktSettings settings, Movie movie)
{
var payload = new TraktCollectMoviesResource
{
Movies = new List<TraktCollectMovie>()
};
payload.Movies.Add(new TraktCollectMovie
{
Title = movie.Title,
Year = movie.Year,
Ids = new TraktMovieIdsResource
{
Tmdb = movie.MovieMetadata.Value.TmdbId,
Imdb = movie.MovieMetadata.Value.ImdbId ?? "",
}
});
_proxy.RemoveFromCollection(payload, settings.AccessToken);
}
private string MapMediaType(Source source)
{
var traktSource = string.Empty;
switch (source)
{
case Source.BLURAY:
traktSource = "bluray";
break;
case Source.WEBDL:
traktSource = "digital";
break;
case Source.WEBRIP:
traktSource = "digital";
break;
case Source.DVD:
traktSource = "dvd";
break;
case Source.TV:
traktSource = "dvd";
break;
}
return traktSource;
}
private string MapResolution(int resolution, string scanType)
{
var traktResolution = string.Empty;
var scanIdentifier = scanType.IsNotNullOrWhiteSpace() && TraktInterlacedTypes.interlacedTypes.Contains(scanType) ? "i" : "p";
switch (resolution)
{
case 2160:
traktResolution = "uhd_4k";
break;
case 1080:
traktResolution = $"hd_1080{scanIdentifier}";
break;
case 720:
traktResolution = "hd_720p";
break;
case 576:
traktResolution = $"sd_576{scanIdentifier}";
break;
case 480:
traktResolution = $"sd_480{scanIdentifier}";
break;
}
return traktResolution;
}
private string MapAudio(MovieFile movieFile)
{
var traktAudioFormat = string.Empty;
var audioCodec = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, movieFile.SceneName) : string.Empty;
switch (audioCodec)
{
case "AC3":
traktAudioFormat = "dolby_digital";
break;
case "EAC3":
traktAudioFormat = "dolby_digital_plus";
break;
case "TrueHD":
traktAudioFormat = "dolby_truehd";
break;
case "EAC3 Atmos":
traktAudioFormat = "dolby_digital_plus_atmos";
break;
case "TrueHD Atmos":
traktAudioFormat = "dolby_atmos";
break;
case "DTS":
case "DTS-ES":
traktAudioFormat = "dts";
break;
case "DTS-HD MA":
traktAudioFormat = "dts_ma";
break;
case "DTS-HD HRA":
traktAudioFormat = "dts_hr";
break;
case "DTS-X":
traktAudioFormat = "dts_x";
break;
case "MP3":
traktAudioFormat = "mp3";
break;
case "MP2":
traktAudioFormat = "mp2";
break;
case "Vorbis":
traktAudioFormat = "ogg";
break;
case "WMA":
traktAudioFormat = "wma";
break;
case "AAC":
traktAudioFormat = "aac";
break;
case "PCM":
traktAudioFormat = "lpcm";
break;
case "FLAC":
traktAudioFormat = "flac";
break;
case "Opus":
traktAudioFormat = "ogg_opus";
break;
}
return traktAudioFormat;
}
private string MapAudioChannels(MovieFile movieFile)
{
var audioChannels = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo).ToString("0.0") : string.Empty;
if (audioChannels == "0.0")
{
audioChannels = string.Empty;
}
return audioChannels;
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Notifications.Trakt
{
public static class TraktInterlacedTypes
{
private static HashSet<string> _interlacedTypes;
static TraktInterlacedTypes()
{
_interlacedTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Interlaced", "MBAFF", "PAFF"
};
}
public static HashSet<string> interlacedTypes => _interlacedTypes;
}
}

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Notifications.Trakt
TraktAuthRefreshResource RefreshAuthToken(string refreshToken);
void AddToCollection(TraktCollectMoviesResource payload, string accessToken);
void RemoveFromCollection(TraktCollectMoviesResource payload, string accessToken);
HttpRequest BuildTraktRequest(string resource, HttpMethod method, string accessToken);
HttpRequest BuildRequest(string resource, HttpMethod method, string accessToken);
}
public class TraktProxy : ITraktProxy
@@ -36,59 +36,30 @@ namespace NzbDrone.Core.Notifications.Trakt
public void AddToCollection(TraktCollectMoviesResource payload, string accessToken)
{
var request = BuildTraktRequest("sync/collection", HttpMethod.Post, accessToken);
var request = BuildRequest("sync/collection", HttpMethod.Post, accessToken);
request.Headers.ContentType = "application/json";
request.SetContent(payload.ToJson());
try
{
_httpClient.Execute(request);
}
catch (HttpException ex)
{
_logger.Error(ex, "Unable to post payload {0}", payload);
throw new TraktException("Unable to post payload", ex);
}
MakeRequest(request);
}
public void RemoveFromCollection(TraktCollectMoviesResource payload, string accessToken)
{
var request = BuildTraktRequest("sync/collection/remove", HttpMethod.Post, accessToken);
var request = BuildRequest("sync/collection/remove", HttpMethod.Post, accessToken);
request.Headers.ContentType = "application/json";
request.SetContent(payload.ToJson());
try
{
_httpClient.Execute(request);
}
catch (HttpException ex)
{
_logger.Error(ex, "Unable to post payload {0}", payload);
throw new TraktException("Unable to post payload", ex);
}
MakeRequest(request);
}
public string GetUserName(string accessToken)
{
var request = BuildTraktRequest("users/settings", HttpMethod.Get, accessToken);
var request = BuildRequest("users/settings", HttpMethod.Get, accessToken);
var response = _httpClient.Get<TraktUserSettingsResource>(request);
try
{
var response = _httpClient.Get<TraktUserSettingsResource>(request);
if (response != null && response.Resource != null)
{
return response.Resource.User.Ids.Slug;
}
}
catch (HttpException)
{
_logger.Warn($"Error refreshing trakt access token");
}
return null;
return response?.Resource?.User?.Ids?.Slug;
}
public HttpRequest GetOAuthRequest(string callbackUrl)
@@ -110,7 +81,7 @@ namespace NzbDrone.Core.Notifications.Trakt
return _httpClient.Get<TraktAuthRefreshResource>(request)?.Resource ?? null;
}
public HttpRequest BuildTraktRequest(string resource, HttpMethod method, string accessToken)
public HttpRequest BuildRequest(string resource, HttpMethod method, string accessToken)
{
var request = new HttpRequestBuilder(URL).Resource(resource).Build();
@@ -127,5 +98,17 @@ namespace NzbDrone.Core.Notifications.Trakt
return request;
}
private void MakeRequest(HttpRequest request)
{
try
{
_httpClient.Execute(request);
}
catch (HttpException ex)
{
throw new TraktException("Unable to send payload", ex);
}
}
}
}

View File

@@ -1,263 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Notifications.Trakt.Resource;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Notifications.Trakt
{
public interface ITraktService
{
HttpRequest GetOAuthRequest(string callbackUrl);
TraktAuthRefreshResource RefreshAuthToken(string refreshToken);
void AddMovieToCollection(TraktSettings settings, Movie movie, MovieFile movieFile);
void RemoveMovieFromCollection(TraktSettings settings, Movie movie);
string GetUserName(string accessToken);
ValidationFailure Test(TraktSettings settings);
}
public class TraktService : ITraktService
{
private readonly ITraktProxy _proxy;
private readonly Logger _logger;
public TraktService(ITraktProxy proxy,
Logger logger)
{
_proxy = proxy;
_logger = logger;
}
public string GetUserName(string accessToken)
{
return _proxy.GetUserName(accessToken);
}
public HttpRequest GetOAuthRequest(string callbackUrl)
{
return _proxy.GetOAuthRequest(callbackUrl);
}
public TraktAuthRefreshResource RefreshAuthToken(string refreshToken)
{
return _proxy.RefreshAuthToken(refreshToken);
}
public ValidationFailure Test(TraktSettings settings)
{
try
{
GetUserName(settings.AccessToken);
return null;
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.Error(ex, "Access Token is invalid: " + ex.Message);
return new ValidationFailure("Token", "Access Token is invalid");
}
_logger.Error(ex, "Unable to send test message: " + ex.Message);
return new ValidationFailure("Token", "Unable to send test message");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message: " + ex.Message);
return new ValidationFailure("", "Unable to send test message");
}
}
public void RemoveMovieFromCollection(TraktSettings settings, Movie movie)
{
var payload = new TraktCollectMoviesResource
{
Movies = new List<TraktCollectMovie>()
};
payload.Movies.Add(new TraktCollectMovie
{
Title = movie.Title,
Year = movie.Year,
Ids = new TraktMovieIdsResource
{
Tmdb = movie.MovieMetadata.Value.TmdbId,
Imdb = movie.MovieMetadata.Value.ImdbId ?? "",
}
});
_proxy.RemoveFromCollection(payload, settings.AccessToken);
}
public void AddMovieToCollection(TraktSettings settings, Movie movie, MovieFile movieFile)
{
var payload = new TraktCollectMoviesResource
{
Movies = new List<TraktCollectMovie>()
};
var traktResolution = MapResolution(movieFile.Quality.Quality.Resolution, movieFile.MediaInfo?.ScanType);
var mediaType = MapMediaType(movieFile.Quality.Quality.Source);
var audio = MapAudio(movieFile);
var audioChannels = MapAudioChannels(movieFile, audio);
payload.Movies.Add(new TraktCollectMovie
{
Title = movie.Title,
Year = movie.Year,
CollectedAt = DateTime.Now,
Resolution = traktResolution,
MediaType = mediaType,
AudioChannels = audioChannels,
Audio = audio,
Ids = new TraktMovieIdsResource
{
Tmdb = movie.MovieMetadata.Value.TmdbId,
Imdb = movie.MovieMetadata.Value.ImdbId ?? "",
}
});
_proxy.AddToCollection(payload, settings.AccessToken);
}
private string MapMediaType(Source source)
{
var traktSource = string.Empty;
switch (source)
{
case Source.BLURAY:
traktSource = "bluray";
break;
case Source.WEBDL:
traktSource = "digital";
break;
case Source.WEBRIP:
traktSource = "digital";
break;
case Source.DVD:
traktSource = "dvd";
break;
case Source.TV:
traktSource = "dvd";
break;
}
return traktSource;
}
private string MapResolution(int resolution, string scanType)
{
var traktResolution = string.Empty;
var interlacedTypes = new string[] { "Interlaced", "MBAFF", "PAFF" };
var scanIdentifier = scanType.IsNotNullOrWhiteSpace() && interlacedTypes.Contains(scanType) ? "i" : "p";
switch (resolution)
{
case 2160:
traktResolution = "uhd_4k";
break;
case 1080:
traktResolution = string.Format("hd_1080{0}", scanIdentifier);
break;
case 720:
traktResolution = "hd_720p";
break;
case 576:
traktResolution = string.Format("sd_576{0}", scanIdentifier);
break;
case 480:
traktResolution = string.Format("sd_480{0}", scanIdentifier);
break;
}
return traktResolution;
}
private string MapAudio(MovieFile movieFile)
{
var traktAudioFormat = string.Empty;
var audioCodec = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, movieFile.SceneName) : string.Empty;
switch (audioCodec)
{
case "AC3":
traktAudioFormat = "dolby_digital";
break;
case "EAC3":
traktAudioFormat = "dolby_digital_plus";
break;
case "TrueHD":
traktAudioFormat = "dolby_truehd";
break;
case "EAC3 Atmos":
traktAudioFormat = "dolby_digital_plus_atmos";
break;
case "TrueHD Atmos":
traktAudioFormat = "dolby_atmos";
break;
case "DTS":
case "DTS-ES":
traktAudioFormat = "dts";
break;
case "DTS-HD MA":
traktAudioFormat = "dts_ma";
break;
case "DTS-HD HRA":
traktAudioFormat = "dts_hr";
break;
case "DTS-X":
traktAudioFormat = "dts_x";
break;
case "MP3":
traktAudioFormat = "mp3";
break;
case "MP2":
traktAudioFormat = "mp2";
break;
case "Vorbis":
traktAudioFormat = "ogg";
break;
case "WMA":
traktAudioFormat = "wma";
break;
case "AAC":
traktAudioFormat = "aac";
break;
case "PCM":
traktAudioFormat = "lpcm";
break;
case "FLAC":
traktAudioFormat = "flac";
break;
case "Opus":
traktAudioFormat = "ogg_opus";
break;
}
return traktAudioFormat;
}
private string MapAudioChannels(MovieFile movieFile, string audioFormat)
{
var audioChannels = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo).ToString("0.0") : string.Empty;
if (audioChannels == "0.0")
{
audioChannels = string.Empty;
}
return audioChannels;
}
}
}

View File

@@ -21,7 +21,11 @@ namespace NzbDrone.Core.Notifications.Webhook
IndexerFlags = movieFile.IndexerFlags.ToString();
Size = movieFile.Size;
DateAdded = movieFile.DateAdded;
MediaInfo = new WebhookMovieFileMediaInfo(movieFile);
if (movieFile.MediaInfo != null)
{
MediaInfo = new WebhookMovieFileMediaInfo(movieFile);
}
}
public int Id { get; set; }

View File

@@ -86,7 +86,6 @@ namespace NzbDrone.Core.Notifications.Xbmc
if (moviePath != null)
{
moviePath = new OsPath(moviePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
_logger.Debug("Updating movie {0} (Path: {1}) on XBMC host: {2}", movie, moviePath, settings.Address);
}
else

View File

@@ -1,6 +1,7 @@
using System.ComponentModel;
using FluentValidation;
using Newtonsoft.Json;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -58,7 +59,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
public bool AlwaysUpdate { get; set; }
[JsonIgnore]
public string Address => string.Format("{0}:{1}", Host, Port);
public string Address => $"{Host.ToUrlHost()}:{Port}";
public NzbDroneValidationResult Validate()
{

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Extensions.FileSystemGlobbing;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
@@ -197,31 +198,34 @@ namespace NzbDrone.Core.Parser
}
// Case sensitive
var caseSensitiveMatch = CaseSensitiveLanguageRegex.Match(title);
var caseSensitiveMatchs = CaseSensitiveLanguageRegex.Matches(title);
if (caseSensitiveMatch.Groups["lithuanian"].Captures.Cast<Capture>().Any())
foreach (Match match in caseSensitiveMatchs)
{
languages.Add(Language.Lithuanian);
}
if (match.Groups["lithuanian"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Lithuanian);
}
if (caseSensitiveMatch.Groups["czech"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Czech);
}
if (match.Groups["czech"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Czech);
}
if (caseSensitiveMatch.Groups["polish"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Polish);
}
if (match.Groups["polish"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Polish);
}
if (caseSensitiveMatch.Groups["bulgarian"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Bulgarian);
}
if (match.Groups["bulgarian"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Bulgarian);
}
if (caseSensitiveMatch.Groups["slovak"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Slovak);
if (match.Groups["slovak"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Slovak);
}
}
var matches = LanguageRegex.Matches(title);

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Parser
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(Parser));
private static readonly Regex EditionRegex = new Regex(@"\(?\b(?<edition>(((Recut.|Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Extended|Despecialized|(Special|Rouge|Final|Assembly|Imperial|Diamond|Signature|Hunter|Rekall)(?=(.(Cut|Edition|Version)))|\d{2,3}(th)?.Anniversary)(?:.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit|Restored|((2|3|4)in1))))))\b\)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex EditionRegex = new Regex(@"\(?\b(?<edition>(((Recut.|Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Extended|Despecialized|(Special|Rouge|Final|Assembly|Imperial|Diamond|Signature|Hunter|Rekall)(?=(.(Cut|Edition|Version)))|\d{2,3}(th)?.Anniversary)(?:.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|Open.?Matte|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|Open?.Matte|IMAX|Fan.?Edit|Restored|((2|3|4)in1))))))\b\)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ReportEditionRegex = new Regex(@"^.+?" + EditionRegex, RegexOptions.Compiled | RegexOptions.IgnoreCase);

View File

@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex RealRegex = new Regex(@"\b(?<real>REAL)\b",
RegexOptions.Compiled);
private static readonly Regex ResolutionRegex = new Regex(@"\b(?:(?<R360p>360p)|(?<R480p>480p|640x480|848x480)|(?<R540p>540p)|(?<R576p>576p)|(?<R720p>720p|1280x720|960p)|(?<R1080p>1080p|1920x1080|1440p|FHD|1080i|4kto1080p)|(?<R2160p>2160p|3840x2160|4k[-_. ](?:UHD|HEVC|BD|H265)|(?:UHD|HEVC|BD|H265)[-_. ]4k))\b",
private static readonly Regex ResolutionRegex = new Regex(@"\b(?:(?<R360p>360p)|(?<R480p>480p|640x480|848x480)|(?<R540p>540p)|(?<R576p>576p)|(?<R720p>720p|1280x720|960p)|(?<R1080p>1080p|1920x1080|1440p|FHD|1080i|4kto1080p)|(?<R2160p>2160p|3840x2160|4k[-_. ](?:UHD|HEVC|BD|H\.?265)|(?:UHD|HEVC|BD|H\.?265)[-_. ]4k))\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
// Handle cases where no resolution is in the release name; assume if UHD then 4k
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex RemuxRegex = new Regex(@"(?:[_. \[]|\d{4}p-)(?<remux>(?:(BD|UHD)[-_. ]?)?Remux)\b|(?<remux>(?:(BD|UHD)[-_. ]?)?Remux[_. ]\d{4}p)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b(?<hcsub>(\w+(?<!(SOFT|HORRIBLE))SUBS?)\b)|(?<hc>(HC|SUBBED))\b",
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b((?<hcsub>(\w+(?<!SOFT|HORRIBLE)SUBS?))|(?<hc>(HC|SUBBED)))\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
public static QualityModel ParseQuality(string name)

View File

@@ -8,7 +8,7 @@
<PackageReference Include="MailKit" Version="2.15.0" />
<PackageReference Include="Npgsql" Version="6.0.3" />
<PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" />
<PackageReference Include="Servarr.FFprobe" Version="5.0.1.93" />
<PackageReference Include="Servarr.FFprobe" Version="5.1.2.106" />
<PackageReference Include="System.Memory" Version="4.5.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
@@ -18,10 +18,10 @@
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
<PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="MonoTorrent" Version="2.0.5" />
<PackageReference Include="MonoTorrent" Version="2.0.7" />
<PackageReference Include="System.Text.Json" Version="6.0.5" />
</ItemGroup>
<ItemGroup>

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