Enable automatic renaming, according to naming scheme, of movie folder after creation of the movie. (#1349)

Please test everything you can about this and report back if everything still works correctly.
pull/2/head
Leonardo Galli 7 years ago committed by GitHub
parent f1e8a9acfc
commit 7db92c6bcf

@ -11,6 +11,8 @@ namespace NzbDrone.Api.Config
public bool AutoDownloadPropers { get; set; }
public bool CreateEmptySeriesFolders { get; set; }
public FileDateType FileDate { get; set; }
public bool AutoRenameFolders { get; set; }
public bool PathsDefaultStatic { get; set; }
public bool SetPermissionsLinux { get; set; }
public string FileChmod { get; set; }
@ -35,6 +37,8 @@ namespace NzbDrone.Api.Config
AutoDownloadPropers = model.AutoDownloadPropers,
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
FileDate = model.FileDate,
AutoRenameFolders = model.AutoRenameFolders,
PathsDefaultStatic = model.PathsDefaultStatic,
SetPermissionsLinux = model.SetPermissionsLinux,
FileChmod = model.FileChmod,

@ -46,11 +46,14 @@ namespace NzbDrone.Api.EpisodeFiles
return movie.ToResource();
}
private void SetQuality(MovieFileResource movieFileResource)
{
{
var movieFile = _mediaFileService.GetMovie(movieFileResource.Id);
movieFile.Quality = movieFileResource.Quality;
_mediaFileService.Update(movieFile);
BroadcastResourceChange(ModelAction.Updated, movieFile.Id);
}
private void DeleteMovieFile(int id)

@ -40,6 +40,7 @@ namespace NzbDrone.Api.Movie
//View & Edit
public string Path { get; set; }
public int ProfileId { get; set; }
public MoviePathState PathState { get; set; }
//Editing Only
public bool Monitored { get; set; }
@ -131,6 +132,7 @@ namespace NzbDrone.Api.Movie
Path = model.Path,
ProfileId = model.ProfileId,
PathState = model.PathState,
Monitored = model.Monitored,
MinimumAvailability = model.MinimumAvailability,
@ -187,6 +189,7 @@ namespace NzbDrone.Api.Movie
Path = resource.Path,
ProfileId = resource.ProfileId,
PathState = resource.PathState,
Monitored = resource.Monitored,
MinimumAvailability = resource.MinimumAvailability,
@ -217,6 +220,7 @@ namespace NzbDrone.Api.Movie
movie.Path = resource.Path;
movie.ProfileId = resource.ProfileId;
movie.PathState = resource.PathState;
movie.Monitored = resource.Monitored;
movie.MinimumAvailability = resource.MinimumAvailability;

@ -296,6 +296,7 @@
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
<Compile Include="OrganizerTests\GetMovieFolderFixture.cs" />
<Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" />
<Compile Include="ParserTests\RomanNumeralTests\RomanNumeralConversionFixture.cs" />
<Compile Include="Qualities\RevisionComparableFixture.cs" />

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
@ -734,5 +734,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(releaseGroup);
}
}
}

@ -0,0 +1,40 @@
using NUnit.Framework;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Test.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NzbDrone.Core.Tv;
using FluentAssertions;
namespace NzbDrone.Core.Test.OrganizerTests
{
[TestFixture]
public class GetMovieFolderFixture : CoreTest<FileNameBuilder>
{
private NamingConfig namingConfig;
[SetUp]
public void Setup()
{
namingConfig = NamingConfig.Default;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(namingConfig);
}
[TestCase("Arrival", 2016, "{Movie Title} ({Release Year})", "Arrival (2016)")]
[TestCase("The Big Short", 2015, "{Movie TitleThe} ({Release Year})", "Big Short, The (2015)")]
[TestCase("The Big Short", 2015, "{Movie Title} ({Release Year})", "The Big Short (2015)")]
public void should_use_movieFolderFormat_to_build_folder_name(string movieTitle, int year, string format, string expected)
{
namingConfig.MovieFolderFormat = format;
var movie = new Movie { Title = movieTitle, Year = year };
Subject.GetMovieFolder(movie).Should().Be(expected);
}
}
}

@ -294,6 +294,20 @@ namespace NzbDrone.Core.Configuration
set { SetValue("ExtraFileExtensions", value); }
}
public bool AutoRenameFolders
{
get { return GetValueBoolean("AutoRenameFolders", false); }
set { SetValue("AutoRenameFolders", value); }
}
public bool PathsDefaultStatic
{
get { return GetValueBoolean("PathsDefaultStatic", true); }
set { SetValue("PathsDefaultStatic", value); }
}
public bool SetPermissionsLinux
{
get { return GetValueBoolean("SetPermissionsLinux", false); }

@ -33,6 +33,8 @@ namespace NzbDrone.Core.Configuration
bool CopyUsingHardlinks { get; set; }
bool EnableMediaInfo { get; set; }
string ExtraFileExtensions { get; set; }
bool AutoRenameFolders { get; set; }
bool PathsDefaultStatic { get; set; }
//Permissions (Media Management)
bool SetPermissionsLinux { get; set; }

@ -0,0 +1,16 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(136)]
public class add_pathstate_to_movies : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Movies").AddColumn("PathState").AsInt32().WithDefaultValue(2);
}
}
}

@ -0,0 +1,20 @@
using NzbDrone.Core.Messaging.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class RenameMovieFolderCommand : Command
{
public List<int> MovieIds { get; set; }
public override bool SendUpdatesToClient => true;
public RenameMovieFolderCommand(List<int> ids)
{
MovieIds = ids;
}
}
}

@ -45,6 +45,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IEventAggregator _eventAggregator;
private readonly IMovieService _movieService;
private readonly IMovieFileRepository _movieFileRepository;
private readonly IRenameMovieFileService _renameMovieFiles;
private readonly Logger _logger;
public DiskScanService(IDiskProvider diskProvider,
@ -57,6 +58,7 @@ namespace NzbDrone.Core.MediaFiles
IEventAggregator eventAggregator,
IMovieService movieService,
IMovieFileRepository movieFileRepository,
IRenameMovieFileService renameMovieFiles,
Logger logger)
{
_diskProvider = diskProvider;
@ -69,6 +71,7 @@ namespace NzbDrone.Core.MediaFiles
_eventAggregator = eventAggregator;
_movieService = movieService;
_movieFileRepository = movieFileRepository;
_renameMovieFiles = renameMovieFiles;
_logger = logger;
}
@ -135,6 +138,9 @@ namespace NzbDrone.Core.MediaFiles
public void Scan(Movie movie)
{
//Try renaming the movie path in case anything changed such as year, title or something else.
_renameMovieFiles.RenameMoviePath(movie, true);
var rootFolder = _diskProvider.GetParentFolder(movie.Path);
if (!_diskProvider.FolderExists(rootFolder))

@ -0,0 +1,18 @@
using NzbDrone.Common.Messaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Events
{
public class MovieFileUpdatedEvent : IEvent
{
public MovieFile MovieFile { get; private set; }
public MovieFileUpdatedEvent(MovieFile movieFile)
{
MovieFile = movieFile;
}
}
}

@ -30,6 +30,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _configService;
private readonly Logger _logger;
@ -40,6 +41,7 @@ namespace NzbDrone.Core.MediaFiles
IDiskTransferService diskTransferService,
IDiskProvider diskProvider,
IMediaFileAttributeService mediaFileAttributeService,
IRecycleBinProvider recycleBinProvider,
IEventAggregator eventAggregator,
IConfigService configService,
Logger logger)
@ -50,6 +52,7 @@ namespace NzbDrone.Core.MediaFiles
_diskTransferService = diskTransferService;
_diskProvider = diskProvider;
_mediaFileAttributeService = mediaFileAttributeService;
_recycleBinProvider = recycleBinProvider;
_eventAggregator = eventAggregator;
_configService = configService;
_logger = logger;
@ -116,6 +119,15 @@ namespace NzbDrone.Core.MediaFiles
_diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode);
var oldMoviePath = movie.Path;
var newMoviePath = new OsPath(destinationFilePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar);
movie.Path = newMoviePath;
if (oldMoviePath != newMoviePath)
{
_movieService.UpdateMovie(movie);
}
movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath);
_updateMovieFileService.ChangeFileDateForFile(movieFile, movie);
@ -132,6 +144,14 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
if(oldMoviePath != newMoviePath)
{
if (_diskProvider.GetFiles(oldMoviePath, SearchOption.AllDirectories).Count() == 0)
{
_recycleBinProvider.DeleteFolder(oldMoviePath);
}
}
return movieFile;
}
@ -143,7 +163,9 @@ namespace NzbDrone.Core.MediaFiles
private void EnsureMovieFolder(MovieFile movieFile, Movie movie, string filePath)
{
var movieFolder = Path.GetDirectoryName(filePath);
movie.Path = movieFolder;
var rootFolder = new OsPath(movieFolder).Directory.FullPath;
var fileName = Path.GetFileName(filePath);
if (!_diskProvider.FolderExists(rootFolder))
{
@ -156,7 +178,7 @@ namespace NzbDrone.Core.MediaFiles
if (!_diskProvider.FolderExists(movieFolder))
{
CreateFolder(movieFolder);
newEvent.SeriesFolder = movieFolder;
newEvent.MovieFolder = movieFolder;
changed = true;
}

@ -13,23 +13,29 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.MediaFiles
{
public interface IRenameMovieFileService
{
List<RenameMovieFilePreview> GetRenamePreviews(int movieId);
void RenameMoviePath(Movie movie, bool shouldRenameFiles);
}
public class RenameMovieFileService : IRenameMovieFileService,
IExecute<RenameMovieFilesCommand>,
IExecute<RenameMovieCommand>
IExecute<RenameMovieCommand>,
IExecute<RenameMovieFolderCommand>
{
private readonly IMovieService _movieService;
private readonly IMediaFileService _mediaFileService;
private readonly IMoveMovieFiles _movieFileMover;
private readonly IEventAggregator _eventAggregator;
private readonly IBuildFileNames _filenameBuilder;
private readonly IConfigService _configService;
private readonly IDiskProvider _diskProvider;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly Logger _logger;
public RenameMovieFileService(IMovieService movieService,
@ -37,6 +43,9 @@ namespace NzbDrone.Core.MediaFiles
IMoveMovieFiles movieFileMover,
IEventAggregator eventAggregator,
IBuildFileNames filenameBuilder,
IConfigService configService,
IRecycleBinProvider recycleBinProvider,
IDiskProvider diskProvider,
Logger logger)
{
_movieService = movieService;
@ -44,6 +53,9 @@ namespace NzbDrone.Core.MediaFiles
_movieFileMover = movieFileMover;
_eventAggregator = eventAggregator;
_filenameBuilder = filenameBuilder;
_configService = configService;
_recycleBinProvider = recycleBinProvider;
_diskProvider = diskProvider;
_logger = logger;
}
@ -71,8 +83,9 @@ namespace NzbDrone.Core.MediaFiles
{
MovieId = movie.Id,
MovieFileId = file.Id,
ExistingPath = file.RelativePath,
NewPath = movie.Path.GetRelativePath(newPath)
ExistingPath = movieFilePath,
//NewPath = movie.Path.GetRelativePath(newPath)
NewPath = newPath
};
}
@ -80,13 +93,19 @@ namespace NzbDrone.Core.MediaFiles
}
private void RenameFiles(List<MovieFile> movieFiles, Movie movie)
private void RenameFiles(List<MovieFile> movieFiles, Movie movie, string oldMoviePath = null)
{
var renamed = new List<MovieFile>();
foreach(var movieFile in movieFiles)
if (oldMoviePath == null)
{
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath);
oldMoviePath = movie.Path;
}
foreach (var movieFile in movieFiles)
{
var oldMovieFilePath = Path.Combine(oldMoviePath, movieFile.RelativePath);
movieFile.Path = oldMovieFilePath;
try
{
@ -94,22 +113,64 @@ namespace NzbDrone.Core.MediaFiles
_movieFileMover.MoveMovieFile(movieFile, movie);
_mediaFileService.Update(movieFile);
_movieService.UpdateMovie(movie);
renamed.Add(movieFile);
_logger.Debug("Renamed movie file: {0}", movieFile);
}
catch(SameFilenameException ex)
catch (SameFilenameException ex)
{
_logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
}
catch(Exception ex)
catch (Exception ex)
{
_logger.Error(ex, "Failed to rename file: " + movieFilePath);
_logger.Error(ex, "Failed to rename file: " + oldMovieFilePath);
}
}
}
public void RenameMoviePath(Movie movie, bool shouldRenameFiles = true)
{
var newFolder = _filenameBuilder.BuildMoviePath(movie);
if (newFolder != movie.Path && movie.PathState == MoviePathState.Dynamic)
{
if (!_configService.AutoRenameFolders)
{
_logger.Info("{0}'s movie should be {1} according to your naming config.", movie, newFolder);
return;
}
_logger.Info("{0}'s movie folder changed to: {1}", movie, newFolder);
var oldFolder = movie.Path;
movie.Path = newFolder;
if (shouldRenameFiles)
{
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
_logger.ProgressInfo("Renaming movie files for {0}", movie.Title);
RenameFiles(movieFiles, movie, oldFolder);
_logger.ProgressInfo("All movie files renamed for {0}", movie.Title);
}
_movieService.UpdateMovie(movie);
if (_diskProvider.GetFiles(oldFolder, SearchOption.AllDirectories).Count() == 0)
{
_recycleBinProvider.DeleteFolder(oldFolder);
}
}
if (movie.PathState == MoviePathState.StaticOnce)
{
movie.PathState = MoviePathState.Dynamic;
_movieService.UpdateMovie(movie);
}
}
public void Execute(RenameMovieFilesCommand message)
{
var movie = _movieService.GetMovie(message.MovieId);
@ -134,5 +195,17 @@ namespace NzbDrone.Core.MediaFiles
}
}
public void Execute(RenameMovieFolderCommand message)
{
_logger.Debug("Renaming movie folder for selected movie if necessary");
var moviesToRename = _movieService.GetMovies(message.MovieIds);
foreach(var movie in moviesToRename)
{
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
_logger.ProgressInfo("Renaming movie folder for {0}", movie.Title);
RenameMoviePath(movie);
}
}
}
}

@ -18,6 +18,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IMediaFileService _mediaFileService;
private readonly IMoveEpisodeFiles _episodeFileMover;
private readonly IMoveMovieFiles _movieFileMover;
private readonly IRenameMovieFileService _movieFileRenamer;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
@ -26,6 +27,7 @@ namespace NzbDrone.Core.MediaFiles
IMoveEpisodeFiles episodeFileMover,
IMoveMovieFiles movieFileMover,
IDiskProvider diskProvider,
IRenameMovieFileService movieFileRenamer,
Logger logger)
{
_recycleBinProvider = recycleBinProvider;
@ -33,6 +35,7 @@ namespace NzbDrone.Core.MediaFiles
_episodeFileMover = episodeFileMover;
_movieFileMover = movieFileMover;
_diskProvider = diskProvider;
_movieFileRenamer = movieFileRenamer;
_logger = logger;
}
@ -57,6 +60,10 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileService.Delete(existingFile, DeleteMediaFileReason.Upgrade);
}
//Temporary for correctly getting path
localMovie.Movie.MovieFileId = 1;
localMovie.Movie.MovieFile = movieFile;
if (copyOnly)
{
moveFileResult.MovieFile = _movieFileMover.CopyMovieFile(movieFile, localMovie);
@ -66,6 +73,8 @@ namespace NzbDrone.Core.MediaFiles
moveFileResult.MovieFile = _movieFileMover.MoveMovieFile(movieFile, localMovie);
}
//_movieFileRenamer.RenameMoviePath(localMovie.Movie, false);
return moveFileResult;
}

@ -125,6 +125,7 @@
<Compile Include="Authentication\UserRepository.cs" />
<Compile Include="Authentication\UserService.cs" />
<Compile Include="Datastore\Migration\123_create_netimport_table.cs" />
<Compile Include="MediaFiles\Events\MovieFileUpdatedEvent.cs" />
<Compile Include="Datastore\Migration\134_add_remux_qualities_for_the_wankers.cs" />
<Compile Include="Datastore\Migration\129_add_parsed_movie_info_to_pending_release.cs" />
<Compile Include="Datastore\Migration\128_remove_kickass.cs" />
@ -1275,7 +1276,9 @@
<Compile Include="Datastore\Migration\131_make_parsed_episode_info_nullable.cs" />
<Compile Include="Housekeeping\Housekeepers\FixWronglyMatchedMovieFiles.cs" />
<Compile Include="Datastore\Migration\135_add_haspredbentry_to_movies.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieFolderCommand.cs" />
<Compile Include="Tv\QueryExtensions.cs" />
<Compile Include="Datastore\Migration\136_add_pathstate_to_movies.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">

@ -20,6 +20,7 @@ namespace NzbDrone.Core.Organizer
string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null);
string BuildFilePath(Movie movie, string fileName, string extension);
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
string BuildMoviePath(Movie movie, NamingConfig namingConfig = null);
string BuildSeasonPath(Series series, int seasonNumber);
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
@ -158,12 +159,11 @@ namespace NzbDrone.Core.Organizer
return GetOriginalTitle(movieFile);
}
//TODO: Update namingConfig for Movies!
var pattern = namingConfig.StandardMovieFormat;
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year
AddReleaseDateTokens(tokenHandlers, movie.Year);
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
AddQualityTokens(tokenHandlers, movie, movieFile);
AddMediaInfoTokens(tokenHandlers, movieFile);
@ -190,11 +190,61 @@ namespace NzbDrone.Core.Organizer
{
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
var path = movie.Path;
var path = "";
if (movie.PathState > 0)
{
path = movie.Path;
}
else
{
path = BuildMoviePath(movie);
}
return Path.Combine(path, fileName + extension);
}
public string BuildMoviePath(Movie movie, NamingConfig namingConfig = null)
{
if (namingConfig == null)
{
namingConfig = _namingConfigService.GetConfig();
}
var path = movie.Path;
var directory = new DirectoryInfo(path).Name;
var parentDirectoryPath = new DirectoryInfo(path).Parent.FullName;
var movieFile = movie.MovieFile;
var pattern = namingConfig.MovieFolderFormat;
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year);
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
if(movie.MovieFileId != 0)
{
movieFile.LazyLoad();
AddQualityTokens(tokenHandlers, movie, movieFile);
AddMediaInfoTokens(tokenHandlers, movieFile);
AddMovieFileTokens(tokenHandlers, movieFile);
AddTagsTokens(tokenHandlers, movieFile);
}
else
{
AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}" });
}
var directoryName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
directoryName = FileNameCleanupRegex.Replace(directoryName, match => match.Captures[0].Value[0].ToString());
directoryName = TrimSeparatorsRegex.Replace(directoryName, string.Empty);
return Path.Combine(parentDirectoryPath, directoryName);
}
public string BuildSeasonPath(Series series, int seasonNumber)
{
var path = series.Path;
@ -302,12 +352,28 @@ namespace NzbDrone.Core.Organizer
namingConfig = _namingConfigService.GetConfig();
}
var movieFile = movie.MovieFile;
var pattern = namingConfig.MovieFolderFormat;
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year);
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
if (movie.MovieFileId != 0)
{
movieFile.LazyLoad();
AddQualityTokens(tokenHandlers, movie, movieFile);
AddMediaInfoTokens(tokenHandlers, movieFile);
AddMovieFileTokens(tokenHandlers, movieFile);
AddTagsTokens(tokenHandlers, movieFile);
}
else
{
AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}"});
}
return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig));
}

@ -44,13 +44,6 @@ namespace NzbDrone.Core.Organizer
{
_buildFileNames = buildFileNames;
_movie = new Movie
{
Title = "The Movie Title",
Year = 2010,
ImdbId = "tt0066921"
};
_standardSeries = new Series
{
SeriesType = SeriesTypes.Standard,
@ -122,13 +115,22 @@ namespace NzbDrone.Core.Organizer
_movieFile = new MovieFile
{
Quality = new QualityModel(Quality.Bluray1080p, new Revision(2)),
RelativePath = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE.mkv",
SceneName = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE",
ReleaseGroup = "RlsGrp",
RelativePath = "The.Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE.mkv",
SceneName = "The.Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE",
ReleaseGroup = "EVOLVE",
MediaInfo = mediaInfo,
Edition = "Ultimate extended edition",
};
_movie = new Movie
{
Title = "The Movie: Title",
Year = 2010,
ImdbId = "tt0066921",
MovieFile = _movieFile,
MovieFileId = 1,
};
_singleEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.HDTV720p, new Revision(2)),

@ -41,6 +41,7 @@ namespace NzbDrone.Core.Tv
public List<Actor> Actors { get; set; }
public string Certification { get; set; }
public string RootFolderPath { get; set; }
public MoviePathState PathState { get; set; }
public DateTime Added { get; set; }
public DateTime? InCinemas { get; set; }
public DateTime? PhysicalRelease { get; set; }
@ -117,4 +118,11 @@ namespace NzbDrone.Core.Tv
{
public bool SearchForMovie { get; set; }
}
public enum MoviePathState
{
Dynamic,
StaticOnce,
Static,
}
}

@ -150,13 +150,23 @@ namespace NzbDrone.Core.Tv
{
Ensure.That(newMovie, () => newMovie).IsNotNull();
MoviePathState defaultState = MoviePathState.Static;
if (!_configService.PathsDefaultStatic)
{
defaultState = MoviePathState.Dynamic;
}
if (string.IsNullOrWhiteSpace(newMovie.Path))
{
var folderName = _fileNameBuilder.GetMovieFolder(newMovie);
newMovie.Path = Path.Combine(newMovie.RootFolderPath, folderName);
newMovie.PathState = defaultState;
}
else
{
newMovie.PathState = defaultState == MoviePathState.Dynamic ? MoviePathState.StaticOnce : MoviePathState.Static;
}
_logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path);
_logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path);
newMovie.CleanTitle = newMovie.Title.CleanSeriesTitle();
newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.TmdbId);
@ -174,10 +184,20 @@ namespace NzbDrone.Core.Tv
newMovies.ForEach(m =>
{
MoviePathState defaultState = MoviePathState.Static;
if (!_configService.PathsDefaultStatic)
{
defaultState = MoviePathState.Dynamic;
}
if (string.IsNullOrWhiteSpace(m.Path))
{
var folderName = _fileNameBuilder.GetMovieFolder(m);
m.Path = Path.Combine(m.RootFolderPath, folderName);
m.PathState = defaultState;
}
else
{
m.PathState = defaultState == MoviePathState.Dynamic ? MoviePathState.StaticOnce : MoviePathState.Static;
}
m.CleanTitle = m.Title.CleanSeriesTitle();

@ -13,6 +13,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Tv.Commands;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.MediaFiles.Commands;
namespace NzbDrone.Core.Tv
{
@ -22,6 +23,7 @@ namespace NzbDrone.Core.Tv
private readonly IMovieService _movieService;
private readonly IRefreshEpisodeService _refreshEpisodeService;
private readonly IEventAggregator _eventAggregator;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IDiskScanService _diskScanService;
private readonly ICheckIfMovieShouldBeRefreshed _checkIfMovieShouldBeRefreshed;
private readonly Logger _logger;
@ -32,12 +34,14 @@ namespace NzbDrone.Core.Tv
IEventAggregator eventAggregator,
IDiskScanService diskScanService,
ICheckIfMovieShouldBeRefreshed checkIfMovieShouldBeRefreshed,
IManageCommandQueue commandQueue,
Logger logger)
{
_movieInfo = movieInfo;
_movieService = movieService;
_refreshEpisodeService = refreshEpisodeService;
_eventAggregator = eventAggregator;
_commandQueueManager = commandQueue;
_diskScanService = diskScanService;
_checkIfMovieShouldBeRefreshed = checkIfMovieShouldBeRefreshed;
_logger = logger;
@ -136,6 +140,7 @@ namespace NzbDrone.Core.Tv
try
{
_logger.Info("Skipping refresh of movie: {0}", movie.Title);
_commandQueueManager.Push(new RenameMovieFolderCommand(new List<int>{movie.Id}));
_diskScanService.Scan(movie);
}
catch (Exception e)

@ -47,27 +47,29 @@
</div>
</div>
<!--<div class="form-group">
<label class="col-sm-4 control-label">Use Season Folder</label>
<div class="form-group">
<label class="col-sm-4 control-label">Static Path</label>
<div class="col-sm-8">
<div class="col-sm-6">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="seasonFolder"/>
<input type="checkbox" name="pathState"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
<div class="btn btn-primary slide-button">
</div>
</label>
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Should downloaded episodes be stored in season folders?"/>
<i class="icon-sonarr-form-info" title="Should movie path stay static or should it change on each disk scan according to your naming config? Note: Auto Rename Folders under Settings -&gt; Media Management must be enabled too."/>
</span>
</div>
</div>
</div>-->
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Profile</label>

@ -22,6 +22,12 @@ var view = Marionette.ItemView.extend({
initialize : function() {
this.model.set('profiles', Profiles);
var pathState = this.model.get("pathState");
if (pathState == "static") {
this.model.set("pathState", true);
} else {
this.model.set("pathState", false);
}
},
onRender : function() {
@ -30,11 +36,18 @@ var view = Marionette.ItemView.extend({
model : this.model,
property : 'tags'
});
},
_onBeforeSave : function() {
var profileId = this.ui.profile.val();
this.model.set({ profileId : profileId });
var pathState = this.model.get("pathState");
if (pathState === true) {
this.model.set("pathState", "static");
} else {
this.model.set("pathState", "dynamic");
}
},
_onAfterSave : function() {

@ -15,7 +15,7 @@ module.exports = Marionette.ItemView.extend({
monitored : '.x-monitored',
profile : '.x-profiles',
minimumAvailability : '.x-minimumavailability',
seasonFolder : '.x-season-folder',
staticPath : '.x-static-path',
rootFolder : '.x-root-folder',
selectedCount : '.x-selected-count',
container : '.series-editor-footer',
@ -52,7 +52,7 @@ module.exports = Marionette.ItemView.extend({
this.listenTo(FullMovieCollection, 'save', function() {
window.alert(' Done Saving');
var selected = FullMovieCollection.where({ selected : true });
});
@ -71,7 +71,7 @@ module.exports = Marionette.ItemView.extend({
var monitored = this.ui.monitored.val();
var minAvail = this.ui.minimumAvailability.val();
var profile = this.ui.profile.val();
var seasonFolder = this.ui.seasonFolder.val();
var staticPath = this.ui.staticPath.val();
var rootFolder = this.ui.rootFolder.val();
var i = 0;
@ -94,10 +94,8 @@ module.exports = Marionette.ItemView.extend({
model.set('profileId', parseInt(profile, 10));
}
if (seasonFolder === 'true') {
model.set('seasonFolder', true);
} else if (seasonFolder === 'false') {
model.set('seasonFolder', false);
if (staticPath !== 'noChange') {
model.set('pathState', staticPath);
}
if (rootFolder !== 'noChange') {
@ -129,10 +127,8 @@ module.exports = Marionette.ItemView.extend({
m.set('profileId', parseInt(profile, 10));
}
if (seasonFolder === 'true') {
m.set('seasonFolder', true);
} else if (seasonFolder === 'false') {
m.set('seasonFolder', false);
if (staticPath !== 'noChange') {
m.set('pathState', staticPath);
}
if (rootFolder !== 'noChange') {

@ -1,6 +1,6 @@
<div class="series-editor-footer">
<div class="row">
<div class="form-group col-md-2">
<div class="form-group col-md-1">
<label>Monitored</label>
<select class="form-control x-action x-monitored">
@ -33,15 +33,15 @@
</select>
</div>
{{!--<div class="form-group col-md-2">
<label>Season Folder</label>
<div class="form-group col-md-2">
<label>Static Path</label>
<select class="form-control x-action x-season-folder">
<select class="form-control x-action x-static-path">
<option value="noChange">No change</option>
<option value="true">Yes</option>
<option value="false">No</option>
<option value="static">Yes</option>
<option value="dynamic">No</option>
</select>
</div>--}}
</div>
<div class="form-group col-md-3">
<label>Root Folder</label>
@ -55,7 +55,7 @@
</select>
</div>
<div class="form-group col-md-3 actions">
<div class="form-group col-md-2 actions">
<label class="x-selected-count">0 movies selected</label>
<div>
<button class="btn btn-primary x-action x-save">Save</button>

@ -165,7 +165,7 @@ module.exports = Marionette.Layout.extend({
onRender : function() {
//this._showToolbar();
//this._showTable();
//this._showPager();
//this._showPager();
//if (window.shownOnce){
// this.movieCollection.fetch();
//}

@ -48,6 +48,7 @@ var view = Marionette.ItemView.extend({
_onAfterSave : function() {
this.trigger('saved');
vent.trigger(vent.Commands.MovieFileEdited);
vent.trigger(vent.Commands.CloseModalCommand);
},

@ -87,7 +87,7 @@ module.exports = Marionette.Layout.extend({
}
});
vent.on(vent.Commands.CloseModalCommand, this._refreshClose, this);
vent.on(vent.Commands.MovieFileEdited, this._showGrid, this);
},
_refresh : function(model) {
@ -105,7 +105,7 @@ module.exports = Marionette.Layout.extend({
this.filesCollection = new FilesCollection();
var file = this.movie.model.get("movieFile");
this.filesCollection.add(file);
this.onShow();
this._showGrid();
},
onShow : function() {
@ -117,6 +117,15 @@ module.exports = Marionette.Layout.extend({
}));
},
_showGrid : function() {
this.regionManager.get('grid').show(new Backgrid.Grid({
row : Backgrid.Row,
columns : this.columns,
collection : this.filesCollection,
className : 'table table-hover'
}));
},
_showMainView : function() {
this.main.show(this.mainView);
},

@ -9,6 +9,7 @@ module.exports = Marionette.ItemView.extend({
//var type = this.model.get('seriesType');
return {
rename : this.naming.get('renameEpisodes'),
folderFormat: this.naming.get('movieFolderFormat'),
format : this.naming.get('standardMovieFormat')
};
},

@ -1,3 +1,4 @@
{{#if rename}}
Folder Naming pattern: {{folderFormat}}<br>
Naming pattern: {{format}}
{{/if}}

@ -9,7 +9,7 @@
</div>
<div class="modal-body">
<div class="alert alert-info">
<div class="path-info x-path-info">All paths are relative to: <strong>{{path}}</strong></div>
<div class="path-info x-path-info">Your movie may be moved; see the paths below</strong></div>
<div class="x-format-region"></div>
</div>

@ -162,6 +162,10 @@
<ul class="dropdown-menu">
{{> MovieTitleNamingPartial}}
{{> ReleaseYearNamingPartial}}
{{> QualityNamingPartial}}
{{> MediaInfoNamingPartial}}
{{> ReleaseGroupNamingPartial}}
{{> OriginalTitleNamingPartial}}
{{> ImdbIdNamingPartial}}
</ul>
</div>

@ -23,6 +23,52 @@
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Automatically Rename Folders</label>
<div class="col-sm-9">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="autoRenameFolders"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-warning" title="CURRENTLY ALPHA! This feature must be enabled for namings schemes beyond '{Movie Title} {Year}' to work. With it folders are automatically renamed according to your naming scheme on each disk scan. If your folder naming scheme contains things such as quality, etc., the movie folder will be automatcially adjusted for that regardless of this setting."/>
</span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Movie Paths Default to Static</label>
<div class="col-sm-9">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="pathsDefaultStatic"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-warning" title="CURRENTLY ALPHA! If enabled, the path of new movies is static and won't change."/>
</span>
</div>
</div>
</div>
</fieldset>
<fieldset class="advanced-setting">

@ -32,7 +32,8 @@ vent.Commands = {
CloseFileBrowser : 'closeFileBrowser',
OpenControlPanelCommand : 'OpenControlPanelCommand',
CloseControlPanelCommand : 'CloseControlPanelCommand',
ShowExistingCommand : 'ShowExistingCommand'
ShowExistingCommand : 'ShowExistingCommand',
MovieFileEdited : 'MovieFileEdited'
};
vent.Hotkeys = {

Loading…
Cancel
Save