mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-16 21:15:33 -04:00
Compare commits
21 Commits
v5.23.3.99
...
v5.25.0.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afbe0ebcd4 | ||
|
|
bfbb7532a2 | ||
|
|
c92d8c08f1 | ||
|
|
358ce92f85 | ||
|
|
3ec5a4b78a | ||
|
|
cb59ce891a | ||
|
|
4d3d46d796 | ||
|
|
0941e51d27 | ||
|
|
ff393a3f65 | ||
|
|
f5faf52469 | ||
|
|
b5b4d4b971 | ||
|
|
873299701b | ||
|
|
d14cca30d7 | ||
|
|
5af61b5900 | ||
|
|
a10759c7e9 | ||
|
|
ac2d92007e | ||
|
|
09cfdc3fa2 | ||
|
|
04f26dbff7 | ||
|
|
159f5df8cc | ||
|
|
b823ad8e65 | ||
|
|
cc8bffc272 |
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '5.23.3'
|
||||
majorVersion: '5.25.0'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
|
||||
@@ -9,12 +9,13 @@ $hoverScale: 1.05;
|
||||
box-shadow: 0 0 10px var(--black);
|
||||
transition: all 200ms ease-in;
|
||||
|
||||
.poster {
|
||||
.poster,
|
||||
.overlayTitle {
|
||||
opacity: 0.5;
|
||||
transition: opacity 100ms linear 100ms;
|
||||
}
|
||||
|
||||
.overlayTitle {
|
||||
.overlayHoverTitle {
|
||||
opacity: 1;
|
||||
transition: opacity 100ms linear 100ms;
|
||||
}
|
||||
@@ -31,7 +32,22 @@ $hoverScale: 1.05;
|
||||
background-color: var(--defaultColor);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
.overlayTitle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--offWhite);
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.overlayHover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -42,10 +58,10 @@ $hoverScale: 1.05;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.overlayTitle {
|
||||
.overlayHoverTitle {
|
||||
padding: 5px;
|
||||
color: var(--offWhite);
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
opacity: 0;
|
||||
|
||||
@@ -10,7 +10,8 @@ interface CssExports {
|
||||
'externalLinks': string;
|
||||
'link': string;
|
||||
'monitorToggleButton': string;
|
||||
'overlay': string;
|
||||
'overlayHover': string;
|
||||
'overlayHoverTitle': string;
|
||||
'overlayTitle': string;
|
||||
'poster': string;
|
||||
'posterContainer': string;
|
||||
|
||||
@@ -82,6 +82,7 @@ class CollectionMovie extends Component {
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
hasPosterError,
|
||||
isEditMovieModalOpen,
|
||||
isNewAddMovieModalOpen
|
||||
} = this.state;
|
||||
@@ -134,26 +135,31 @@ class CollectionMovie extends Component {
|
||||
onLoad={this.onPosterLoad}
|
||||
/>
|
||||
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.overlayTitle}>
|
||||
{
|
||||
hasPosterError &&
|
||||
<div className={styles.overlayTitle}>
|
||||
{title}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={styles.overlayHover}>
|
||||
<div className={styles.overlayHoverTitle}>
|
||||
{title} {year > 0 ? `(${year})` : ''}
|
||||
</div>
|
||||
|
||||
{
|
||||
id ?
|
||||
<div className={styles.overlayStatus}>
|
||||
<MovieIndexProgressBar
|
||||
movieId={id}
|
||||
movieFile={movieFile}
|
||||
monitored={monitored}
|
||||
hasFile={hasFile}
|
||||
status={status}
|
||||
bottomRadius={true}
|
||||
width={posterWidth}
|
||||
detailedProgressBar={detailedProgressBar}
|
||||
isAvailable={isAvailable}
|
||||
/>
|
||||
</div> :
|
||||
<MovieIndexProgressBar
|
||||
movieId={id}
|
||||
movieFile={movieFile}
|
||||
monitored={monitored}
|
||||
hasFile={hasFile}
|
||||
status={status}
|
||||
bottomRadius={true}
|
||||
width={posterWidth}
|
||||
detailedProgressBar={detailedProgressBar}
|
||||
isAvailable={isAvailable}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -92,15 +92,14 @@ class CollectionOverviews extends Component {
|
||||
|
||||
if (this._grid && scrollTop !== 0 && !scrollRestored) {
|
||||
this.setState({ scrollRestored: true });
|
||||
this._grid.scrollToPosition({ scrollTop });
|
||||
this._gridScrollToPosition({ scrollTop });
|
||||
}
|
||||
|
||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||
|
||||
if (this._grid && index != null) {
|
||||
|
||||
this._grid.scrollToCell({
|
||||
this._gridScrollToCell({
|
||||
rowIndex: index,
|
||||
columnIndex: 0
|
||||
});
|
||||
@@ -186,6 +185,19 @@ class CollectionOverviews extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
_gridScrollToCell = ({ rowIndex = 0, columnIndex = 0 }) => {
|
||||
const scrollOffset = this._grid.getOffsetForCell({
|
||||
rowIndex,
|
||||
columnIndex
|
||||
});
|
||||
|
||||
this._gridScrollToPosition(scrollOffset);
|
||||
};
|
||||
|
||||
_gridScrollToPosition = ({ scrollTop = 0, scrollLeft = 0 }) => {
|
||||
this.props.scroller?.scrollTo({ top: scrollTop, left: scrollLeft });
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ReactNode, useEffect, useRef } from 'react';
|
||||
import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
|
||||
import { Grid, GridCellProps, WindowScroller } from 'react-virtualized';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import Scroller from 'Components/Scroller/Scroller';
|
||||
@@ -79,6 +79,39 @@ function VirtualTable<T extends ModelBase>({
|
||||
position: undefined,
|
||||
};
|
||||
|
||||
const handleScrollToPosition = useCallback(
|
||||
({
|
||||
scrollTop = 0,
|
||||
scrollLeft = 0,
|
||||
}: {
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}) => {
|
||||
scroller?.scrollTo({ top: scrollTop, left: scrollLeft });
|
||||
},
|
||||
[scroller]
|
||||
);
|
||||
|
||||
const handleScrollToCell = useCallback(
|
||||
({
|
||||
rowIndex = 0,
|
||||
columnIndex = 0,
|
||||
}: {
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
}) => {
|
||||
if (gridRef.current) {
|
||||
const scrollOffset = gridRef.current.getOffsetForCell({
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
});
|
||||
|
||||
handleScrollToPosition(scrollOffset);
|
||||
}
|
||||
},
|
||||
[gridRef, handleScrollToPosition]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (gridRef.current && width > 0) {
|
||||
gridRef.current.recomputeGridSize();
|
||||
@@ -97,10 +130,10 @@ function VirtualTable<T extends ModelBase>({
|
||||
|
||||
useEffect(() => {
|
||||
if (gridRef.current && scrollTop && !scrollRestored.current) {
|
||||
gridRef.current.scrollToPosition({ scrollLeft: 0, scrollTop });
|
||||
handleScrollToPosition({ scrollLeft: 0, scrollTop });
|
||||
scrollRestored.current = true;
|
||||
}
|
||||
}, [scrollTop]);
|
||||
}, [scrollTop, handleScrollToPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -108,12 +141,12 @@ function VirtualTable<T extends ModelBase>({
|
||||
scrollIndex != null &&
|
||||
scrollIndex !== previousScrollIndex
|
||||
) {
|
||||
gridRef.current.scrollToCell({
|
||||
handleScrollToCell({
|
||||
rowIndex: scrollIndex,
|
||||
columnIndex: 0,
|
||||
});
|
||||
}
|
||||
}, [scrollIndex, previousScrollIndex]);
|
||||
}, [scrollIndex, previousScrollIndex, handleScrollToCell]);
|
||||
|
||||
return (
|
||||
<WindowScroller scrollElement={isSmallScreen ? undefined : scroller}>
|
||||
|
||||
@@ -95,8 +95,7 @@ class DiscoverMovieOverviews extends Component {
|
||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||
|
||||
if (this._grid && index != null) {
|
||||
|
||||
this._grid.scrollToCell({
|
||||
this._gridScrollToCell({
|
||||
rowIndex: index,
|
||||
columnIndex: 0
|
||||
});
|
||||
@@ -182,6 +181,19 @@ class DiscoverMovieOverviews extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
_gridScrollToCell = ({ rowIndex = 0, columnIndex = 0 }) => {
|
||||
const scrollOffset = this._grid.getOffsetForCell({
|
||||
rowIndex,
|
||||
columnIndex
|
||||
});
|
||||
|
||||
this._gridScrollToPosition(scrollOffset);
|
||||
};
|
||||
|
||||
_gridScrollToPosition = ({ scrollTop = 0, scrollLeft = 0 }) => {
|
||||
this.props.scroller?.scrollTo({ top: scrollTop, left: scrollLeft });
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ class DiscoverMoviePosters extends Component {
|
||||
if (this._grid && index != null) {
|
||||
const row = Math.floor(index / columnCount);
|
||||
|
||||
this._grid.scrollToCell({
|
||||
this._gridScrollToCell({
|
||||
rowIndex: row,
|
||||
columnIndex: 0
|
||||
});
|
||||
@@ -271,6 +271,19 @@ class DiscoverMoviePosters extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
_gridScrollToCell = ({ rowIndex = 0, columnIndex = 0 }) => {
|
||||
const scrollOffset = this._grid.getOffsetForCell({
|
||||
rowIndex,
|
||||
columnIndex
|
||||
});
|
||||
|
||||
this._gridScrollToPosition(scrollOffset);
|
||||
};
|
||||
|
||||
_gridScrollToPosition = ({ scrollTop = 0, scrollLeft = 0 }) => {
|
||||
this.props.scroller?.scrollTo({ top: scrollTop, left: scrollLeft });
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
.physicalRelease,
|
||||
.digitalRelease,
|
||||
.releaseDate,
|
||||
.genres {
|
||||
.genres,
|
||||
.keywords {
|
||||
composes: cell;
|
||||
|
||||
flex: 0 0 180px;
|
||||
|
||||
@@ -12,6 +12,7 @@ interface CssExports {
|
||||
'genres': string;
|
||||
'imdbRating': string;
|
||||
'inCinemas': string;
|
||||
'keywords': string;
|
||||
'minimumAvailability': string;
|
||||
'movieStatus': string;
|
||||
'originalLanguage': string;
|
||||
|
||||
@@ -72,6 +72,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||
minimumAvailability,
|
||||
path,
|
||||
genres = [],
|
||||
keywords = [],
|
||||
ratings,
|
||||
popularity,
|
||||
certification,
|
||||
@@ -339,6 +340,20 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'keywords') {
|
||||
const joinedKeywords = keywords.join(', ');
|
||||
const truncatedKeywords =
|
||||
keywords.length > 3
|
||||
? `${keywords.slice(0, 3).join(', ')}...`
|
||||
: joinedKeywords;
|
||||
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
<span title={joinedKeywords}>{truncatedKeywords}</span>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'movieStatus') {
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
.physicalRelease,
|
||||
.digitalRelease,
|
||||
.releaseDate,
|
||||
.genres {
|
||||
.genres,
|
||||
.keywords {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 180px;
|
||||
|
||||
@@ -9,6 +9,7 @@ interface CssExports {
|
||||
'genres': string;
|
||||
'imdbRating': string;
|
||||
'inCinemas': string;
|
||||
'keywords': string;
|
||||
'minimumAvailability': string;
|
||||
'movieStatus': string;
|
||||
'originalLanguage': string;
|
||||
|
||||
@@ -82,6 +82,7 @@ interface Movie extends ModelBase {
|
||||
minimumAvailability: MovieAvailability;
|
||||
path: string;
|
||||
genres: string[];
|
||||
keywords: string[];
|
||||
ratings: Ratings;
|
||||
popularity: number;
|
||||
certification: string;
|
||||
|
||||
@@ -181,6 +181,12 @@ export const defaultState = {
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'keywords',
|
||||
label: () => translate('Keywords'),
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'movieStatus',
|
||||
label: () => translate('Status'),
|
||||
@@ -473,8 +479,8 @@ export const defaultState = {
|
||||
label: () => translate('Genres'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
optionsSelector: function(items) {
|
||||
const genreList = items.reduce((acc, movie) => {
|
||||
movie.genres.forEach((genre) => {
|
||||
const genreList = items.reduce((acc, { genres = [] }) => {
|
||||
genres.forEach((genre) => {
|
||||
acc.push({
|
||||
id: genre,
|
||||
name: genre
|
||||
@@ -487,6 +493,27 @@ export const defaultState = {
|
||||
return genreList.sort(sortByProp('name'));
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'keywords',
|
||||
label: () => translate('Keywords'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
optionsSelector: function(items) {
|
||||
const keywordList = items.reduce((acc, { keywords = [] }) => {
|
||||
keywords.forEach((keyword) => {
|
||||
if (acc.findIndex((a) => a.id === keyword) === -1) {
|
||||
acc.push({
|
||||
id: keyword,
|
||||
name: keyword
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return keywordList.sort(sortByProp('name'));
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'tmdbRating',
|
||||
label: () => translate('TmdbRating'),
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
private static bool InternalIsOfficialBuild()
|
||||
{
|
||||
// Official builds will never have such a high revision
|
||||
if (BuildInfo.Version.Major >= 10 || BuildInfo.Version.Revision > 10000)
|
||||
if (BuildInfo.Version.Major >= 10 || BuildInfo.Version.Revision > 20000)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
}
|
||||
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
|
||||
{
|
||||
throw new WebException("Http request timed out", ex.InnerException, WebExceptionStatus.Timeout, null);
|
||||
throw new WebException("Http request timed out", ex, WebExceptionStatus.Timeout, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ namespace NzbDrone.Core.Test.DiskSpace
|
||||
[TestCase("/var/lib/docker")]
|
||||
[TestCase("/some/place/docker/aufs")]
|
||||
[TestCase("/etc/network")]
|
||||
[TestCase("/Volumes/.timemachine/ABC123456-A1BC-12A3B45678C9/2025-05-13-181401.backup")]
|
||||
public void should_not_check_diskspace_for_irrelevant_mounts(string path)
|
||||
{
|
||||
var mount = new Mock<IMount>();
|
||||
|
||||
@@ -67,7 +67,8 @@ namespace NzbDrone.Core.Test.Languages
|
||||
new object[] { 52, Language.Marathi },
|
||||
new object[] { 53, Language.Tagalog },
|
||||
new object[] { 54, Language.Urdu },
|
||||
new object[] { 55, Language.Romansh }
|
||||
new object[] { 55, Language.Romansh },
|
||||
new object[] { 56, Language.Mongolian }
|
||||
};
|
||||
|
||||
public static object[] ToIntCases =
|
||||
@@ -129,7 +130,8 @@ namespace NzbDrone.Core.Test.Languages
|
||||
new object[] { Language.Marathi, 52 },
|
||||
new object[] { Language.Tagalog, 53 },
|
||||
new object[] { Language.Urdu, 54 },
|
||||
new object[] { Language.Romansh, 55 }
|
||||
new object[] { Language.Romansh, 55 },
|
||||
new object[] { Language.Mongolian, 56 }
|
||||
};
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -405,6 +405,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
[TestCase("ice", "IS")]
|
||||
[TestCase("dut", "NL")]
|
||||
[TestCase("nor", "NO")]
|
||||
[TestCase("khk", "MN")]
|
||||
[TestCase("mvf", "MN")]
|
||||
public void should_format_languagecodes_properly(string language, string code)
|
||||
{
|
||||
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.FULL}";
|
||||
|
||||
@@ -98,5 +98,16 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
var result = IsoLanguages.Find(isoCode);
|
||||
result.Language.Should().Be(Language.Romansh);
|
||||
}
|
||||
|
||||
[TestCase("mn")]
|
||||
[TestCase("mon")]
|
||||
[TestCase("khk")]
|
||||
[TestCase("mvf")]
|
||||
[TestCase("mn-Cyrl")]
|
||||
public void should_return_mongolian(string isoCode)
|
||||
{
|
||||
var result = IsoLanguages.Find(isoCode);
|
||||
result.Language.Should().Be(Language.Mongolian);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,6 +513,14 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
result.Should().Contain(Language.Romansh);
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.2025.Mongolian.WEB-DL.h264")]
|
||||
[TestCase("Movie.Title.2025.Khalkha.WEB-DL.h264")]
|
||||
public void should_parse_language_mongolian(string postTitle)
|
||||
{
|
||||
var result = LanguageParser.ParseLanguages(postTitle);
|
||||
result.Should().Contain(Language.Mongolian);
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.en.sub")]
|
||||
[TestCase("Movie Title.eng.sub")]
|
||||
[TestCase("Movie.Title.eng.forced.sub")]
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.AutoTagging.Specifications
|
||||
{
|
||||
private static readonly GenreSpecificationValidator Validator = new ();
|
||||
|
||||
public override int Order => 1;
|
||||
public override int Order => 2;
|
||||
public override string ImplementationName => "Genre";
|
||||
|
||||
[FieldDefinition(1, Label = "AutoTaggingSpecificationGenre", Type = FieldType.Tag)]
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.AutoTagging.Specifications
|
||||
{
|
||||
public class KeywordSpecificationValidator : AbstractValidator<KeywordSpecification>
|
||||
{
|
||||
public KeywordSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class KeywordSpecification : AutoTaggingSpecificationBase
|
||||
{
|
||||
private static readonly KeywordSpecificationValidator Validator = new ();
|
||||
|
||||
public override int Order => 2;
|
||||
public override string ImplementationName => "Keyword";
|
||||
|
||||
[FieldDefinition(1, Label = "AutoTaggingSpecificationKeyword", Type = FieldType.Tag)]
|
||||
public IEnumerable<string> Value { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
||||
{
|
||||
return movie?.MovieMetadata?.Value?.Keywords.Any(keyword => Value.ContainsIgnoreCase(keyword)) ?? false;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
|
||||
RuleFor(c => c.Max).GreaterThan(c => c.Min);
|
||||
RuleFor(c => c.Max).LessThanOrEqualTo(double.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(242)]
|
||||
public class add_movie_keywords : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("MovieMetadata").AddColumn("Keywords").AsString().Nullable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.DiskSpace
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled);
|
||||
private static readonly Regex _regexSpecialDrive = new Regex(@"^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)|/\.timemachine", RegexOptions.Compiled);
|
||||
|
||||
public DiskSpaceService(IMovieService movieService, IRootFolderService rootFolderService, IDiskProvider diskProvider, Logger logger)
|
||||
{
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace NzbDrone.Core.Download
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
if (ex.Response.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for movie '{0}' failed since it no longer exists ({1})", remoteMovie.Release.Title, torrentUrl);
|
||||
throw new ReleaseUnavailableException(remoteMovie.Release, "Downloading torrent failed", ex);
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
var request = indexer?.GetDownloadRequest(url) ?? new HttpRequest(url);
|
||||
request.RateLimitKey = remoteMovie?.Release?.IndexerId.ToString();
|
||||
request.AllowAutoRedirect = true;
|
||||
|
||||
var response = await RetryStrategy
|
||||
.ExecuteAsync(static async (state, _) => await state._httpClient.GetAsync(state.request), (_httpClient, request))
|
||||
@@ -59,7 +60,7 @@ namespace NzbDrone.Core.Download
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
if (ex.Response.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone)
|
||||
{
|
||||
_logger.Error(ex, "Downloading nzb file for movie '{0}' failed since it no longer exists ({1})", remoteMovie.Release.Title, url);
|
||||
throw new ReleaseUnavailableException(remoteMovie.Release, "Downloading nzb failed", ex);
|
||||
|
||||
@@ -40,9 +40,10 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
|
||||
var excludeCompanyIds = Settings.FilterCriteria.ExcludeCompanyIds;
|
||||
var languageCode = Settings.FilterCriteria.LanguageCode;
|
||||
|
||||
var todaysDate = DateTime.Now.ToString("yyyy-MM-dd");
|
||||
var threeMonthsAgo = DateTime.Parse(todaysDate).AddMonths(-3).ToString("yyyy-MM-dd");
|
||||
var threeMonthsFromNow = DateTime.Parse(todaysDate).AddMonths(3).ToString("yyyy-MM-dd");
|
||||
var now = DateTime.UtcNow;
|
||||
var todaysDate = now.ToString("yyyy-MM-dd");
|
||||
var threeMonthsAgo = now.AddMonths(-3).ToString("yyyy-MM-dd");
|
||||
var threeMonthsFromNow = now.AddMonths(3).ToString("yyyy-MM-dd");
|
||||
|
||||
var requestBuilder = RequestBuilder.Create()
|
||||
.SetSegment("api", "3")
|
||||
@@ -54,8 +55,9 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
|
||||
switch (Settings.TMDbListType)
|
||||
{
|
||||
case (int)TMDbPopularListType.Theaters:
|
||||
requestBuilder.AddQueryParam("primary_release_date.gte", threeMonthsAgo)
|
||||
.AddQueryParam("primary_release_date.lte", todaysDate);
|
||||
requestBuilder
|
||||
.AddQueryParam("primary_release_date.gte", threeMonthsAgo)
|
||||
.AddQueryParam("primary_release_date.lte", todaysDate);
|
||||
break;
|
||||
case (int)TMDbPopularListType.Popular:
|
||||
requestBuilder.AddQueryParam("sort_by", "popularity.desc");
|
||||
@@ -64,8 +66,9 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
|
||||
requestBuilder.AddQueryParam("sort_by", "vote_average.desc");
|
||||
break;
|
||||
case (int)TMDbPopularListType.Upcoming:
|
||||
requestBuilder.AddQueryParam("primary_release_date.gte", todaysDate)
|
||||
.AddQueryParam("primary_release_date.lte", threeMonthsFromNow);
|
||||
requestBuilder
|
||||
.AddQueryParam("primary_release_date.gte", todaysDate)
|
||||
.AddQueryParam("primary_release_date.lte", threeMonthsFromNow);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace NzbDrone.Core.ImportLists.TMDb
|
||||
[FieldOption(Hint = "Urdu")]
|
||||
ur,
|
||||
[FieldOption(Hint = "Raeto-Romance")]
|
||||
rm
|
||||
rm,
|
||||
[FieldOption(Hint = "Mongolian")]
|
||||
mn
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ namespace NzbDrone.Core.Languages
|
||||
public static Language Tagalog => new Language(53, "Tagalog");
|
||||
public static Language Urdu => new Language(54, "Urdu");
|
||||
public static Language Romansh => new Language(55, "Romansh");
|
||||
public static Language Mongolian => new Language(56, "Mongolian");
|
||||
public static Language Any => new Language(-1, "Any");
|
||||
public static Language Original => new Language(-2, "Original");
|
||||
|
||||
@@ -191,6 +192,7 @@ namespace NzbDrone.Core.Languages
|
||||
Tagalog,
|
||||
Urdu,
|
||||
Romansh,
|
||||
Mongolian,
|
||||
Any,
|
||||
Original
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"Clear": "Vymazat",
|
||||
"CreateEmptyMovieFolders": "Vytvořte prázdné složky s filmy",
|
||||
"MovieIsRecommend": "Film se doporučuje na základě nedávného přidání",
|
||||
"NegateHelpText": "Pokud je zaškrtnuto, vlastní formát se nepoužije, pokud se tato podmínka {0} shoduje.",
|
||||
"NegateHelpText": "Pokud je zaškrtnuto, vlastní formát se nepoužije, pokud je splněna tato podmínka {implementationName}.",
|
||||
"OnLatestVersion": "Nejnovější verze aplikace {appName} je již nainstalována",
|
||||
"QualitySettings": "Nastavení kvality",
|
||||
"QueueIsEmpty": "Fronta je prázdná",
|
||||
@@ -71,8 +71,8 @@
|
||||
"ImportListsSettingsSummary": "Importovat seznamy, vyloučit seznamy",
|
||||
"LogLevelTraceHelpTextWarning": "Trasování protokolování by mělo být povoleno pouze dočasně",
|
||||
"MinimumAgeHelpText": "Pouze Usenet: Minimální věk NZB v minutách, než jsou uchopeny. Tímto způsobem dáte novým verzím čas na propagaci u vašeho poskytovatele usenet.",
|
||||
"MinutesHundredTwenty": "120 minut: {0}",
|
||||
"MinutesNinety": "90 minut: {0}",
|
||||
"MinutesHundredTwenty": "120 minut: {hundredTwenty}",
|
||||
"MinutesNinety": "90 minut: {ninety}",
|
||||
"NoListRecommendations": "Nebyly nalezeny žádné položky seznamu ani doporučení. Chcete-li začít, budete chtít přidat nový film, importovat některé existující nebo přidat seznam.",
|
||||
"PreviewRename": "Náhled Přejmenovat",
|
||||
"PreviewRenameHelpText": "Tip: Chcete-li zobrazit náhled přejmenování ... vyberte možnost Zrušit, klikněte na libovolný název filmu a použijte ikonu",
|
||||
@@ -107,7 +107,7 @@
|
||||
"StartImport": "Spusťte import",
|
||||
"Status": "Postavení",
|
||||
"Studio": "Studio",
|
||||
"AddNewMovieRootFolderHelpText": "Podsložka „{0}“ bude vytvořena automaticky",
|
||||
"AddNewMovieRootFolderHelpText": "Podsložka '{folder}' bude vytvořena automaticky",
|
||||
"ICalTagsMoviesHelpText": "Platí pro filmy s alespoň jednou shodnou značkou",
|
||||
"TorrentDelayTime": "Torrent Delay: {0}",
|
||||
"Trakt": "Trakt",
|
||||
@@ -222,8 +222,8 @@
|
||||
"ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.",
|
||||
"CertificationCountry": "Země certifikace",
|
||||
"ImportErrors": "Chyby importu",
|
||||
"ImportExtraFilesMovieHelpText": "Po importu filmového souboru importujte odpovídající další soubory (titulky, nfo atd.)",
|
||||
"ImportFailed": "Import se nezdařil: {0}",
|
||||
"ImportExtraFilesMovieHelpText": "Importovat odpovídající doplňkové soubory (titulky, nfo atd.) po importu souboru filmu",
|
||||
"ImportFailed": "Import se nezdařil: {sourceTitle}",
|
||||
"CreateEmptyMovieFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů",
|
||||
"DeleteDownloadClient": "Odstranit staženého klienta",
|
||||
"DownloadClientStatusCheckAllClientMessage": "Všichni klienti pro stahování nejsou kvůli chybám k dispozici",
|
||||
@@ -278,7 +278,7 @@
|
||||
"MinimumCustomFormatScoreHelpText": "Minimální skóre vlastního formátu povoleno ke stažení",
|
||||
"MinimumAge": "Minimální věk",
|
||||
"MoveFiles": "Přesouvat soubory",
|
||||
"MinutesSixty": "60 minut: {0}",
|
||||
"MinutesSixty": "60 minut: {sixty}",
|
||||
"Small": "Malý",
|
||||
"Calendar": "Kalendář",
|
||||
"CancelPendingTask": "Opravdu chcete zrušit tento úkol čekající na vyřízení?",
|
||||
@@ -299,7 +299,7 @@
|
||||
"Profiles": "Profily",
|
||||
"ProxyType": "Typ serveru proxy",
|
||||
"PtpOldSettingsCheckMessage": "Následující indexovače PassThePopcorn mají zastaralá nastavení a měla by být aktualizována: {0}",
|
||||
"QualitiesHelpText": "Kvality vyšší v seznamu jsou upřednostňovány. Vlastnosti ve stejné skupině jsou stejné. Chtějí se pouze ověřené kvality",
|
||||
"QualitiesHelpText": "Kvality výše v seznamu jsou více upřednostňovány, i když nejsou zaškrtnuty. Kvality v rámci stejné skupiny jsou si rovny. Pouze zaškrtnuté kvality jsou požadovány",
|
||||
"QualityProfileInUseMovieListCollection": "Nelze odstranit kvalitní profil připojený k filmu",
|
||||
"CalendarFeed": "Informační kanál {appName} Calendar",
|
||||
"ReadTheWikiForMoreInformation": "Další informace najdete na Wiki",
|
||||
@@ -413,7 +413,7 @@
|
||||
"PhysicalRelease": "Fyzické uvolnění",
|
||||
"Port": "Přístav",
|
||||
"PortNumber": "Číslo portu",
|
||||
"ProfilesSettingsSummary": "Profily kvality, jazyka a zpoždění",
|
||||
"ProfilesSettingsSummary": "Profily kvality, jazyka, zpoždění a vydání",
|
||||
"Progress": "Pokrok",
|
||||
"Proper": "Správně",
|
||||
"Protocol": "Protokol",
|
||||
@@ -422,7 +422,7 @@
|
||||
"AllowHardcodedSubsHelpText": "Zjištěné pevně zakódované odběry budou automaticky staženy",
|
||||
"QualityProfile": "Profil kvality",
|
||||
"QualityProfiles": "Profily kvality",
|
||||
"QuickImport": "Rychlý import",
|
||||
"QuickImport": "Přesunout Automaticky",
|
||||
"SupportedDownloadClients": "{appName} podporuje libovolného klienta pro stahování, který používá standard Newznab, stejně jako další klienty pro stahování uvedené níže.",
|
||||
"SupportedListsMovie": "{appName} podporuje všechny seznamy filmů RSS i níže uvedené.",
|
||||
"RadarrTags": "{appName} tagy",
|
||||
@@ -430,7 +430,7 @@
|
||||
"RecyclingBinHelpText": "Zde budou filmové soubory odstraněny, místo aby byly trvale odstraněny",
|
||||
"Source": "Zdroj",
|
||||
"RefreshAndScan": "Obnovit a skenovat",
|
||||
"RequiredHelpText": "Aby se mohl použít vlastní formát, musí být splněna tato podmínka {0}. Jinak stačí jedna shoda {1}.",
|
||||
"RequiredHelpText": "Tato podmínka {implementationName} musí být splněna, aby se použil vlastní formát. Jinak stačí jediná shoda {implementationName}.",
|
||||
"AddNewRestriction": "Přidat nové omezení",
|
||||
"AppDataLocationHealthCheckMessage": "Aktualizace nebude možná, aby se zabránilo odstranění AppData při aktualizaci",
|
||||
"RestartRequiredHelpTextWarning": "Vyžaduje restart, aby se projevilo",
|
||||
@@ -440,7 +440,7 @@
|
||||
"RootFolderCheckMultipleMessage": "Chybí více kořenových složek: {rootFolderPaths}",
|
||||
"SendAnonymousUsageData": "Odesílejte anonymní údaje o používání",
|
||||
"FileBrowserPlaceholderText": "Začněte psát nebo vyberte cestu níže",
|
||||
"StartupDirectory": "Spouštěcí adresář",
|
||||
"StartupDirectory": "Spouštěcí Adresář",
|
||||
"System": "Systém",
|
||||
"SystemTimeHealthCheckMessage": "Systémový čas je vypnutý o více než 1 den. Naplánované úlohy nemusí fungovat správně, dokud nebude čas opraven",
|
||||
"Posters": "Plakáty",
|
||||
@@ -519,7 +519,7 @@
|
||||
"EditMovie": "Upravit film",
|
||||
"DeleteQualityProfile": "Smažte profil kvality",
|
||||
"DeleteRestriction": "Odstranit omezení",
|
||||
"DeleteSelectedMovie": "Odstranit vybrané filmy",
|
||||
"DeleteSelectedMovie": "Odstranit vybraný film",
|
||||
"DeleteSelectedMovieFiles": "Odstranit vybrané filmové soubory",
|
||||
"DeleteMovieFolderConfirmation": "Složka filmu „{0}“ a veškerý její obsah budou smazány.",
|
||||
"DestinationPath": "Cesta k cíli",
|
||||
@@ -544,7 +544,7 @@
|
||||
"Downloading": "Stahování",
|
||||
"DownloadPropersAndRepacksHelpText": "Zda se má automaticky upgradovat na Propers / Repacks",
|
||||
"DownloadPropersAndRepacksHelpTextCustomFormat": "Pomocí možnosti „Nepřednostňujte“ můžete třídit podle skóre vlastního formátu přes položky Proppers / Repacks",
|
||||
"DownloadWarning": "Upozornění na stahování: {0}",
|
||||
"DownloadWarning": "Varování při stahování: {warningMessage}",
|
||||
"EditCustomFormat": "Upravit vlastní formát",
|
||||
"Edition": "Edice",
|
||||
"EditImportListExclusion": "Upravit vyloučení seznamu",
|
||||
@@ -650,7 +650,7 @@
|
||||
"Links": "Odkazy",
|
||||
"ImportLists": "Seznamy",
|
||||
"ImportListSettings": "Nastavení seznamu",
|
||||
"ListSyncLevelHelpText": "Filmy v knihovně budou odstraněny nebo nesledovány, pokud nejsou ve vašem seznamu",
|
||||
"ListSyncLevelHelpText": "Filmy v knihovně budou zpracovány na základě vašeho výběru, pokud vypadnou nebo se neobjeví na vašich seznamech",
|
||||
"LogFiles": "Záznam souborů",
|
||||
"Logging": "Protokolování",
|
||||
"LogLevel": "Úroveň protokolu",
|
||||
@@ -702,7 +702,7 @@
|
||||
"NoTagsHaveBeenAddedYet": "Zatím nebyly přidány žádné značky",
|
||||
"Options": "Možnosti",
|
||||
"Organize": "Organizovat",
|
||||
"OrganizeConfirm": "Opravdu chcete uspořádat všechny soubory ve {0} vybraných filmech?",
|
||||
"OrganizeConfirm": "Opravdu chcete uspořádat všechny soubory ve {count} vybraném filmu(ech)?",
|
||||
"OrganizeSelectedMovies": "Uspořádejte vybrané filmy",
|
||||
"Original": "Originál",
|
||||
"OutputPath": "Výstupní cesta",
|
||||
@@ -713,7 +713,7 @@
|
||||
"PreferIndexerFlagsHelpText": "Upřednostněte vydání pomocí speciálních vlajek",
|
||||
"Preferred": "Upřednostňováno",
|
||||
"Priority": "Přednost",
|
||||
"PrioritySettings": "Priorita: {0}",
|
||||
"PrioritySettings": "Priorita: {priority}",
|
||||
"ProcessingFolders": "Zpracování složek",
|
||||
"ProxyCheckBadRequestMessage": "Nepodařilo se otestovat proxy. StatusCode: {statusCode}",
|
||||
"ProxyCheckResolveIpMessage": "Nepodařilo se vyřešit adresu IP konfigurovaného hostitele proxy {proxyHostName}",
|
||||
@@ -744,7 +744,7 @@
|
||||
"RemovedMovieCheckSingleMessage": "Film {movie} byl odebrán z TMDb",
|
||||
"RemoveFailedDownloadsHelpText": "Odebrat neúspěšná stahování z historie stahování klienta",
|
||||
"RemoveFilter": "Vyjměte filtr",
|
||||
"RemoveFromDownloadClient": "Odebrat ze staženého klienta",
|
||||
"RemoveFromDownloadClient": "Odebrat z Klienta pro stahování",
|
||||
"RemoveFromQueue": "Odebrat z fronty",
|
||||
"Renamed": "Přejmenováno",
|
||||
"RemoveHelpTextWarning": "Odebráním odstraníte stažené soubory a soubory z klienta pro stahování.",
|
||||
@@ -848,7 +848,7 @@
|
||||
"Table": "Stůl",
|
||||
"TableOptions": "Možnosti tabulky",
|
||||
"TableColumnsHelpText": "Vyberte, které sloupce jsou viditelné a v jakém pořadí se zobrazují",
|
||||
"TagDetails": "Podrobnosti značky - {0}",
|
||||
"TagDetails": "Podrobnosti značky - {label}",
|
||||
"TagIsNotUsedAndCanBeDeleted": "Značka se nepoužívá a lze ji smazat",
|
||||
"Tags": "Značky",
|
||||
"TagsSettingsSummary": "Podívejte se na všechny značky a na to, jak se používají. Nepoužité značky lze odstranit",
|
||||
@@ -945,7 +945,7 @@
|
||||
"Edit": "Upravit",
|
||||
"SqliteVersionCheckUpgradeRequiredMessage": "Aktuálně nainstalovaná verze SQLite {0} již není podporována. Upgradujte SQLite alespoň na verzi {1}.",
|
||||
"ShowReleaseDate": "Zobrazit datum vydání",
|
||||
"ShowReleaseDateHelpText": "Zobrazit datum vydání pod plakátem",
|
||||
"ShowReleaseDateHelpText": "Zobrazit datum vydání na základě minimální dostupnosti pod plakátem",
|
||||
"ShowCinemaRelease": "Zobrazit datum vydání kina",
|
||||
"OnMovieDelete": "Při mazání filmu",
|
||||
"OnMovieFileDelete": "Při mazání filmových souborů",
|
||||
@@ -979,7 +979,7 @@
|
||||
"DeleteDelayProfileMessageText": "Opravdu chcete smazat tento profil zpoždění?",
|
||||
"DeleteFormatMessageText": "Opravdu chcete smazat značku formátu {0}?",
|
||||
"RemoveSelectedItemQueueMessageText": "Opravdu chcete odebrat {0} položku {1} z fronty?",
|
||||
"RemoveSelectedItemsQueueMessageText": "Opravdu chcete odebrat {0} položku {1} z fronty?",
|
||||
"RemoveSelectedItemsQueueMessageText": "Opravdu chcete odebrat {selectedCount} položky z fronty?",
|
||||
"ApplyTagsHelpTextAdd": "Přidat: Přidat štítky do existujícího seznamu štítků",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít štítky na vybrané indexery",
|
||||
"ApplyTagsHelpTextRemove": "Odebrat: Odebrat zadané štítky",
|
||||
@@ -1338,5 +1338,31 @@
|
||||
"DownloadClientFreeboxAuthenticationError": "Přihlášení k Freebox API se nezdařilo. Důvod: {errorDescription}",
|
||||
"DownloadClientDownloadStationValidationFolderMissingDetail": "Složka '{downloadDir}' neexistuje, musí být vytvořená ručně ve Sdílené složce '{sharedFolder}'.",
|
||||
"DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "Přihlaste se do vaší DiskStation jako {username} a ručně to nastavte v nastavení DownloadStation pod BT/HTTP/FTP/NZB -> Umístění.",
|
||||
"DownloadClientDownloadStationValidationSharedFolderMissingDetail": "DiskStation nemá sdílenou složku s názvem '{sharedFolder}', jste si jisti, že jste ji zadali správně?"
|
||||
"DownloadClientDownloadStationValidationSharedFolderMissingDetail": "DiskStation nemá sdílenou složku s názvem '{sharedFolder}', jste si jisti, že jste ji zadali správně?",
|
||||
"AutoTaggingSpecificationStudio": "Studio (a)",
|
||||
"BlocklistedAt": "Blocklistováno od {date}",
|
||||
"DeleteMovieFolderCountWithFilesConfirmation": "Opravdu si přejete smazat {count} vybraných filmů a veškerý obsah?",
|
||||
"DeleteMovieFolderMovieCount": "{movieFileCount} filmových souborů o celkové velikosti {size}",
|
||||
"NotificationsAppriseSettingsPasswordHelpText": "Heslo pro základní HTTP autentizaci",
|
||||
"NotificationsAppriseSettingsServerUrl": "URL serveru Apprise",
|
||||
"NotificationsAppriseSettingsStatelessUrls": "Bezstavové URL adresy Apprise",
|
||||
"NotificationsAppriseSettingsStatelessUrlsHelpText": "Jedna nebo více URL adres oddělených čárkami, které určují, kam má být oznámení odesláno. Ponechte prázdné, pokud je použito trvalé úložiště.",
|
||||
"NotificationsAppriseSettingsTags": "Tagy Apprise",
|
||||
"NotificationsAppriseSettingsTagsHelpText": "Volitelně upozornit pouze ty, kteří jsou odpovídajícím způsobem označeni.",
|
||||
"NotificationsAppriseSettingsUsernameHelpText": "Uživatelské jméno pro základní HTTP autentizaci",
|
||||
"NotificationsCustomScriptSettingsName": "Vlastní skript",
|
||||
"NotificationsCustomScriptSettingsArguments": "Argumenty",
|
||||
"NotificationsCustomScriptSettingsArgumentsHelpText": "Argumenty, které se mají předat skriptu",
|
||||
"NotificationsCustomScriptValidationFileDoesNotExist": "Soubor neexistuje",
|
||||
"NotificationsCustomScriptSettingsProviderMessage": "Testování spustí skript s EventType nastaveným na {eventTypeTest}, ujistěte se, že váš skript toto správně zpracovává",
|
||||
"NotificationsAppriseSettingsIncludePosterHelpText": "Zahrnout plakát do zprávy",
|
||||
"NotificationsAppriseSettingsIncludePoster": "Zahrnout plakát",
|
||||
"AutoTaggingSpecificationMaximumRuntime": "Maximální délka trvání",
|
||||
"AutoTaggingSpecificationMinimumRuntime": "Minimální délka trvání",
|
||||
"DownloadClientFreeboxUnableToReachFreeboxApi": "Nelze se připojit k Freebox API. Ověřte nastavení 'URL API' pro základní URL a verzi.",
|
||||
"NotificationsAppriseSettingsNotificationType": "Typ oznámení Apprise",
|
||||
"AnnouncedMovieAvailabilityDescription": "Filmy jsou považovány za dostupné, jakmile jsou přidány do {appName}.",
|
||||
"CustomFormatsSpecificationQualityModifier": "Modifikátor kvality",
|
||||
"NotificationsAppriseSettingsServerUrlHelpText": "URL serveru Apprise, včetně http(s):// a portu, pokud je potřeba",
|
||||
"Disposition": "Dispozice"
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
"AutoTaggingNegateHelpText": "If checked, the auto tagging rule will not apply if this {implementationName} condition matches.",
|
||||
"AutoTaggingRequiredHelpText": "This {implementationName} condition must match for the auto tagging rule to apply. Otherwise a single {implementationName} match is sufficient.",
|
||||
"AutoTaggingSpecificationGenre": "Genre(s)",
|
||||
"AutoTaggingSpecificationKeyword": "Keyword(s)",
|
||||
"AutoTaggingSpecificationMaximumRuntime": "Maximum Runtime",
|
||||
"AutoTaggingSpecificationMaximumYear": "Maximum Year",
|
||||
"AutoTaggingSpecificationMinimumRuntime": "Minimum Runtime",
|
||||
@@ -959,6 +960,7 @@
|
||||
"KeyboardShortcutsMovieIndexScrollTop": "Movie Index: Scroll Top",
|
||||
"KeyboardShortcutsOpenModal": "Open This Modal",
|
||||
"KeyboardShortcutsSaveSettings": "Save Settings",
|
||||
"Keywords": "Keywords",
|
||||
"Label": "Label",
|
||||
"LabelIsRequired": "Label is required",
|
||||
"Language": "Language",
|
||||
|
||||
@@ -974,7 +974,7 @@
|
||||
"RemoveFromBlocklist": "Удалить из черного списка",
|
||||
"Blocklisted": "Черный список",
|
||||
"BlocklistReleases": "Релиз из черного списка",
|
||||
"RemoveFailed": "Удаление не удалось",
|
||||
"RemoveFailed": "Удалять после ошибки",
|
||||
"RemoveSelectedItem": "Удалить выбранный элемент",
|
||||
"RemoveSelectedItems": "Удалить выбранные элементы",
|
||||
"BypassDelayIfHighestQuality": "Игнорировать при максимальном качестве",
|
||||
@@ -984,7 +984,7 @@
|
||||
"NotificationTriggersHelpText": "Выберите события, которые должны вызвать это уведомление",
|
||||
"RemotePathMappingCheckFileRemoved": "Файл {path} был удален в процессе обработки.",
|
||||
"RemotePathMappingCheckDockerFolderMissing": "Вы используете docker; загрузчик {downloadClientName} размещает загрузки в {path}, но этот каталог, похоже, не существует внутри контейнера. Проверьте соответствия удаленных путей и настройки тома контейнера.",
|
||||
"RemoveCompleted": "Удаление завершено",
|
||||
"RemoveCompleted": "Удалять завершенные",
|
||||
"RemoveDownloadsAlert": "Параметры удаления перенесены в настройки загрузчиков в таблице выше.",
|
||||
"TaskUserAgentTooltip": "User-Agent, представленный приложением, который вызывает API",
|
||||
"IndexerTagMovieHelpText": "Используйте этот индексатор только для фильмов с хотя бы одним совпадающим тегом. Оставьте пустым, чтобы использовать для всех фильмов.",
|
||||
|
||||
@@ -2015,5 +2015,8 @@
|
||||
"MovieEditRootFolderHelpText": "Filmleri aynı kök klasöre taşımak, film klasörlerinin güncellenen başlığa veya adlandırma biçimine uyacak şekilde yeniden adlandırılmasında kullanılabilir",
|
||||
"UpdateMoviePath": "Film Yolunu Güncelle",
|
||||
"EditMovieCollectionModalHeader": "Düzenle - {title}",
|
||||
"NotificationsAppriseSettingsIncludePoster": "Posteri Dahil Et"
|
||||
"NotificationsAppriseSettingsIncludePoster": "Posteri Dahil Et",
|
||||
"MovieFolderFormatHelpTextDeprecatedWarning": "Film dosyası özellikleriyle ilişkili token'lar artık kullanımdan kaldırılmıştır ve gelecek ana sürümlerde desteklenmeyecektir.",
|
||||
"NamingConfigMovieFolderFormatDeprecatedHealthCheckMessage": "Film Klasör Formatı, artık kullanılmayan dosya ilişkili token'lar içermemelidir: {tokens}",
|
||||
"NotificationsAppriseSettingsIncludePosterHelpText": "Mesaja poster ekle"
|
||||
}
|
||||
|
||||
@@ -14,5 +14,8 @@
|
||||
"Yesterday": "昨天",
|
||||
"Updates": "更新",
|
||||
"Warn": "警告",
|
||||
"BackupNow": "立即备份"
|
||||
"BackupNow": "立即备份",
|
||||
"AddANewPath": "新建",
|
||||
"Actions": "Actions",
|
||||
"AddAutoTagError": "添加"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||
public int? Runtime { get; set; }
|
||||
public List<ImageResource> Images { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public List<string> Keywords { get; set; }
|
||||
|
||||
public int Year { get; set; }
|
||||
public DateTime? Premier { get; set; }
|
||||
|
||||
@@ -272,7 +272,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
movie.Ratings = MapRatings(resource.MovieRatings) ?? new Ratings();
|
||||
|
||||
movie.TmdbId = resource.TmdbId;
|
||||
movie.Genres = resource.Genres;
|
||||
movie.Genres = resource.Genres ?? new List<string>();
|
||||
movie.Keywords = resource.Keywords ?? new List<string>();
|
||||
movie.Images = resource.Images.Select(MapImage).ToList();
|
||||
|
||||
movie.Recommendations = resource.Recommendations?.Select(r => r.TmdbId).ToList() ?? new List<int>();
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace NzbDrone.Core.Movies
|
||||
_movieMetadataService.Upsert(newMovie.MovieMetadata.Value);
|
||||
newMovie.MovieMetadataId = newMovie.MovieMetadata.Value.Id;
|
||||
|
||||
_movieService.UpdateTags(newMovie);
|
||||
|
||||
_movieService.AddMovie(newMovie);
|
||||
|
||||
return newMovie;
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Movies
|
||||
Translations = new List<MovieTranslation>();
|
||||
Images = new List<MediaCover.MediaCover>();
|
||||
Genres = new List<string>();
|
||||
Keywords = new List<string>();
|
||||
OriginalLanguage = Language.English;
|
||||
Recommendations = new List<int>();
|
||||
Ratings = new Ratings();
|
||||
@@ -24,6 +25,7 @@ namespace NzbDrone.Core.Movies
|
||||
|
||||
public List<MediaCover.MediaCover> Images { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public List<string> Keywords { get; set; }
|
||||
public DateTime? InCinemas { get; set; }
|
||||
public DateTime? PhysicalRelease { get; set; }
|
||||
public DateTime? DigitalRelease { get; set; }
|
||||
|
||||
@@ -114,6 +114,7 @@ namespace NzbDrone.Core.Movies
|
||||
movieMetadata.Runtime = movieInfo.Runtime;
|
||||
movieMetadata.Ratings = movieInfo.Ratings;
|
||||
movieMetadata.Genres = movieInfo.Genres;
|
||||
movieMetadata.Keywords = movieInfo.Keywords;
|
||||
movieMetadata.Certification = movieInfo.Certification;
|
||||
movieMetadata.InCinemas = movieInfo.InCinemas;
|
||||
movieMetadata.Website = movieInfo.Website;
|
||||
@@ -205,8 +206,14 @@ namespace NzbDrone.Core.Movies
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTags(Movie movie)
|
||||
private void UpdateTags(Movie movie, bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_logger.Trace("Skipping tag update for {0}. Reason: New movie", movie);
|
||||
return;
|
||||
}
|
||||
|
||||
var tagsUpdated = _movieService.UpdateTags(movie);
|
||||
|
||||
if (tagsUpdated)
|
||||
@@ -230,7 +237,7 @@ namespace NzbDrone.Core.Movies
|
||||
try
|
||||
{
|
||||
movie = RefreshMovieInfo(movieId);
|
||||
UpdateTags(movie);
|
||||
UpdateTags(movie, isNew);
|
||||
RescanMovie(movie, isNew, trigger);
|
||||
}
|
||||
catch (MovieNotFoundException)
|
||||
@@ -240,7 +247,7 @@ namespace NzbDrone.Core.Movies
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't refresh info for {0}", movie);
|
||||
UpdateTags(movie);
|
||||
UpdateTags(movie, isNew);
|
||||
RescanMovie(movie, isNew, trigger);
|
||||
throw;
|
||||
}
|
||||
@@ -277,13 +284,13 @@ namespace NzbDrone.Core.Movies
|
||||
_logger.Error(e, "Couldn't refresh info for {0}", movieLocal);
|
||||
}
|
||||
|
||||
UpdateTags(movie);
|
||||
UpdateTags(movie, false);
|
||||
RescanMovie(movieLocal, false, trigger);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Skipping refresh of movie: {0}", movieLocal.Title);
|
||||
UpdateTags(movie);
|
||||
UpdateTags(movie, false);
|
||||
RescanMovie(movieLocal, false, trigger);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,9 @@ namespace NzbDrone.Core.Organizer
|
||||
{ "rum", "ron" },
|
||||
{ "slo", "slk" },
|
||||
{ "tib", "bod" },
|
||||
{ "wel", "cym" }
|
||||
{ "wel", "cym" },
|
||||
{ "khk", "mon" },
|
||||
{ "mvf", "mon" }
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
public static readonly ImmutableArray<string> BadCharacters = ImmutableArray.Create("\\", "/", "<", ">", "?", "*", "|", "\"");
|
||||
|
||||
@@ -64,7 +64,8 @@ namespace NzbDrone.Core.Parser
|
||||
new IsoLanguage("mr", "", "mar", "Marathi", Language.Marathi),
|
||||
new IsoLanguage("tl", "", "tgl", "Tagalog", Language.Tagalog),
|
||||
new IsoLanguage("ur", "", "urd", "Urdu", Language.Urdu),
|
||||
new IsoLanguage("rm", "", "roh", "Romansh", Language.Romansh)
|
||||
new IsoLanguage("rm", "", "roh", "Romansh", Language.Romansh),
|
||||
new IsoLanguage("mn", "", "mon", "Mongolian", Language.Mongolian)
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, Language> AlternateIsoCodeMappings = new ()
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace NzbDrone.Core.Parser
|
||||
(?<korean>\bKOR\b)|
|
||||
(?<urdu>\burdu\b)|
|
||||
(?<romansh>\b(?:romansh|rumantsch|romansch)\b)|
|
||||
(?<mongolian>\b(?:mongolian|khalkha)\b)|
|
||||
(?<original>\b(?:orig|original)\b)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
@@ -432,6 +433,11 @@ namespace NzbDrone.Core.Parser
|
||||
languages.Add(Language.Romansh);
|
||||
}
|
||||
|
||||
if (match.Groups["mongolian"].Success)
|
||||
{
|
||||
languages.Add(Language.Mongolian);
|
||||
}
|
||||
|
||||
if (match.Groups["original"].Success)
|
||||
{
|
||||
languages.Add(Language.Original);
|
||||
|
||||
@@ -96,6 +96,11 @@ namespace NzbDrone.Core.RemotePathMappings
|
||||
throw new ArgumentException("Invalid Host");
|
||||
}
|
||||
|
||||
if (mapping.RemotePath.StartsWith(" "))
|
||||
{
|
||||
throw new ArgumentException("Remote Path must not start with a space");
|
||||
}
|
||||
|
||||
var remotePath = new OsPath(mapping.RemotePath);
|
||||
var localPath = new OsPath(mapping.LocalPath);
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using Radarr.Api.V3.AutoTagging;
|
||||
using Radarr.Http.ClientSchema;
|
||||
|
||||
namespace NzbDrone.Integration.Test.ApiTests
|
||||
{
|
||||
@@ -29,6 +31,53 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||
result.Tags.Should().Equal(tag.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Order(0)]
|
||||
public void add_movie_should_trigger_autotag()
|
||||
{
|
||||
var tag = EnsureTag("autotag-test");
|
||||
var movie = Movies.Lookup("imdb:tt0110912").Single();
|
||||
movie.Genres = new List<string> { "Thriller" };
|
||||
|
||||
var item = AutoTagging.Post(new AutoTaggingResource
|
||||
{
|
||||
Name = "Test",
|
||||
RemoveTagsAutomatically = false,
|
||||
Tags = new HashSet<int> { tag.Id },
|
||||
Specifications = new List<AutoTaggingSpecificationSchema>
|
||||
{
|
||||
new AutoTaggingSpecificationSchema
|
||||
{
|
||||
Name = "Test",
|
||||
Implementation = "GenreSpecification",
|
||||
ImplementationName = "Genre",
|
||||
Negate = false,
|
||||
Required = false,
|
||||
Fields = new List<Field>
|
||||
{
|
||||
new Field
|
||||
{
|
||||
Name = "value",
|
||||
Label = "Genre(s)",
|
||||
Type = "tag",
|
||||
Value = new List<string> { "Thriller" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EnsureNoMovie(680, "Pulp Fiction");
|
||||
|
||||
movie.QualityProfileId = 1;
|
||||
movie.Path = Path.Combine(MovieRootFolder, movie.Title);
|
||||
|
||||
var result = Movies.Post(movie);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result.Tags.Should().Contain(tag.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Order(0)]
|
||||
public void add_movie_without_profileid_should_return_badrequest()
|
||||
|
||||
@@ -17,6 +17,7 @@ using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Integration.Test.Client;
|
||||
using NzbDrone.SignalR;
|
||||
using NzbDrone.Test.Common.Categories;
|
||||
using Radarr.Api.V3.AutoTagging;
|
||||
using Radarr.Api.V3.Blocklist;
|
||||
using Radarr.Api.V3.Config;
|
||||
using Radarr.Api.V3.DownloadClient;
|
||||
@@ -36,6 +37,7 @@ namespace NzbDrone.Integration.Test
|
||||
{
|
||||
protected RestClient RestClient { get; private set; }
|
||||
|
||||
public ClientBase<AutoTaggingResource> AutoTagging;
|
||||
public ClientBase<BlocklistResource> Blocklist;
|
||||
public CommandClient Commands;
|
||||
public ClientBase<TaskResource> Tasks;
|
||||
@@ -99,6 +101,7 @@ namespace NzbDrone.Integration.Test
|
||||
RestClient.AddDefaultHeader("Authentication", ApiKey);
|
||||
RestClient.AddDefaultHeader("X-Api-Key", ApiKey);
|
||||
|
||||
AutoTagging = new ClientBase<AutoTaggingResource>(RestClient, ApiKey);
|
||||
Blocklist = new ClientBase<BlocklistResource>(RestClient, ApiKey);
|
||||
Commands = new CommandClient(RestClient, ApiKey);
|
||||
Tasks = new ClientBase<TaskResource>(RestClient, ApiKey, "system/task");
|
||||
|
||||
@@ -172,6 +172,11 @@ namespace Radarr.Api.V3.MovieFiles
|
||||
[Consumes("application/json")]
|
||||
public object DeleteMovieFiles([FromBody] MovieFileListResource resource)
|
||||
{
|
||||
if (!resource.MovieFileIds.Any())
|
||||
{
|
||||
throw new BadRequestException("movieFileIds must be provided");
|
||||
}
|
||||
|
||||
var movieFiles = _mediaFileService.GetMovies(resource.MovieFileIds);
|
||||
var movie = _movieService.GetMovie(movieFiles.First().MovieId);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Radarr.Api.V3.MovieFiles
|
||||
{
|
||||
public class MovieFileListResource
|
||||
{
|
||||
public List<int> MovieFileIds { get; set; }
|
||||
public List<int> MovieFileIds { get; set; } = new ();
|
||||
public List<Language> Languages { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public string Edition { get; set; }
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Radarr.Api.V3.Movies
|
||||
public string Folder { get; set; }
|
||||
public string Certification { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public List<string> Keywords { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
public AddMovieOptions AddOptions { get; set; }
|
||||
@@ -153,6 +154,7 @@ namespace Radarr.Api.V3.Movies
|
||||
Certification = model.MovieMetadata.Value.Certification,
|
||||
Website = model.MovieMetadata.Value.Website,
|
||||
Genres = model.MovieMetadata.Value.Genres,
|
||||
Keywords = model.MovieMetadata.Value.Keywords,
|
||||
Tags = model.Tags,
|
||||
Added = model.Added,
|
||||
AddOptions = model.AddOptions,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
using Radarr.Http;
|
||||
@@ -21,11 +22,19 @@ namespace Radarr.Api.V3.RemotePathMappings
|
||||
_remotePathMappingService = remotePathMappingService;
|
||||
|
||||
SharedValidator.RuleFor(c => c.Host)
|
||||
.NotEmpty();
|
||||
.NotEmpty();
|
||||
|
||||
// We cannot use IsValidPath here, because it's a remote path, possibly other OS.
|
||||
SharedValidator.RuleFor(c => c.RemotePath)
|
||||
.NotEmpty();
|
||||
.NotEmpty();
|
||||
|
||||
SharedValidator.RuleFor(c => c.RemotePath)
|
||||
.Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.StartsWith(" "))
|
||||
.WithMessage("Remote Path '{PropertyValue}' must not start with a space");
|
||||
|
||||
SharedValidator.RuleFor(c => c.RemotePath)
|
||||
.Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.EndsWith(" "))
|
||||
.WithMessage("Remote Path '{PropertyValue}' must not end with a space");
|
||||
|
||||
SharedValidator.RuleFor(c => c.LocalPath)
|
||||
.Cascade(CascadeMode.Stop)
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Radarr.Api.V3.System.Backup
|
||||
}
|
||||
|
||||
[HttpPost("restore/upload")]
|
||||
[RequestFormLimits(MultipartBodyLengthLimit = 500000000)]
|
||||
[RequestFormLimits(MultipartBodyLengthLimit = 5000000000)]
|
||||
public object UploadAndRestore()
|
||||
{
|
||||
var files = Request.Form.Files;
|
||||
|
||||
@@ -11027,6 +11027,13 @@
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"tags": {
|
||||
"uniqueItems": true,
|
||||
"type": "array",
|
||||
|
||||
Reference in New Issue
Block a user