diff --git a/frontend/src/Activity/History/Details/HistoryDetails.css b/frontend/src/Activity/History/Details/HistoryDetails.css
index aaa33e627..383f08afd 100644
--- a/frontend/src/Activity/History/Details/HistoryDetails.css
+++ b/frontend/src/Activity/History/Details/HistoryDetails.css
@@ -1,5 +1,5 @@
.description {
- composes: title from '~Components/DescriptionList/DescriptionListItemDescription.css';
+ composes: description from '~Components/DescriptionList/DescriptionListItemDescription.css';
overflow-wrap: break-word;
}
diff --git a/frontend/src/Activity/Queue/QueueRow.js b/frontend/src/Activity/Queue/QueueRow.js
index 5bb637c84..e26b20353 100644
--- a/frontend/src/Activity/Queue/QueueRow.js
+++ b/frontend/src/Activity/Queue/QueueRow.js
@@ -307,7 +307,7 @@ QueueRow.propTypes = {
trackedDownloadStatus: PropTypes.string,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string,
- movie: PropTypes.object.isRequired,
+ movie: PropTypes.object,
quality: PropTypes.object.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
protocol: PropTypes.string.isRequired,
diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovie.css b/frontend/src/AddMovie/AddNewMovie/AddNewMovie.css
index 7c558d6d0..ed5a43c65 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovie.css
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovie.css
@@ -35,14 +35,20 @@
.message {
margin-top: 30px;
text-align: center;
+ font-weight: 300;
+ font-size: $largeFontSize;
}
.helpText {
margin-bottom: 10px;
- font-weight: 300;
font-size: 24px;
}
+.noMoviesText {
+ margin-top: 80px;
+ margin-bottom: 20px;
+}
+
.noResults {
margin-bottom: 10px;
font-weight: 300;
diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovie.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovie.js
index 0960e4e2e..1aaf4fd9b 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovie.js
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovie.js
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
-import { icons } from 'Helpers/Props';
+import { icons, kinds } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import Link from 'Components/Link/Link';
import Icon from 'Components/Icon';
@@ -79,7 +79,8 @@ class AddNewMovie extends Component {
render() {
const {
error,
- items
+ items,
+ hasExistingMovies
} = this.props;
const term = this.state.term;
@@ -160,13 +161,34 @@ class AddNewMovie extends Component {
}
{
- !term &&
+ term ?
+ null :
-
It's easy to add a new movie, just start typing the name the movie you want to add.
+
+ It's easy to add a new movie, just start typing the name the movie you want to add.
+
You can also search using TMDB ID of a movie. eg. tmdb:71663
}
+ {
+ !term && !hasExistingMovies ?
+
+
+ You haven't added any movies yet, do you want to import some or all of your movies first?
+
+
+
+
+
:
+ null
+ }
+
@@ -181,6 +203,7 @@ AddNewMovie.propTypes = {
isAdding: PropTypes.bool.isRequired,
addError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
+ hasExistingMovies: PropTypes.bool.isRequired,
onMovieLookupChange: PropTypes.func.isRequired,
onClearMovieLookup: PropTypes.func.isRequired
};
diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieConnector.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieConnector.js
index 6c238a10d..c44674ebc 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieConnector.js
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieConnector.js
@@ -11,13 +11,15 @@ import AddNewMovie from './AddNewMovie';
function createMapStateToProps() {
return createSelector(
(state) => state.addMovie,
+ (state) => state.movies.items.length,
(state) => state.router.location,
- (addMovie, location) => {
+ (addMovie, existingMoviesCount, location) => {
const { params } = parseUrl(location.search);
return {
+ ...addMovie,
term: params.term,
- ...addMovie
+ hasExistingMovies: existingMoviesCount > 0
};
}
);
diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js
index e110274e7..a5eac49a8 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js
@@ -55,8 +55,10 @@ class AddNewMovieModalContent extends Component {
rootFolderPath,
monitor,
qualityProfileId,
+ folder,
tags,
isSmallScreen,
+ isWindows,
onModalClose,
onInputChange
} = this.props;
@@ -97,6 +99,15 @@ class AddNewMovieModalContent extends Component {
@@ -180,8 +191,10 @@ AddNewMovieModalContent.propTypes = {
rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object,
+ folder: PropTypes.string.isRequired,
tags: PropTypes.object.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
+ isWindows: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired,
onInputChange: PropTypes.func.isRequired,
onAddMoviePress: PropTypes.func.isRequired
diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContentConnector.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContentConnector.js
index c86134c14..78691c862 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContentConnector.js
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContentConnector.js
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setAddMovieDefault, addMovie } from 'Store/Actions/addMovieActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
+import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import selectSettings from 'Store/Selectors/selectSettings';
import AddNewMovieModalContent from './AddNewMovieModalContent';
@@ -11,7 +12,8 @@ function createMapStateToProps() {
return createSelector(
(state) => state.addMovie,
createDimensionsSelector(),
- (addMovieState, dimensions) => {
+ createSystemStatusSelector(),
+ (addMovieState, dimensions, systemStatus) => {
const {
isAdding,
addError,
@@ -30,6 +32,7 @@ function createMapStateToProps() {
isSmallScreen: dimensions.isSmallScreen,
validationErrors,
validationWarnings,
+ isWindows: systemStatus.isWindows,
...settings
};
}
diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.js
index 1afdbb478..618894527 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.js
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.js
@@ -52,6 +52,7 @@ class AddNewMovieSearchResult extends Component {
status,
overview,
ratings,
+ folder,
images,
isExistingMovie,
isExclusionMovie,
@@ -148,6 +149,7 @@ class AddNewMovieSearchResult extends Component {
title={title}
year={year}
overview={overview}
+ folder={folder}
images={images}
onModalClose={this.onAddMovieModalClose}
/>
@@ -165,6 +167,7 @@ AddNewMovieSearchResult.propTypes = {
status: PropTypes.string.isRequired,
overview: PropTypes.string,
ratings: PropTypes.object.isRequired,
+ folder: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingMovie: PropTypes.bool.isRequired,
isExclusionMovie: PropTypes.bool.isRequired,
diff --git a/frontend/src/Components/Form/EnhancedSelectInput.js b/frontend/src/Components/Form/EnhancedSelectInput.js
index 80ee78e81..6c50c1c82 100644
--- a/frontend/src/Components/Form/EnhancedSelectInput.js
+++ b/frontend/src/Components/Form/EnhancedSelectInput.js
@@ -262,6 +262,7 @@ class EnhancedSelectInput extends Component {
isDisabled,
hasError,
hasWarning,
+ valueOptions,
selectedValueOptions,
selectedValueComponent: SelectedValueComponent,
optionComponent: OptionComponent
@@ -363,6 +364,7 @@ class EnhancedSelectInput extends Component {
key={v.key}
id={v.key}
isSelected={index === selectedIndex}
+ {...valueOptions}
{...v}
isMobile={false}
onSelect={this.onSelect}
@@ -404,6 +406,7 @@ class EnhancedSelectInput extends Component {
key={v.key}
id={v.key}
isSelected={index === selectedIndex}
+ {...valueOptions}
{...v}
isMobile={true}
onSelect={this.onSelect}
@@ -431,6 +434,7 @@ EnhancedSelectInput.propTypes = {
isDisabled: PropTypes.bool,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
+ valueOptions: PropTypes.object.isRequired,
selectedValueOptions: PropTypes.object.isRequired,
selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
optionComponent: PropTypes.elementType,
@@ -441,6 +445,7 @@ EnhancedSelectInput.defaultProps = {
className: styles.enhancedSelect,
disabledClassName: styles.isDisabled,
isDisabled: false,
+ valueOptions: {},
selectedValueOptions: {},
selectedValueComponent: HintedSelectInputSelectedValue,
optionComponent: HintedSelectInputOption
diff --git a/frontend/src/Components/Form/RootFolderSelectInputConnector.js b/frontend/src/Components/Form/RootFolderSelectInputConnector.js
index b3dfcbd20..b76501dc1 100644
--- a/frontend/src/Components/Form/RootFolderSelectInputConnector.js
+++ b/frontend/src/Components/Form/RootFolderSelectInputConnector.js
@@ -1,4 +1,3 @@
-import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
@@ -13,7 +12,7 @@ function createMapStateToProps() {
(state) => state.rootFolders,
(state, { includeNoChange }) => includeNoChange,
(rootFolders, includeNoChange) => {
- const values = _.map(rootFolders.items, (rootFolder) => {
+ const values = rootFolders.items.map((rootFolder) => {
return {
key: rootFolder.path,
value: rootFolder.path,
@@ -85,7 +84,7 @@ class RootFolderSelectInputConnector extends Component {
onChange
} = this.props;
- if (!value || !_.some(values, (v) => v.key === value) || value === ADD_NEW_KEY) {
+ if (!value || !values.some((v) => v.key === value) || value === ADD_NEW_KEY) {
const defaultValue = values[0];
if (defaultValue.key === ADD_NEW_KEY) {
diff --git a/frontend/src/Components/Form/RootFolderSelectInputOption.css b/frontend/src/Components/Form/RootFolderSelectInputOption.css
index d8b44fcad..ebc8ddc54 100644
--- a/frontend/src/Components/Form/RootFolderSelectInputOption.css
+++ b/frontend/src/Components/Form/RootFolderSelectInputOption.css
@@ -13,6 +13,15 @@
}
}
+.value {
+ display: flex;
+}
+
+.movieFolder {
+ flex: 0 0 auto;
+ color: $disabledColor;
+}
+
.freeSpace {
margin-left: 15px;
color: $darkGray;
diff --git a/frontend/src/Components/Form/RootFolderSelectInputOption.js b/frontend/src/Components/Form/RootFolderSelectInputOption.js
index a4db9cd82..40608d9de 100644
--- a/frontend/src/Components/Form/RootFolderSelectInputOption.js
+++ b/frontend/src/Components/Form/RootFolderSelectInputOption.js
@@ -7,14 +7,20 @@ import styles from './RootFolderSelectInputOption.css';
function RootFolderSelectInputOption(props) {
const {
+ id,
value,
freeSpace,
+ movieFolder,
isMobile,
+ isWindows,
...otherProps
} = props;
+ const slashCharacter = isWindows ? '\\' : '/';
+
return (
@@ -23,7 +29,18 @@ function RootFolderSelectInputOption(props) {
isMobile && styles.isMobile
)}
>
- {value}
+
+ {value}
+
+ {
+ movieFolder && id !== 'addNew' ?
+
+ {slashCharacter}
+ {movieFolder}
+
:
+ null
+ }
+
{
freeSpace != null &&
@@ -37,9 +54,12 @@ function RootFolderSelectInputOption(props) {
}
RootFolderSelectInputOption.propTypes = {
+ id: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
freeSpace: PropTypes.number,
- isMobile: PropTypes.bool.isRequired
+ movieFolder: PropTypes.string,
+ isMobile: PropTypes.bool.isRequired,
+ isWindows: PropTypes.bool
};
export default RootFolderSelectInputOption;
diff --git a/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.css b/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.css
index 6b0cf9e4f..fe036445b 100644
--- a/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.css
+++ b/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.css
@@ -7,10 +7,20 @@
overflow: hidden;
}
+.pathContainer {
+ @add-mixin truncate;
+ display: flex;
+ flex: 1 0 0;
+}
+
.path {
@add-mixin truncate;
+ flex: 0 1 auto;
+}
- flex: 1 0 0;
+.movieFolder {
+ flex: 0 1 auto;
+ color: $disabledColor;
}
.freeSpace {
diff --git a/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.js b/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.js
index ffd769254..803b8bee9 100644
--- a/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.js
+++ b/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.js
@@ -8,17 +8,32 @@ function RootFolderSelectInputSelectedValue(props) {
const {
value,
freeSpace,
+ movieFolder,
includeFreeSpace,
+ isWindows,
...otherProps
} = props;
+ const slashCharacter = isWindows ? '\\' : '/';
+
return (
-
- {value}
+
+
+ {value}
+
+
+ {
+ movieFolder ?
+
+ {slashCharacter}
+ {movieFolder}
+
:
+ null
+ }
{
@@ -34,6 +49,8 @@ function RootFolderSelectInputSelectedValue(props) {
RootFolderSelectInputSelectedValue.propTypes = {
value: PropTypes.string,
freeSpace: PropTypes.number,
+ movieFolder: PropTypes.string,
+ isWindows: PropTypes.bool,
includeFreeSpace: PropTypes.bool.isRequired
};
diff --git a/src/NzbDrone.Api/Movies/MovieResource.cs b/src/NzbDrone.Api/Movies/MovieResource.cs
index c89b08e26..6d3f86f11 100644
--- a/src/NzbDrone.Api/Movies/MovieResource.cs
+++ b/src/NzbDrone.Api/Movies/MovieResource.cs
@@ -227,19 +227,9 @@ public static Core.Movies.Movie ToModel(this MovieResource resource)
public static Core.Movies.Movie ToModel(this MovieResource resource, Core.Movies.Movie movie)
{
- movie.ImdbId = resource.ImdbId;
- movie.TmdbId = resource.TmdbId;
+ var updatedmovie = resource.ToModel();
- movie.Path = resource.Path;
- movie.ProfileId = resource.ProfileId;
- movie.PathState = resource.PathState;
-
- movie.Monitored = resource.Monitored;
- movie.MinimumAvailability = resource.MinimumAvailability;
-
- movie.RootFolderPath = resource.RootFolderPath;
- movie.Tags = resource.Tags;
- movie.AddOptions = resource.AddOptions;
+ movie.ApplyChanges(updatedmovie);
return movie;
}
diff --git a/src/NzbDrone.Common/Extensions/PathExtensions.cs b/src/NzbDrone.Common/Extensions/PathExtensions.cs
index 1a11fb7c4..bc7636cab 100644
--- a/src/NzbDrone.Common/Extensions/PathExtensions.cs
+++ b/src/NzbDrone.Common/Extensions/PathExtensions.cs
@@ -81,6 +81,15 @@ public static string GetParentPath(this string childPath)
return Directory.GetParent(cleanPath)?.FullName;
}
+ public static string GetCleanPath(this string path)
+ {
+ var cleanPath = OsInfo.IsWindows
+ ? PARENT_PATH_END_SLASH_REGEX.Replace(path, "")
+ : path.TrimEnd(Path.DirectorySeparatorChar);
+
+ return cleanPath;
+ }
+
public static bool IsParentPath(this string parentPath, string childPath)
{
if (parentPath != "/" && !parentPath.EndsWith(":\\"))
diff --git a/src/NzbDrone.Core/Movies/Movie.cs b/src/NzbDrone.Core/Movies/Movie.cs
index 111c12208..311127b1b 100644
--- a/src/NzbDrone.Core/Movies/Movie.cs
+++ b/src/NzbDrone.Core/Movies/Movie.cs
@@ -142,6 +142,22 @@ public override string ToString()
{
return string.Format("[{1} ({2})][{0}, {3}]", ImdbId, Title.NullSafe(), Year.NullSafe(), TmdbId);
}
+
+ public void ApplyChanges(Movie otherMovie)
+ {
+ TmdbId = otherMovie.TmdbId;
+
+ Path = otherMovie.Path;
+ ProfileId = otherMovie.ProfileId;
+ PathState = otherMovie.PathState;
+
+ Monitored = otherMovie.Monitored;
+ MinimumAvailability = otherMovie.MinimumAvailability;
+
+ RootFolderPath = otherMovie.RootFolderPath;
+ Tags = otherMovie.Tags;
+ AddOptions = otherMovie.AddOptions;
+ }
}
public enum MoviePathState
diff --git a/src/NzbDrone.Core/Movies/MovieEditedService.cs b/src/NzbDrone.Core/Movies/MovieEditedService.cs
deleted file mode 100644
index fcfc04361..000000000
--- a/src/NzbDrone.Core/Movies/MovieEditedService.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using NzbDrone.Core.Messaging.Commands;
-using NzbDrone.Core.Messaging.Events;
-using NzbDrone.Core.Movies.Commands;
-using NzbDrone.Core.Movies.Events;
-
-namespace NzbDrone.Core.Movies
-{
- public class MovieEditedService : IHandle
- {
- private readonly IManageCommandQueue _commandQueueManager;
-
- public MovieEditedService(IManageCommandQueue commandQueueManager)
- {
- _commandQueueManager = commandQueueManager;
- }
-
- public void Handle(MovieEditedEvent message)
- {
- if (message.Movie.ImdbId != message.OldMovie.ImdbId)
- {
- _commandQueueManager.Push(new RefreshMovieCommand(message.Movie.Id)); //Probably not needed, as metadata should stay the same.
- }
- }
- }
-}
diff --git a/src/Radarr.Api.V2/Movies/MovieFolderAsRootFolderValidator.cs b/src/Radarr.Api.V2/Movies/MovieFolderAsRootFolderValidator.cs
new file mode 100644
index 000000000..6ce918f0b
--- /dev/null
+++ b/src/Radarr.Api.V2/Movies/MovieFolderAsRootFolderValidator.cs
@@ -0,0 +1,39 @@
+using System;
+using System.IO;
+using FluentValidation.Validators;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Core.Organizer;
+
+namespace Radarr.Api.V2.Movies
+{
+ public class MovieFolderAsRootFolderValidator : PropertyValidator
+ {
+ private readonly IBuildFileNames _fileNameBuilder;
+
+ public MovieFolderAsRootFolderValidator(IBuildFileNames fileNameBuilder)
+ : base("Root folder path contains movie folder")
+ {
+ _fileNameBuilder = fileNameBuilder;
+ }
+
+ protected override bool IsValid(PropertyValidatorContext context)
+ {
+ if (context.PropertyValue == null) return true;
+
+ var movieResource = context.Instance as MovieResource;
+
+ if (movieResource == null) return true;
+
+ var rootFolderPath = context.PropertyValue.ToString();
+ var rootFolder = new DirectoryInfo(rootFolderPath).Name;
+ var movie = movieResource.ToModel();
+ var movieFolder = _fileNameBuilder.GetMovieFolder(movie);
+
+ if (movieFolder == rootFolder) return false;
+
+ var distance = movieFolder.LevenshteinDistance(rootFolder);
+
+ return distance >= Math.Max(1, movieFolder.Length * 0.2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Radarr.Api.V2/Movies/MovieLookupModule.cs b/src/Radarr.Api.V2/Movies/MovieLookupModule.cs
index bf850b891..206cc8e51 100644
--- a/src/Radarr.Api.V2/Movies/MovieLookupModule.cs
+++ b/src/Radarr.Api.V2/Movies/MovieLookupModule.cs
@@ -8,6 +8,7 @@
using System;
using Radarr.Http;
using Radarr.Http.REST;
+using NzbDrone.Core.Organizer;
namespace Radarr.Api.V2.Movies
{
@@ -15,12 +16,14 @@ public class MovieLookupModule : RadarrRestModule
{
private readonly ISearchForNewMovie _searchProxy;
private readonly IProvideMovieInfo _movieInfo;
+ private readonly IBuildFileNames _fileNameBuilder;
- public MovieLookupModule(ISearchForNewMovie searchProxy, IProvideMovieInfo movieInfo)
+ public MovieLookupModule(ISearchForNewMovie searchProxy, IProvideMovieInfo movieInfo, IBuildFileNames fileNameBuilder)
: base("/movie/lookup")
{
_movieInfo = movieInfo;
_searchProxy = searchProxy;
+ _fileNameBuilder = fileNameBuilder;
Get("/", x => Search());
Get("/tmdb", x => SearchByTmdbId());
Get("/imdb", x => SearchByImdbId());
@@ -51,17 +54,19 @@ private object Search()
return MapToResource(imdbResults);
}
- private static IEnumerable MapToResource(IEnumerable movies)
+ private IEnumerable MapToResource(IEnumerable movies)
{
- foreach (var currentSeries in movies)
+ foreach (var currentMovie in movies)
{
- var resource = currentSeries.ToResource();
- var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
+ var resource = currentMovie.ToResource();
+ var poster = currentMovie.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
{
resource.RemotePoster = poster.Url;
}
+ resource.Folder = _fileNameBuilder.GetMovieFolder(currentMovie);
+
yield return resource;
}
}
diff --git a/src/Radarr.Api.V2/Movies/MovieModule.cs b/src/Radarr.Api.V2/Movies/MovieModule.cs
index 413979807..14a35caed 100644
--- a/src/Radarr.Api.V2/Movies/MovieModule.cs
+++ b/src/Radarr.Api.V2/Movies/MovieModule.cs
@@ -38,7 +38,8 @@ public MovieModule(IBroadcastSignalRMessage signalRBroadcaster,
MoviePathValidator moviesPathValidator,
MovieExistsValidator moviesExistsValidator,
MovieAncestorValidator moviesAncestorValidator,
- ProfileExistsValidator profileExistsValidator
+ ProfileExistsValidator profileExistsValidator,
+ MovieFolderAsRootFolderValidator movieFolderAsRootFolderValidator
)
: base(signalRBroadcaster)
{
@@ -65,7 +66,10 @@ ProfileExistsValidator profileExistsValidator
SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(profileExistsValidator);
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
- PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
+ PostValidator.RuleFor(s => s.RootFolderPath)
+ .IsValidPath()
+ .SetValidator(movieFolderAsRootFolderValidator)
+ .When(s => s.Path.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.Title).NotEmpty();
PostValidator.RuleFor(s => s.TmdbId).NotNull().NotEmpty().SetValidator(moviesExistsValidator);
diff --git a/src/Radarr.Api.V2/Movies/MovieResource.cs b/src/Radarr.Api.V2/Movies/MovieResource.cs
index c6055ae4d..0149bab11 100644
--- a/src/Radarr.Api.V2/Movies/MovieResource.cs
+++ b/src/Radarr.Api.V2/Movies/MovieResource.cs
@@ -58,6 +58,7 @@ public MovieResource()
public int TmdbId { get; set; }
public string TitleSlug { get; set; }
public string RootFolderPath { get; set; }
+ public string Folder { get; set; }
public string Certification { get; set; }
public List Genres { get; set; }
public HashSet Tags { get; set; }
@@ -178,19 +179,9 @@ public static Movie ToModel(this MovieResource resource)
public static Movie ToModel(this MovieResource resource, Movie movie)
{
- movie.ImdbId = resource.ImdbId;
- movie.TmdbId = resource.TmdbId;
+ var updatedmovie = resource.ToModel();
- movie.Path = resource.Path;
- movie.ProfileId = resource.QualityProfileId;
- movie.PathState = resource.PathState;
-
- movie.Monitored = resource.Monitored;
- movie.MinimumAvailability = resource.MinimumAvailability;
-
- movie.RootFolderPath = resource.RootFolderPath;
- movie.Tags = resource.Tags;
- movie.AddOptions = resource.AddOptions;
+ movie.ApplyChanges(updatedmovie);
return movie;
}
diff --git a/src/Radarr.Api.V2/RootFolders/RootFolderResource.cs b/src/Radarr.Api.V2/RootFolders/RootFolderResource.cs
index 719e297b3..c8dbab65b 100644
--- a/src/Radarr.Api.V2/RootFolders/RootFolderResource.cs
+++ b/src/Radarr.Api.V2/RootFolders/RootFolderResource.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using NzbDrone.Common.Extensions;
using NzbDrone.Core.RootFolders;
using Radarr.Http.REST;
@@ -23,7 +24,7 @@ public static RootFolderResource ToResource(this RootFolder model)
{
Id = model.Id,
- Path = model.Path,
+ Path = model.Path.GetCleanPath(),
FreeSpace = model.FreeSpace,
UnmappedFolders = model.UnmappedFolders
};
@@ -37,7 +38,7 @@ public static RootFolder ToModel(this RootFolderResource resource)
{
Id = resource.Id,
- Path = resource.Path,
+ Path = resource.Path
//FreeSpace
//UnmappedFolders
};