1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-05 13:21:25 -05:00

Compare commits

...

13 Commits

Author SHA1 Message Date
Bogdan
ff393a3f65 Show movie titles when poster is missing on collections page 2025-05-24 00:26:48 +03:00
Aden Northcote
f5faf52469 Fixed: Update AutoTags on movie add (#11079)
Co-authored-by: aden <aden@hughorse.net>
2025-05-23 18:56:02 +03:00
Bogdan
b5b4d4b971 Return error with missing field for movie files endpoint
Fixes #10555
2025-05-23 18:50:36 +03:00
Bogdan
873299701b Use UTC dates for TMDB Popular lists 2025-05-23 18:12:54 +03:00
Bogdan
d14cca30d7 Use the thrown exception in http timeout handling
(cherry picked from commit 14e324ee30694ae017a39fd6f66392dc2d104617)
2025-05-23 12:25:47 +03:00
Stevie Robinson
5af61b5900 New: Ignore volumes containing .timemachine from Disk Space
(cherry picked from commit a853c537db0a6bd499a2277987dc170d2a1f5645)
2025-05-23 12:24:50 +03:00
carrossos
a10759c7e9 Treat HTTP 410 response for failed download similarly to HTTP 404
(cherry picked from commit 818ae02a7a8f0a8ea0a44e0015e2667d96453332)
2025-05-23 12:24:37 +03:00
Stevie Robinson
ac2d92007e New: Don't allow remote path to start with space
(cherry picked from commit 5ba3ff598770fdf9e5a53d490c8bcbdd6a59c4cc)
2025-05-23 12:13:27 +03:00
Mark McDowall
09cfdc3fa2 Increase maximum backup restoration size to 5GB
(cherry picked from commit e38deb34221ebf131adcce9551774898f46b1f7f)
2025-05-23 12:13:12 +03:00
Stevie Robinson
04f26dbff7 Ensure Custom Format Maximum Size won't overflow
(cherry picked from commit a50d2562649bbe77d0feb9fbfc594d56952e0a5e)
2025-05-23 12:12:37 +03:00
Bogdan
159f5df8cc Fix jump to character for Collections and Discover
Fix for a regression introduced in react-virtualized 9.21.2 when WindowScroller is used with Grids
2025-05-22 15:40:29 +03:00
Elerir
b823ad8e65 New: Add Mongolian language
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2025-05-21 12:08:15 +03:00
Bogdan
cc8bffc272 Bump version to 5.24.0 2025-05-17 15:56:03 +03:00
33 changed files with 273 additions and 58 deletions

View File

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

View File

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

View File

@@ -10,7 +10,8 @@ interface CssExports {
'externalLinks': string;
'link': string;
'monitorToggleButton': string;
'overlay': string;
'overlayHover': string;
'overlayHoverTitle': string;
'overlayTitle': string;
'poster': string;
'posterContainer': string;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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")]

View File

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

View File

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

View File

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

View File

@@ -59,7 +59,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);

View File

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

View File

@@ -73,6 +73,8 @@ namespace NzbDrone.Core.ImportLists.TMDb
[FieldOption(Hint = "Urdu")]
ur,
[FieldOption(Hint = "Raeto-Romance")]
rm
rm,
[FieldOption(Hint = "Mongolian")]
mn
}
}

View File

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

View File

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

View File

@@ -205,8 +205,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 +236,7 @@ namespace NzbDrone.Core.Movies
try
{
movie = RefreshMovieInfo(movieId);
UpdateTags(movie);
UpdateTags(movie, isNew);
RescanMovie(movie, isNew, trigger);
}
catch (MovieNotFoundException)
@@ -240,7 +246,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 +283,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);
}
}

View File

@@ -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("\\", "/", "<", ">", "?", "*", "|", "\"");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,9 @@
using System.Collections.Generic;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using Radarr.Http;
using Radarr.Http.REST;
@@ -21,11 +23,20 @@ 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 must not start with a space");
SharedValidator.RuleFor(c => c.RemotePath)
.Must(remotePath => !remotePath.IsNotNullOrWhiteSpace() && !remotePath.EndsWith(" "))
.WithMessage("Remote Path probably should not end with a space")
.AsWarning();
SharedValidator.RuleFor(c => c.LocalPath)
.Cascade(CascadeMode.Stop)

View File

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