mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-05 13:21:25 -05:00
Compare commits
13 Commits
v5.23.3.99
...
v5.24.0.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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.24.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
|
||||
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user