mirror of
https://github.com/Radarr/Radarr.git
synced 2024-08-18 08:19:38 +02:00
Implemented importing movies. This is still in early stages, however it should work pretty well.
This commit is contained in:
parent
d9d8cbacec
commit
ad95fbfd4a
@ -22,9 +22,9 @@ rm $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr
|
|||||||
chmod +x $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2
|
chmod +x $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2
|
||||||
mv $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2 $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr >& error.log
|
mv $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2 $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr >& error.log
|
||||||
|
|
||||||
cp -r $outputFolder Radarr_Windows_$VERSION
|
cp -r $outputFolder/ Radarr_Windows_$VERSION
|
||||||
cp -r $outputFolderMono Radarr_Mono_$VERSION
|
cp -r $outputFolderMono/ Radarr_Mono_$VERSION
|
||||||
cp -r $outputFolderOsxApp Radarr_OSX_$VERSION
|
cp -r $outputFolderOsxApp/ Radarr_OSX_$VERSION
|
||||||
|
|
||||||
zip -r Radarr_Windows_$VERSION.zip Radarr_Windows_$VERSION >& /dev/null
|
zip -r Radarr_Windows_$VERSION.zip Radarr_Windows_$VERSION >& /dev/null
|
||||||
zip -r Radarr_Mono_$VERSION.zip Radarr_Mono_$VERSION >& /dev/null
|
zip -r Radarr_Mono_$VERSION.zip Radarr_Mono_$VERSION >& /dev/null
|
||||||
|
@ -23,7 +23,7 @@ public static void Main(string[] args)
|
|||||||
{
|
{
|
||||||
System.Console.WriteLine("");
|
System.Console.WriteLine("");
|
||||||
System.Console.WriteLine("");
|
System.Console.WriteLine("");
|
||||||
Logger.Fatal(exception.Message + ". This can happen if another instance of Sonarr is already running another application is using the same port (default: 7878) or the user has insufficient permissions");
|
Logger.Fatal(exception.Message + ". This can happen if another instance of Radarr is already running another application is using the same port (default: 7878) or the user has insufficient permissions");
|
||||||
System.Console.WriteLine("Press enter to exit...");
|
System.Console.WriteLine("Press enter to exit...");
|
||||||
System.Console.ReadLine();
|
System.Console.ReadLine();
|
||||||
Environment.Exit(1);
|
Environment.Exit(1);
|
||||||
|
@ -303,12 +303,12 @@ private XDocument LoadConfigFile()
|
|||||||
|
|
||||||
if (contents.IsNullOrWhiteSpace())
|
if (contents.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
throw new InvalidConfigFileException($"{_configFile} is empty. Please delete the config file and Sonarr will recreate it.");
|
throw new InvalidConfigFileException($"{_configFile} is empty. Please delete the config file and Radarr will recreate it.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contents.All(char.IsControl))
|
if (contents.All(char.IsControl))
|
||||||
{
|
{
|
||||||
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Sonarr will recreate it.");
|
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Radarr will recreate it.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return XDocument.Parse(_diskProvider.ReadAllText(_configFile));
|
return XDocument.Parse(_diskProvider.ReadAllText(_configFile));
|
||||||
@ -323,7 +323,7 @@ private XDocument LoadConfigFile()
|
|||||||
|
|
||||||
catch (XmlException ex)
|
catch (XmlException ex)
|
||||||
{
|
{
|
||||||
throw new InvalidConfigFileException($"{_configFile} is corrupt is invalid. Please delete the config file and Sonarr will recreate it.", ex);
|
throw new InvalidConfigFileException($"{_configFile} is corrupt is invalid. Please delete the config file and Radarr will recreate it.", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using Marr.Data.Mapping;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Datastore.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(104)]
|
||||||
|
public class add_moviefiles_table : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Create.TableForModel("MovieFiles")
|
||||||
|
.WithColumn("MovieId").AsInt32()
|
||||||
|
.WithColumn("Path").AsString().Unique()
|
||||||
|
.WithColumn("Quality").AsString()
|
||||||
|
.WithColumn("Size").AsInt64()
|
||||||
|
.WithColumn("DateAdded").AsDateTime()
|
||||||
|
.WithColumn("SceneName").AsString().Nullable()
|
||||||
|
.WithColumn("MediaInfo").AsString().Nullable()
|
||||||
|
.WithColumn("ReleaseGroup").AsString().Nullable()
|
||||||
|
.WithColumn("RelativePath").AsString().Nullable();
|
||||||
|
|
||||||
|
Alter.Table("Movies").AddColumn("MovieFileId").AsInt32().WithDefaultValue(0);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -60,7 +60,6 @@ public void Migrate(string connectionString, MigrationContext migrationContext)
|
|||||||
|
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|
||||||
_announcer.ElapsedTime(sw.Elapsed);
|
_announcer.ElapsedTime(sw.Elapsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,10 +76,7 @@ public static void Map()
|
|||||||
.Relationship()
|
.Relationship()
|
||||||
.HasOne(s => s.Profile, s => s.ProfileId);
|
.HasOne(s => s.Profile, s => s.ProfileId);
|
||||||
|
|
||||||
Mapper.Entity<Movie>().RegisterModel("Movies")
|
|
||||||
.Ignore(s => s.RootFolderPath)
|
|
||||||
.Relationship()
|
|
||||||
.HasOne(s => s.Profile, s => s.ProfileId);
|
|
||||||
|
|
||||||
Mapper.Entity<EpisodeFile>().RegisterModel("EpisodeFiles")
|
Mapper.Entity<EpisodeFile>().RegisterModel("EpisodeFiles")
|
||||||
.Ignore(f => f.Path)
|
.Ignore(f => f.Path)
|
||||||
@ -89,6 +86,21 @@ public static void Map()
|
|||||||
query: (db, parent) => db.Query<Episode>().Where(c => c.EpisodeFileId == parent.Id).ToList())
|
query: (db, parent) => db.Query<Episode>().Where(c => c.EpisodeFileId == parent.Id).ToList())
|
||||||
.HasOne(file => file.Series, file => file.SeriesId);
|
.HasOne(file => file.Series, file => file.SeriesId);
|
||||||
|
|
||||||
|
Mapper.Entity<MovieFile>().RegisterModel("MovieFiles")
|
||||||
|
.Ignore(f => f.Path)
|
||||||
|
.Relationships.AutoMapICollectionOrComplexProperties()
|
||||||
|
.For("Movie")
|
||||||
|
.LazyLoad(condition: parent => parent.Id > 0,
|
||||||
|
query: (db, parent) => db.Query<Movie>().Where(c => c.MovieFileId == parent.Id).ToList())
|
||||||
|
.HasOne(file => file.Movie, file => file.MovieId);
|
||||||
|
|
||||||
|
Mapper.Entity<Movie>().RegisterModel("Movies")
|
||||||
|
.Ignore(s => s.RootFolderPath)
|
||||||
|
.Relationship()
|
||||||
|
.HasOne(s => s.Profile, s => s.ProfileId)
|
||||||
|
.HasOne(m => m.MovieFile, m => m.MovieFileId);
|
||||||
|
|
||||||
|
|
||||||
Mapper.Entity<Episode>().RegisterModel("Episodes")
|
Mapper.Entity<Episode>().RegisterModel("Episodes")
|
||||||
.Ignore(e => e.SeriesTitle)
|
.Ignore(e => e.SeriesTitle)
|
||||||
.Ignore(e => e.Series)
|
.Ignore(e => e.Series)
|
||||||
|
@ -60,7 +60,7 @@ public override IEnumerable<DownloadClientItem> GetItems()
|
|||||||
{
|
{
|
||||||
DownloadClient = Definition.Name,
|
DownloadClient = Definition.Name,
|
||||||
DownloadId = Definition.Name + "_" + item.DownloadId,
|
DownloadId = Definition.Name + "_" + item.DownloadId,
|
||||||
Category = "sonarr",
|
Category = "Radarr",
|
||||||
Title = item.Title,
|
Title = item.Title,
|
||||||
|
|
||||||
TotalSize = item.TotalSize,
|
TotalSize = item.TotalSize,
|
||||||
|
@ -27,6 +27,7 @@ public class CompletedDownloadService : ICompletedDownloadService
|
|||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IHistoryService _historyService;
|
private readonly IHistoryService _historyService;
|
||||||
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
||||||
|
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
|
||||||
private readonly IParsingService _parsingService;
|
private readonly IParsingService _parsingService;
|
||||||
private readonly IMovieService _movieService;
|
private readonly IMovieService _movieService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
@ -36,6 +37,7 @@ public CompletedDownloadService(IConfigService configService,
|
|||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
IHistoryService historyService,
|
IHistoryService historyService,
|
||||||
IDownloadedEpisodesImportService downloadedEpisodesImportService,
|
IDownloadedEpisodesImportService downloadedEpisodesImportService,
|
||||||
|
IDownloadedMovieImportService downloadedMovieImportService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
ISeriesService seriesService,
|
ISeriesService seriesService,
|
||||||
IMovieService movieService,
|
IMovieService movieService,
|
||||||
@ -45,6 +47,7 @@ public CompletedDownloadService(IConfigService configService,
|
|||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_historyService = historyService;
|
_historyService = historyService;
|
||||||
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
||||||
|
_downloadedMovieImportService = downloadedMovieImportService;
|
||||||
_parsingService = parsingService;
|
_parsingService = parsingService;
|
||||||
_movieService = movieService;
|
_movieService = movieService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -64,7 +67,7 @@ public void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false
|
|||||||
|
|
||||||
if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
|
if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
|
trackedDownload.Warn("Download wasn't grabbed by Radarr and not in a category, Skipping.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,29 +129,59 @@ public void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false
|
|||||||
private void Import(TrackedDownload trackedDownload)
|
private void Import(TrackedDownload trackedDownload)
|
||||||
{
|
{
|
||||||
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
|
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
|
||||||
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
|
if (trackedDownload.RemoteMovie.Movie != null)
|
||||||
|
|
||||||
if (importResults.Empty())
|
|
||||||
{
|
{
|
||||||
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
|
var importResults = _downloadedMovieImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteMovie.Movie, trackedDownload.DownloadItem);
|
||||||
return;
|
|
||||||
|
if (importResults.Empty())
|
||||||
|
{
|
||||||
|
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importResults.Count(c => c.Result == ImportResultType.Imported) >= 1)
|
||||||
|
{
|
||||||
|
trackedDownload.State = TrackedDownloadStage.Imported;
|
||||||
|
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importResults.Any(c => c.Result != ImportResultType.Imported))
|
||||||
|
{
|
||||||
|
var statusMessages = importResults
|
||||||
|
.Where(v => v.Result != ImportResultType.Imported)
|
||||||
|
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
trackedDownload.Warn(statusMessages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (importResults.Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
|
|
||||||
{
|
{
|
||||||
trackedDownload.State = TrackedDownloadStage.Imported;
|
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
|
||||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importResults.Any(c => c.Result != ImportResultType.Imported))
|
if (importResults.Empty())
|
||||||
{
|
{
|
||||||
var statusMessages = importResults
|
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
|
||||||
.Where(v => v.Result != ImportResultType.Imported)
|
return;
|
||||||
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
|
}
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
trackedDownload.Warn(statusMessages);
|
if (importResults.Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
|
||||||
|
{
|
||||||
|
trackedDownload.State = TrackedDownloadStage.Imported;
|
||||||
|
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importResults.Any(c => c.Result != ImportResultType.Imported))
|
||||||
|
{
|
||||||
|
var statusMessages = importResults
|
||||||
|
.Where(v => v.Result != ImportResultType.Imported)
|
||||||
|
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
trackedDownload.Warn(statusMessages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ protected ValidationFailure TestFolder(string folder, string propertyName, bool
|
|||||||
{
|
{
|
||||||
return new NzbDroneValidationFailure(propertyName, "Folder does not exist")
|
return new NzbDroneValidationFailure(propertyName, "Folder does not exist")
|
||||||
{
|
{
|
||||||
DetailedDescription = string.Format("The folder you specified does not exist or is inaccessible. Please verify the folder permissions for the user account '{0}', which is used to execute Sonarr.", Environment.UserName)
|
DetailedDescription = string.Format("The folder you specified does not exist or is inaccessible. Please verify the folder permissions for the user account '{0}', which is used to execute Radarr.", Environment.UserName)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ protected ValidationFailure TestFolder(string folder, string propertyName, bool
|
|||||||
_logger.Error("Folder '{0}' is not writable.", folder);
|
_logger.Error("Folder '{0}' is not writable.", folder);
|
||||||
return new NzbDroneValidationFailure(propertyName, "Unable to write to folder")
|
return new NzbDroneValidationFailure(propertyName, "Unable to write to folder")
|
||||||
{
|
{
|
||||||
DetailedDescription = string.Format("The folder you specified is not writable. Please verify the folder permissions for the user account '{0}', which is used to execute Sonarr.", Environment.UserName)
|
DetailedDescription = string.Format("The folder you specified is not writable. Please verify the folder permissions for the user account '{0}', which is used to execute Radarr.", Environment.UserName)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +371,7 @@ private string DownloadFromWebUrl(RemoteEpisode remoteEpisode, string torrentUrl
|
|||||||
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
|
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
|
||||||
{
|
{
|
||||||
_logger.Debug(
|
_logger.Debug(
|
||||||
"{0} did not return the expected InfoHash for '{1}', Sonarr could potentially lose track of the download in progress.",
|
"{0} did not return the expected InfoHash for '{1}', Radarr could potentially lose track of the download in progress.",
|
||||||
Definition.Implementation, remoteEpisode.Release.DownloadUrl);
|
Definition.Implementation, remoteEpisode.Release.DownloadUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,7 +402,7 @@ private string DownloadFromMagnetUrl(RemoteEpisode remoteEpisode, string magnetU
|
|||||||
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
|
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
|
||||||
{
|
{
|
||||||
_logger.Debug(
|
_logger.Debug(
|
||||||
"{0} did not return the expected InfoHash for '{1}', Sonarr could potentially lose track of the download in progress.",
|
"{0} did not return the expected InfoHash for '{1}', Radarr could potentially lose track of the download in progress.",
|
||||||
Definition.Implementation, remoteEpisode.Release.DownloadUrl);
|
Definition.Implementation, remoteEpisode.Release.DownloadUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
265
src/NzbDrone.Core/MediaFiles/DownloadedMovieCommandService.cs
Normal file
265
src/NzbDrone.Core/MediaFiles/DownloadedMovieCommandService.cs
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
{
|
||||||
|
public interface IDownloadedMovieImportService
|
||||||
|
{
|
||||||
|
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
|
||||||
|
List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null);
|
||||||
|
bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DownloadedMovieImportService : IDownloadedMovieImportService
|
||||||
|
{
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IDiskScanService _diskScanService;
|
||||||
|
private readonly IMovieService _movieService;
|
||||||
|
private readonly IParsingService _parsingService;
|
||||||
|
private readonly IMakeImportDecision _importDecisionMaker;
|
||||||
|
private readonly IImportApprovedMovie _importApprovedMovie;
|
||||||
|
private readonly IDetectSample _detectSample;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public DownloadedMovieImportService(IDiskProvider diskProvider,
|
||||||
|
IDiskScanService diskScanService,
|
||||||
|
IMovieService movieService,
|
||||||
|
IParsingService parsingService,
|
||||||
|
IMakeImportDecision importDecisionMaker,
|
||||||
|
IImportApprovedMovie importApprovedMovie,
|
||||||
|
IDetectSample detectSample,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_diskScanService = diskScanService;
|
||||||
|
_movieService = movieService;
|
||||||
|
_parsingService = parsingService;
|
||||||
|
_importDecisionMaker = importDecisionMaker;
|
||||||
|
_importApprovedMovie = importApprovedMovie;
|
||||||
|
_detectSample = detectSample;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo)
|
||||||
|
{
|
||||||
|
var results = new List<ImportResult>();
|
||||||
|
|
||||||
|
foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName))
|
||||||
|
{
|
||||||
|
var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null);
|
||||||
|
results.AddRange(folderResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false))
|
||||||
|
{
|
||||||
|
var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null);
|
||||||
|
results.AddRange(fileResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null)
|
||||||
|
{
|
||||||
|
if (_diskProvider.FolderExists(path))
|
||||||
|
{
|
||||||
|
var directoryInfo = new DirectoryInfo(path);
|
||||||
|
|
||||||
|
if (movie == null)
|
||||||
|
{
|
||||||
|
return ProcessFolder(directoryInfo, importMode, downloadClientItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_diskProvider.FileExists(path))
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(path);
|
||||||
|
|
||||||
|
if (movie == null)
|
||||||
|
{
|
||||||
|
return ProcessFile(fileInfo, importMode, downloadClientItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Error("Import failed, path does not exist or is not accessible by Sonarr: {0}", path);
|
||||||
|
return new List<ImportResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie)
|
||||||
|
{
|
||||||
|
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||||
|
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar");
|
||||||
|
|
||||||
|
foreach (var videoFile in videoFiles)
|
||||||
|
{
|
||||||
|
var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile));
|
||||||
|
|
||||||
|
if (episodeParseResult == null)
|
||||||
|
{
|
||||||
|
_logger.Warn("Unable to parse file on import: [{0}]", videoFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = _diskProvider.GetFileSize(videoFile);
|
||||||
|
var quality = QualityParser.ParseQuality(videoFile);
|
||||||
|
|
||||||
|
if (!_detectSample.IsSample(movie, quality, videoFile, size, episodeParseResult.IsPossibleSpecialEpisode))
|
||||||
|
{
|
||||||
|
_logger.Warn("Non-sample file detected: [{0}]", videoFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rarFiles.Any(f => _diskProvider.GetFileSize(f) > 10.Megabytes()))
|
||||||
|
{
|
||||||
|
_logger.Warn("RAR file detected, will require manual cleanup");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
|
||||||
|
{
|
||||||
|
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||||
|
var movie = _parsingService.GetMovie(cleanedUpName);
|
||||||
|
|
||||||
|
if (movie == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("Unknown Movie {0}", cleanedUpName);
|
||||||
|
|
||||||
|
return new List<ImportResult>
|
||||||
|
{
|
||||||
|
UnknownMovieResult("Unknown Movie")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
|
||||||
|
{
|
||||||
|
if (_movieService.MoviePathExists(directoryInfo.FullName))
|
||||||
|
{
|
||||||
|
_logger.Warn("Unable to process folder that is mapped to an existing show");
|
||||||
|
return new List<ImportResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||||
|
var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
|
||||||
|
|
||||||
|
if (folderInfo != null)
|
||||||
|
{
|
||||||
|
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||||
|
|
||||||
|
if (downloadClientItem == null)
|
||||||
|
{
|
||||||
|
foreach (var videoFile in videoFiles)
|
||||||
|
{
|
||||||
|
if (_diskProvider.IsFileLocked(videoFile))
|
||||||
|
{
|
||||||
|
return new List<ImportResult>
|
||||||
|
{
|
||||||
|
FileIsLockedResult(videoFile)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true);
|
||||||
|
var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
|
||||||
|
|
||||||
|
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
|
||||||
|
importResults.Any(i => i.Result == ImportResultType.Imported) &&
|
||||||
|
ShouldDeleteFolder(directoryInfo, movie))
|
||||||
|
{
|
||||||
|
_logger.Debug("Deleting folder after importing valid files");
|
||||||
|
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return importResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
|
||||||
|
{
|
||||||
|
var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name));
|
||||||
|
|
||||||
|
if (movie == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("Unknown Movie for file: {0}", fileInfo.Name);
|
||||||
|
|
||||||
|
return new List<ImportResult>
|
||||||
|
{
|
||||||
|
UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
|
||||||
|
{
|
||||||
|
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
|
||||||
|
{
|
||||||
|
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
|
||||||
|
|
||||||
|
return new List<ImportResult>
|
||||||
|
{
|
||||||
|
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadClientItem == null)
|
||||||
|
{
|
||||||
|
if (_diskProvider.IsFileLocked(fileInfo.FullName))
|
||||||
|
{
|
||||||
|
return new List<ImportResult>
|
||||||
|
{
|
||||||
|
FileIsLockedResult(fileInfo.FullName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, null, true);
|
||||||
|
|
||||||
|
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCleanedUpFolderName(string folder)
|
||||||
|
{
|
||||||
|
folder = folder.Replace("_UNPACK_", "")
|
||||||
|
.Replace("_FAILED_", "");
|
||||||
|
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportResult FileIsLockedResult(string videoFile)
|
||||||
|
{
|
||||||
|
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
|
||||||
|
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportResult UnknownMovieResult(string message, string videoFile = null)
|
||||||
|
{
|
||||||
|
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
|
||||||
|
|
||||||
|
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||||||
public interface IDetectSample
|
public interface IDetectSample
|
||||||
{
|
{
|
||||||
bool IsSample(Series series, QualityModel quality, string path, long size, bool isSpecial);
|
bool IsSample(Series series, QualityModel quality, string path, long size, bool isSpecial);
|
||||||
|
bool IsSample(Movie movie, QualityModel quality, string path, long size, bool isSpecial);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DetectSample : IDetectSample
|
public class DetectSample : IDetectSample
|
||||||
@ -79,6 +80,57 @@ public bool IsSample(Series series, QualityModel quality, string path, long size
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsSample(Movie movie, QualityModel quality, string path, long size, bool isSpecial)
|
||||||
|
{
|
||||||
|
if (isSpecial)
|
||||||
|
{
|
||||||
|
_logger.Debug("Special, skipping sample check");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
|
if (extension != null && extension.Equals(".flv", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
_logger.Debug("Skipping sample check for .flv file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension != null && extension.Equals(".strm", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
_logger.Debug("Skipping sample check for .strm file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var runTime = _videoFileInfoReader.GetRunTime(path);
|
||||||
|
var minimumRuntime = GetMinimumAllowedRuntime(movie);
|
||||||
|
|
||||||
|
if (runTime.TotalMinutes.Equals(0))
|
||||||
|
{
|
||||||
|
_logger.Error("[{0}] has a runtime of 0, is it a valid video file?", path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runTime.TotalSeconds < minimumRuntime)
|
||||||
|
{
|
||||||
|
_logger.Debug("[{0}] appears to be a sample. Runtime: {1} seconds. Expected at least: {2} seconds", path, runTime, minimumRuntime);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (DllNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.Debug("Falling back to file size detection");
|
||||||
|
|
||||||
|
return CheckSize(size, quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug("Runtime is over 90 seconds");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private bool CheckSize(long size, QualityModel quality)
|
private bool CheckSize(long size, QualityModel quality)
|
||||||
{
|
{
|
||||||
if (_largeSampleSizeQualities.Contains(quality.Quality))
|
if (_largeSampleSizeQualities.Contains(quality.Quality))
|
||||||
@ -99,6 +151,11 @@ private bool CheckSize(long size, QualityModel quality)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int GetMinimumAllowedRuntime(Movie movie)
|
||||||
|
{
|
||||||
|
return 120; //2 minutes
|
||||||
|
}
|
||||||
|
|
||||||
private int GetMinimumAllowedRuntime(Series series)
|
private int GetMinimumAllowedRuntime(Series series)
|
||||||
{
|
{
|
||||||
//Webisodes - 90 seconds
|
//Webisodes - 90 seconds
|
||||||
|
@ -6,5 +6,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||||||
public interface IImportDecisionEngineSpecification
|
public interface IImportDecisionEngineSpecification
|
||||||
{
|
{
|
||||||
Decision IsSatisfiedBy(LocalEpisode localEpisode);
|
Decision IsSatisfiedBy(LocalEpisode localEpisode);
|
||||||
|
|
||||||
|
Decision IsSatisfiedBy(LocalMovie localMovie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,172 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Extras;
|
||||||
|
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
{
|
||||||
|
public interface IImportApprovedMovie
|
||||||
|
{
|
||||||
|
List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportApprovedMovie : IImportApprovedMovie
|
||||||
|
{
|
||||||
|
private readonly IUpgradeMediaFiles _episodeFileUpgrader;
|
||||||
|
private readonly IMediaFileService _mediaFileService;
|
||||||
|
private readonly IExtraService _extraService;
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public ImportApprovedMovie(IUpgradeMediaFiles episodeFileUpgrader,
|
||||||
|
IMediaFileService mediaFileService,
|
||||||
|
IExtraService extraService,
|
||||||
|
IDiskProvider diskProvider,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_episodeFileUpgrader = episodeFileUpgrader;
|
||||||
|
_mediaFileService = mediaFileService;
|
||||||
|
_extraService = extraService;
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto)
|
||||||
|
{
|
||||||
|
var qualifiedImports = decisions.Where(c => c.Approved)
|
||||||
|
.GroupBy(c => c.LocalMovie.Movie.Id, (i, s) => s
|
||||||
|
.OrderByDescending(c => c.LocalMovie.Quality, new QualityModelComparer(s.First().LocalMovie.Movie.Profile))
|
||||||
|
.ThenByDescending(c => c.LocalMovie.Size))
|
||||||
|
.SelectMany(c => c)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var importResults = new List<ImportResult>();
|
||||||
|
|
||||||
|
foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalMovie.Size)
|
||||||
|
.ThenByDescending(e => e.LocalMovie.Size))
|
||||||
|
{
|
||||||
|
var localMovie = importDecision.LocalMovie;
|
||||||
|
var oldFiles = new List<MovieFile>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//check if already imported
|
||||||
|
if (importResults.Select(r => r.ImportDecision.LocalMovie.Movie)
|
||||||
|
.Select(e => e.Id).Contains(localMovie.Movie.Id))
|
||||||
|
{
|
||||||
|
importResults.Add(new ImportResult(importDecision, "Movie has already been imported"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var episodeFile = new MovieFile();
|
||||||
|
episodeFile.DateAdded = DateTime.UtcNow;
|
||||||
|
episodeFile.MovieId = localMovie.Movie.Id;
|
||||||
|
episodeFile.Path = localMovie.Path.CleanFilePath();
|
||||||
|
episodeFile.Size = _diskProvider.GetFileSize(localMovie.Path);
|
||||||
|
episodeFile.Quality = localMovie.Quality;
|
||||||
|
episodeFile.MediaInfo = localMovie.MediaInfo;
|
||||||
|
episodeFile.Movie = localMovie.Movie;
|
||||||
|
episodeFile.ReleaseGroup = localMovie.ParsedEpisodeInfo.ReleaseGroup;
|
||||||
|
|
||||||
|
bool copyOnly;
|
||||||
|
switch (importMode)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case ImportMode.Auto:
|
||||||
|
copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly;
|
||||||
|
break;
|
||||||
|
case ImportMode.Move:
|
||||||
|
copyOnly = false;
|
||||||
|
break;
|
||||||
|
case ImportMode.Copy:
|
||||||
|
copyOnly = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newDownload)
|
||||||
|
{
|
||||||
|
episodeFile.SceneName = GetSceneName(downloadClientItem, localMovie);
|
||||||
|
|
||||||
|
var moveResult = _episodeFileUpgrader.UpgradeMovieFile(episodeFile, localMovie, copyOnly);
|
||||||
|
oldFiles = moveResult.OldFiles;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
episodeFile.RelativePath = localMovie.Movie.Path.GetRelativePath(episodeFile.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
_mediaFileService.Add(episodeFile);
|
||||||
|
importResults.Add(new ImportResult(importDecision));
|
||||||
|
|
||||||
|
if (newDownload)
|
||||||
|
{
|
||||||
|
//_extraService.ImportExtraFiles(localMovie, episodeFile, copyOnly); TODO update for movie
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadClientItem != null)
|
||||||
|
{
|
||||||
|
_eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, episodeFile, newDownload));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newDownload)
|
||||||
|
{
|
||||||
|
_eventAggregator.PublishEvent(new MovieDownloadedEvent(localMovie, episodeFile, oldFiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Warn(e, "Couldn't import episode " + localMovie);
|
||||||
|
importResults.Add(new ImportResult(importDecision, "Failed to import episode"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Adding all the rejected decisions
|
||||||
|
importResults.AddRange(decisions.Where(c => !c.Approved)
|
||||||
|
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));
|
||||||
|
|
||||||
|
return importResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSceneName(DownloadClientItem downloadClientItem, LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
if (downloadClientItem != null)
|
||||||
|
{
|
||||||
|
var title = Parser.Parser.RemoveFileExtension(downloadClientItem.Title);
|
||||||
|
|
||||||
|
var parsedTitle = Parser.Parser.ParseTitle(title);
|
||||||
|
|
||||||
|
if (parsedTitle != null && !parsedTitle.FullSeason)
|
||||||
|
{
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = Path.GetFileNameWithoutExtension(localMovie.Path.CleanFilePath());
|
||||||
|
|
||||||
|
if (SceneChecker.IsSceneTitle(fileName))
|
||||||
|
{
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||||||
public class ImportDecision
|
public class ImportDecision
|
||||||
{
|
{
|
||||||
public LocalEpisode LocalEpisode { get; private set; }
|
public LocalEpisode LocalEpisode { get; private set; }
|
||||||
|
public LocalMovie LocalMovie { get; private set; }
|
||||||
public IEnumerable<Rejection> Rejections { get; private set; }
|
public IEnumerable<Rejection> Rejections { get; private set; }
|
||||||
|
|
||||||
public bool Approved => Rejections.Empty();
|
public bool Approved => Rejections.Empty();
|
||||||
@ -18,5 +19,20 @@ public ImportDecision(LocalEpisode localEpisode, params Rejection[] rejections)
|
|||||||
LocalEpisode = localEpisode;
|
LocalEpisode = localEpisode;
|
||||||
Rejections = rejections.ToList();
|
Rejections = rejections.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImportDecision(LocalMovie localMovie, params Rejection[] rejections)
|
||||||
|
{
|
||||||
|
LocalMovie = localMovie;
|
||||||
|
Rejections = rejections.ToList();
|
||||||
|
LocalEpisode = new LocalEpisode
|
||||||
|
{
|
||||||
|
Quality = localMovie.Quality,
|
||||||
|
ExistingFile = localMovie.ExistingFile,
|
||||||
|
MediaInfo = localMovie.MediaInfo,
|
||||||
|
ParsedEpisodeInfo = localMovie.ParsedEpisodeInfo,
|
||||||
|
Path = localMovie.Path,
|
||||||
|
Size = localMovie.Size
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,36 +98,32 @@ private ImportDecision GetDecision(string file, Movie movie, ParsedEpisodeInfo f
|
|||||||
{
|
{
|
||||||
ImportDecision decision = null;
|
ImportDecision decision = null;
|
||||||
|
|
||||||
/*try
|
try
|
||||||
{
|
{
|
||||||
var localEpisode = _parsingService.GetLocalEpisode(file, movie, shouldUseFolderName ? folderInfo : null, sceneSource);
|
var localMovie = _parsingService.GetLocalMovie(file, movie, shouldUseFolderName ? folderInfo : null, sceneSource);
|
||||||
|
|
||||||
if (localEpisode != null)
|
if (localMovie != null)
|
||||||
{
|
{
|
||||||
localEpisode.Quality = GetQuality(folderInfo, localEpisode.Quality, movie);
|
localMovie.Quality = GetQuality(folderInfo, localMovie.Quality, movie);
|
||||||
localEpisode.Size = _diskProvider.GetFileSize(file);
|
localMovie.Size = _diskProvider.GetFileSize(file);
|
||||||
|
|
||||||
_logger.Debug("Size: {0}", localEpisode.Size);
|
_logger.Debug("Size: {0}", localMovie.Size);
|
||||||
|
|
||||||
//TODO: make it so media info doesn't ruin the import process of a new series
|
//TODO: make it so media info doesn't ruin the import process of a new series
|
||||||
if (sceneSource)
|
if (sceneSource)
|
||||||
{
|
{
|
||||||
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
|
localMovie.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
|
||||||
}
|
decision = GetDecision(localMovie);
|
||||||
|
|
||||||
if (localEpisode.Episodes.Empty())
|
|
||||||
{
|
|
||||||
decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode"));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
decision = GetDecision(localEpisode);
|
decision = GetDecision(localMovie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
localEpisode = new LocalEpisode();
|
var localEpisode = new LocalEpisode();
|
||||||
localEpisode.Path = file;
|
localEpisode.Path = file;
|
||||||
|
|
||||||
decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file"));
|
decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file"));
|
||||||
@ -139,13 +135,23 @@ private ImportDecision GetDecision(string file, Movie movie, ParsedEpisodeInfo f
|
|||||||
|
|
||||||
var localEpisode = new LocalEpisode { Path = file };
|
var localEpisode = new LocalEpisode { Path = file };
|
||||||
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file"));
|
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file"));
|
||||||
}*/
|
}
|
||||||
|
|
||||||
decision = new ImportDecision(null, new Rejection("IMPLEMENTATION MISSING!!!"));
|
//LocalMovie nullMovie = null;
|
||||||
|
|
||||||
|
//decision = new ImportDecision(nullMovie, new Rejection("IMPLEMENTATION MISSING!!!"));
|
||||||
|
|
||||||
return decision;
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImportDecision GetDecision(LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
var reasons = _specifications.Select(c => EvaluateSpec(c, localMovie))
|
||||||
|
.Where(c => c != null);
|
||||||
|
|
||||||
|
return new ImportDecision(localMovie, reasons.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
private ImportDecision GetDecision(string file, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName)
|
private ImportDecision GetDecision(string file, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName)
|
||||||
{
|
{
|
||||||
ImportDecision decision = null;
|
ImportDecision decision = null;
|
||||||
@ -204,6 +210,33 @@ private ImportDecision GetDecision(LocalEpisode localEpisode)
|
|||||||
return new ImportDecision(localEpisode, reasons.ToArray());
|
return new ImportDecision(localEpisode, reasons.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = spec.IsSatisfiedBy(localMovie);
|
||||||
|
|
||||||
|
if (!result.Accepted)
|
||||||
|
{
|
||||||
|
return new Rejection(result.Reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NotImplementedException e)
|
||||||
|
{
|
||||||
|
_logger.Warn(e, "Spec " + spec.ToString() + " currently does not implement evaluation for movies.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//e.Data.Add("report", remoteEpisode.Report.ToJson());
|
||||||
|
//e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
|
||||||
|
_logger.Error(e, "Couldn't evaluate decision on " + localMovie.Path);
|
||||||
|
return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode)
|
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -292,6 +325,17 @@ private bool ShouldUseFolderName(List<string> videoFiles, Movie movie, ParsedEpi
|
|||||||
}) == 1;
|
}) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Movie movie)
|
||||||
|
{
|
||||||
|
if (UseFolderQuality(folderInfo, fileQuality, movie))
|
||||||
|
{
|
||||||
|
_logger.Debug("Using quality from folder: {0}", folderInfo.Quality);
|
||||||
|
return folderInfo.Quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileQuality;
|
||||||
|
}
|
||||||
|
|
||||||
private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
|
private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
|
||||||
{
|
{
|
||||||
if (UseFolderQuality(folderInfo, fileQuality, series))
|
if (UseFolderQuality(folderInfo, fileQuality, series))
|
||||||
@ -303,6 +347,31 @@ private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQ
|
|||||||
return fileQuality;
|
return fileQuality;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool UseFolderQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Movie movie)
|
||||||
|
{
|
||||||
|
if (folderInfo == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folderInfo.Quality.Quality == Quality.Unknown)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileQuality.QualitySource == QualitySource.Extension)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new QualityModelComparer(movie.Profile).Compare(folderInfo.Quality, fileQuality) > 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private bool UseFolderQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
|
private bool UseFolderQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
|
||||||
{
|
{
|
||||||
if (folderInfo == null)
|
if (folderInfo == null)
|
||||||
|
@ -63,5 +63,48 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
|||||||
|
|
||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
if (_configService.SkipFreeSpaceCheckWhenImporting)
|
||||||
|
{
|
||||||
|
_logger.Debug("Skipping free space check when importing");
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (localMovie.ExistingFile)
|
||||||
|
{
|
||||||
|
_logger.Debug("Skipping free space check for existing episode");
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = Directory.GetParent(localMovie.Movie.Path);
|
||||||
|
var freeSpace = _diskProvider.GetAvailableSpace(path.FullName);
|
||||||
|
|
||||||
|
if (!freeSpace.HasValue)
|
||||||
|
{
|
||||||
|
_logger.Debug("Free space check returned an invalid result for: {0}", path);
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (freeSpace < localMovie.Size + 100.Megabytes())
|
||||||
|
{
|
||||||
|
_logger.Warn("Not enough free space ({0}) to import: {1} ({2})", freeSpace, localMovie, localMovie.Size);
|
||||||
|
return Decision.Reject("Not enough free space");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException ex)
|
||||||
|
{
|
||||||
|
_logger.Error("Unable to check free disk space while importing. " + ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to check free disk space while importing: " + localMovie.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,16 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
|||||||
{
|
{
|
||||||
if (localEpisode.ParsedEpisodeInfo.FullSeason)
|
if (localEpisode.ParsedEpisodeInfo.FullSeason)
|
||||||
{
|
{
|
||||||
//_logger.Debug("Single episode file detected as containing all episodes in the season"); //Not needed for Movies mwhahahahah
|
_logger.Debug("Single episode file detected as containing all episodes in the season"); //Not needed for Movies mwhahahahah
|
||||||
//return Decision.Reject("Single episode file contains all episodes in seasons");
|
return Decision.Reject("Single episode file contains all episodes in seasons");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
@ -14,6 +15,37 @@ public MatchesFolderSpecification(Logger logger)
|
|||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
if (localMovie.ExistingFile)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirInfo = new FileInfo(localMovie.Path).Directory;
|
||||||
|
|
||||||
|
if (dirInfo == null)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var folderInfo = Parser.Parser.ParseTitle(dirInfo.Name);
|
||||||
|
|
||||||
|
if (folderInfo == null)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folderInfo.FullSeason)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
||||||
{
|
{
|
||||||
if (localEpisode.ExistingFile)
|
if (localEpisode.ExistingFile)
|
||||||
|
@ -37,5 +37,27 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
|||||||
|
|
||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(LocalMovie localEpisode)
|
||||||
|
{
|
||||||
|
if (localEpisode.ExistingFile)
|
||||||
|
{
|
||||||
|
_logger.Debug("Existing file, skipping sample check");
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var sample = _detectSample.IsSample(localEpisode.Movie,
|
||||||
|
localEpisode.Quality,
|
||||||
|
localEpisode.Path,
|
||||||
|
localEpisode.Size,
|
||||||
|
false);
|
||||||
|
|
||||||
|
if (sample)
|
||||||
|
{
|
||||||
|
return Decision.Reject("Sample");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,5 +56,40 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
|||||||
|
|
||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(LocalMovie localEpisode)
|
||||||
|
{
|
||||||
|
if (localEpisode.ExistingFile)
|
||||||
|
{
|
||||||
|
_logger.Debug("{0} is in series folder, skipping unpacking check", localEpisode.Path);
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var workingFolder in _configService.DownloadClientWorkingFolders.Split('|'))
|
||||||
|
{
|
||||||
|
DirectoryInfo parent = Directory.GetParent(localEpisode.Path);
|
||||||
|
while (parent != null)
|
||||||
|
{
|
||||||
|
if (parent.Name.StartsWith(workingFolder))
|
||||||
|
{
|
||||||
|
if (OsInfo.IsNotWindows)
|
||||||
|
{
|
||||||
|
_logger.Debug("{0} is still being unpacked", localEpisode.Path);
|
||||||
|
return Decision.Reject("File is still being unpacked");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_diskProvider.FileGetLastWrite(localEpisode.Path) > DateTime.UtcNow.AddMinutes(-5))
|
||||||
|
{
|
||||||
|
_logger.Debug("{0} appears to be unpacking still", localEpisode.Path);
|
||||||
|
return Decision.Reject("File is still being unpacked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = parent.Parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
@ -27,5 +28,10 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
|||||||
_logger.Debug("Episode file on disk contains more episodes than this file contains");
|
_logger.Debug("Episode file on disk contains more episodes than this file contains");
|
||||||
return Decision.Reject("Episode file on disk contains more episodes than this file contains");
|
return Decision.Reject("Episode file on disk contains more episodes than this file contains");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
@ -13,6 +14,11 @@ public UnverifiedSceneNumberingSpecification(Logger logger)
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
||||||
{
|
{
|
||||||
if (localEpisode.ExistingFile)
|
if (localEpisode.ExistingFile)
|
||||||
|
@ -26,5 +26,10 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
|||||||
|
|
||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(LocalMovie localEpisode)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
src/NzbDrone.Core/MediaFiles/Events/MovieDownloadedEvent.cs
Normal file
20
src/NzbDrone.Core/MediaFiles/Events/MovieDownloadedEvent.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
{
|
||||||
|
public class MovieDownloadedEvent : IEvent
|
||||||
|
{
|
||||||
|
public LocalMovie Movie { get; private set; }
|
||||||
|
public MovieFile MovieFile { get; private set; }
|
||||||
|
public List<MovieFile> OldFiles { get; private set; }
|
||||||
|
|
||||||
|
public MovieDownloadedEvent(LocalMovie episode, MovieFile episodeFile, List<MovieFile> oldFiles)
|
||||||
|
{
|
||||||
|
Movie = episode;
|
||||||
|
MovieFile = episodeFile;
|
||||||
|
OldFiles = oldFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/NzbDrone.Core/MediaFiles/Events/MovieFileAddedEvent.cs
Normal file
14
src/NzbDrone.Core/MediaFiles/Events/MovieFileAddedEvent.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
{
|
||||||
|
public class MovieFileAddedEvent : IEvent
|
||||||
|
{
|
||||||
|
public MovieFile MovieFile { get; private set; }
|
||||||
|
|
||||||
|
public MovieFileAddedEvent(MovieFile episodeFile)
|
||||||
|
{
|
||||||
|
MovieFile = episodeFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/NzbDrone.Core/MediaFiles/Events/MovieFileDeletedEvent.cs
Normal file
16
src/NzbDrone.Core/MediaFiles/Events/MovieFileDeletedEvent.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
{
|
||||||
|
public class MovieFileDeletedEvent : IEvent
|
||||||
|
{
|
||||||
|
public MovieFile MovieFile { get; private set; }
|
||||||
|
public DeleteMediaFileReason Reason { get; private set; }
|
||||||
|
|
||||||
|
public MovieFileDeletedEvent(MovieFile episodeFile, DeleteMediaFileReason reason)
|
||||||
|
{
|
||||||
|
MovieFile = episodeFile;
|
||||||
|
Reason = reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
{
|
||||||
|
public class MovieFolderCreatedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Movie Movie { get; private set; }
|
||||||
|
public MovieFile MovieFile { get; private set; }
|
||||||
|
public string SeriesFolder { get; set; }
|
||||||
|
public string SeasonFolder { get; set; }
|
||||||
|
public string MovieFolder { get; set; }
|
||||||
|
|
||||||
|
public MovieFolderCreatedEvent(Movie movie, MovieFile episodeFile)
|
||||||
|
{
|
||||||
|
Movie = movie;
|
||||||
|
MovieFile = episodeFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/NzbDrone.Core/MediaFiles/Events/MovieImportedEvent.cs
Normal file
32
src/NzbDrone.Core/MediaFiles/Events/MovieImportedEvent.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
{
|
||||||
|
public class MovieImportedEvent : IEvent
|
||||||
|
{
|
||||||
|
public LocalMovie MovieInfo { get; private set; }
|
||||||
|
public MovieFile ImportedMovie { get; private set; }
|
||||||
|
public bool NewDownload { get; private set; }
|
||||||
|
public string DownloadClient { get; private set; }
|
||||||
|
public string DownloadId { get; private set; }
|
||||||
|
public bool IsReadOnly { get; set; }
|
||||||
|
|
||||||
|
public MovieImportedEvent(LocalMovie episodeInfo, MovieFile importedMovie, bool newDownload)
|
||||||
|
{
|
||||||
|
MovieInfo = episodeInfo;
|
||||||
|
ImportedMovie = importedMovie;
|
||||||
|
NewDownload = newDownload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieImportedEvent(LocalMovie episodeInfo, MovieFile importedMovie, bool newDownload, string downloadClient, string downloadId, bool isReadOnly)
|
||||||
|
{
|
||||||
|
MovieInfo = episodeInfo;
|
||||||
|
ImportedMovie = importedMovie;
|
||||||
|
NewDownload = newDownload;
|
||||||
|
DownloadClient = downloadClient;
|
||||||
|
DownloadId = downloadId;
|
||||||
|
IsReadOnly = isReadOnly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,16 +7,20 @@
|
|||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.Tv.Events;
|
using NzbDrone.Core.Tv.Events;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles
|
namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
public interface IMediaFileService
|
public interface IMediaFileService
|
||||||
{
|
{
|
||||||
|
MovieFile Add(MovieFile episodeFile);
|
||||||
|
void Update(MovieFile episodeFile);
|
||||||
|
void Delete(MovieFile episodeFile, DeleteMediaFileReason reason);
|
||||||
EpisodeFile Add(EpisodeFile episodeFile);
|
EpisodeFile Add(EpisodeFile episodeFile);
|
||||||
void Update(EpisodeFile episodeFile);
|
void Update(EpisodeFile episodeFile);
|
||||||
void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason);
|
void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason);
|
||||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||||
List<EpisodeFile> GetFilesByMovie(int movieId);
|
List<MovieFile> GetFilesByMovie(int movieId);
|
||||||
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||||
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
||||||
List<string> FilterExistingFiles(List<string> files, Series series);
|
List<string> FilterExistingFiles(List<string> files, Series series);
|
||||||
@ -30,12 +34,14 @@ public class MediaFileService : IMediaFileService, IHandleAsync<SeriesDeletedEve
|
|||||||
{
|
{
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IMediaFileRepository _mediaFileRepository;
|
private readonly IMediaFileRepository _mediaFileRepository;
|
||||||
|
private readonly IMovieFileRepository _movieFileRepository;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public MediaFileService(IMediaFileRepository mediaFileRepository, IEventAggregator eventAggregator, Logger logger)
|
public MediaFileService(IMediaFileRepository mediaFileRepository, IMovieFileRepository movieFileRepository, IEventAggregator eventAggregator, Logger logger)
|
||||||
{
|
{
|
||||||
_mediaFileRepository = mediaFileRepository;
|
_mediaFileRepository = mediaFileRepository;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
|
_movieFileRepository = movieFileRepository;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,14 +67,24 @@ public void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason)
|
|||||||
_eventAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, reason));
|
_eventAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(MovieFile episodeFile, DeleteMediaFileReason reason)
|
||||||
|
{
|
||||||
|
//Little hack so we have the episodes and series attached for the event consumers
|
||||||
|
episodeFile.Movie.LazyLoad();
|
||||||
|
episodeFile.Path = Path.Combine(episodeFile.Movie.Value.Path, episodeFile.RelativePath);
|
||||||
|
|
||||||
|
_movieFileRepository.Delete(episodeFile);
|
||||||
|
_eventAggregator.PublishEvent(new MovieFileDeletedEvent(episodeFile, reason));
|
||||||
|
}
|
||||||
|
|
||||||
public List<EpisodeFile> GetFilesBySeries(int seriesId)
|
public List<EpisodeFile> GetFilesBySeries(int seriesId)
|
||||||
{
|
{
|
||||||
return _mediaFileRepository.GetFilesBySeries(seriesId);
|
return _mediaFileRepository.GetFilesBySeries(seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<EpisodeFile> GetFilesByMovie(int movieId)
|
public List<MovieFile> GetFilesByMovie(int movieId)
|
||||||
{
|
{
|
||||||
return _mediaFileRepository.GetFilesBySeries(movieId); //TODO: Update implementation for movie files.
|
return _movieFileRepository.GetFilesByMovie(movieId); //TODO: Update implementation for movie files.
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||||
@ -114,5 +130,18 @@ public void HandleAsync(SeriesDeletedEvent message)
|
|||||||
var files = GetFilesBySeries(message.Series.Id);
|
var files = GetFilesBySeries(message.Series.Id);
|
||||||
_mediaFileRepository.DeleteMany(files);
|
_mediaFileRepository.DeleteMany(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MovieFile Add(MovieFile episodeFile)
|
||||||
|
{
|
||||||
|
var addedFile = _movieFileRepository.Insert(episodeFile);
|
||||||
|
_eventAggregator.PublishEvent(new MovieFileAddedEvent(addedFile));
|
||||||
|
return addedFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(MovieFile episodeFile)
|
||||||
|
{
|
||||||
|
_movieFileRepository.Update(episodeFile);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
29
src/NzbDrone.Core/MediaFiles/MovieFile.cs
Normal file
29
src/NzbDrone.Core/MediaFiles/MovieFile.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Marr.Data;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
{
|
||||||
|
public class MovieFile : ModelBase
|
||||||
|
{
|
||||||
|
public int MovieId { get; set; }
|
||||||
|
public string RelativePath { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public DateTime DateAdded { get; set; }
|
||||||
|
public string SceneName { get; set; }
|
||||||
|
public string ReleaseGroup { get; set; }
|
||||||
|
public QualityModel Quality { get; set; }
|
||||||
|
public MediaInfoModel MediaInfo { get; set; }
|
||||||
|
public LazyLoaded<Movie> Movie { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("[{0}] {1}", Id, RelativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/NzbDrone.Core/MediaFiles/MovieFileMoveResult.cs
Normal file
15
src/NzbDrone.Core/MediaFiles/MovieFileMoveResult.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
{
|
||||||
|
public class MovieFileMoveResult
|
||||||
|
{
|
||||||
|
public MovieFileMoveResult()
|
||||||
|
{
|
||||||
|
OldFiles = new List<MovieFile>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieFile MovieFile { get; set; }
|
||||||
|
public List<MovieFile> OldFiles { get; set; }
|
||||||
|
}
|
||||||
|
}
|
191
src/NzbDrone.Core/MediaFiles/MovieFileMovingService.cs
Normal file
191
src/NzbDrone.Core/MediaFiles/MovieFileMovingService.cs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.EnsureThat;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Organizer;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
{
|
||||||
|
public interface IMoveMovieFiles
|
||||||
|
{
|
||||||
|
MovieFile MoveMovieFile(MovieFile movieFile, Movie movie);
|
||||||
|
MovieFile MoveMovieFile(MovieFile movieFile, LocalMovie localMovie);
|
||||||
|
MovieFile CopyMovieFile(MovieFile movieFile, LocalMovie localMovie);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MovieFileMovingService : IMoveMovieFiles
|
||||||
|
{
|
||||||
|
private readonly IMovieService _movieService;
|
||||||
|
private readonly IUpdateMovieFileService _updateMovieFileService;
|
||||||
|
private readonly IBuildFileNames _buildFileNames;
|
||||||
|
private readonly IDiskTransferService _diskTransferService;
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IMediaFileAttributeService _mediaFileAttributeService;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public MovieFileMovingService(IMovieService movieService,
|
||||||
|
IUpdateMovieFileService updateMovieFileService,
|
||||||
|
IBuildFileNames buildFileNames,
|
||||||
|
IDiskTransferService diskTransferService,
|
||||||
|
IDiskProvider diskProvider,
|
||||||
|
IMediaFileAttributeService mediaFileAttributeService,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
|
IConfigService configService,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_movieService = movieService;
|
||||||
|
_updateMovieFileService = updateMovieFileService;
|
||||||
|
_buildFileNames = buildFileNames;
|
||||||
|
_diskTransferService = diskTransferService;
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_mediaFileAttributeService = mediaFileAttributeService;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_configService = configService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieFile MoveMovieFile(MovieFile movieFile, Movie movie)
|
||||||
|
{
|
||||||
|
var newFileName = _buildFileNames.BuildFileName(movie, movieFile);
|
||||||
|
var filePath = _buildFileNames.BuildFilePath(movie, newFileName, Path.GetExtension(movieFile.RelativePath));
|
||||||
|
|
||||||
|
EnsureMovieFolder(movieFile, movie, filePath);
|
||||||
|
|
||||||
|
_logger.Debug("Renaming movie file: {0} to {1}", movieFile, filePath);
|
||||||
|
|
||||||
|
return TransferFile(movieFile, movie, filePath, TransferMode.Move);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieFile MoveMovieFile(MovieFile movieFile, LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
var newFileName = _buildFileNames.BuildFileName(localMovie.Movie, movieFile);
|
||||||
|
var filePath = _buildFileNames.BuildFilePath(localMovie.Movie, newFileName, Path.GetExtension(localMovie.Path));
|
||||||
|
|
||||||
|
EnsureMovieFolder(movieFile, localMovie, filePath);
|
||||||
|
|
||||||
|
_logger.Debug("Moving movie file: {0} to {1}", movieFile.Path, filePath);
|
||||||
|
|
||||||
|
return TransferFile(movieFile, localMovie.Movie, filePath, TransferMode.Move);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieFile CopyMovieFile(MovieFile movieFile, LocalMovie localMovie)
|
||||||
|
{
|
||||||
|
var newFileName = _buildFileNames.BuildFileName(localMovie.Movie, movieFile);
|
||||||
|
var filePath = _buildFileNames.BuildFilePath(localMovie.Movie, newFileName, Path.GetExtension(localMovie.Path));
|
||||||
|
|
||||||
|
EnsureMovieFolder(movieFile, localMovie, filePath);
|
||||||
|
|
||||||
|
if (_configService.CopyUsingHardlinks)
|
||||||
|
{
|
||||||
|
_logger.Debug("Hardlinking movie file: {0} to {1}", movieFile.Path, filePath);
|
||||||
|
return TransferFile(movieFile, localMovie.Movie, filePath, TransferMode.HardLinkOrCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug("Copying movie file: {0} to {1}", movieFile.Path, filePath);
|
||||||
|
return TransferFile(movieFile, localMovie.Movie, filePath, TransferMode.Copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MovieFile TransferFile(MovieFile movieFile, Movie movie, string destinationFilePath, TransferMode mode)
|
||||||
|
{
|
||||||
|
Ensure.That(movieFile, () => movieFile).IsNotNull();
|
||||||
|
Ensure.That(movie,() => movie).IsNotNull();
|
||||||
|
Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath();
|
||||||
|
|
||||||
|
var movieFilePath = movieFile.Path ?? Path.Combine(movie.Path, movieFile.RelativePath);
|
||||||
|
|
||||||
|
if (!_diskProvider.FileExists(movieFilePath))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Movie file path does not exist", movieFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movieFilePath == destinationFilePath)
|
||||||
|
{
|
||||||
|
throw new SameFilenameException("File not moved, source and destination are the same", movieFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
_diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode);
|
||||||
|
|
||||||
|
movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath);
|
||||||
|
|
||||||
|
_updateMovieFileService.ChangeFileDateForFile(movieFile, movie);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_mediaFileAttributeService.SetFolderLastWriteTime(movie.Path, movieFile.DateAdded);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Warn(ex, "Unable to set last write time");
|
||||||
|
}
|
||||||
|
|
||||||
|
_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
|
||||||
|
|
||||||
|
return movieFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureMovieFolder(MovieFile movieFile, LocalMovie localMovie, string filePath)
|
||||||
|
{
|
||||||
|
EnsureMovieFolder(movieFile, localMovie.Movie, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureMovieFolder(MovieFile movieFile, Movie movie, string filePath)
|
||||||
|
{
|
||||||
|
var movieFolder = Path.GetDirectoryName(filePath);
|
||||||
|
var rootFolder = new OsPath(movieFolder).Directory.FullPath;
|
||||||
|
|
||||||
|
if (!_diskProvider.FolderExists(rootFolder))
|
||||||
|
{
|
||||||
|
throw new DirectoryNotFoundException(string.Format("Root folder '{0}' was not found.", rootFolder));
|
||||||
|
}
|
||||||
|
|
||||||
|
var changed = false;
|
||||||
|
var newEvent = new MovieFolderCreatedEvent(movie, movieFile);
|
||||||
|
|
||||||
|
if (!_diskProvider.FolderExists(movieFolder))
|
||||||
|
{
|
||||||
|
CreateFolder(movieFolder);
|
||||||
|
newEvent.SeriesFolder = movieFolder;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
_eventAggregator.PublishEvent(newEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateFolder(string directoryName)
|
||||||
|
{
|
||||||
|
Ensure.That(directoryName, () => directoryName).IsNotNullOrWhiteSpace();
|
||||||
|
|
||||||
|
var parentFolder = new OsPath(directoryName).Directory.FullPath;
|
||||||
|
if (!_diskProvider.FolderExists(parentFolder))
|
||||||
|
{
|
||||||
|
CreateFolder(parentFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_diskProvider.CreateFolder(directoryName);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to create directory: " + directoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
_mediaFileAttributeService.SetFolderPermissions(directoryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/NzbDrone.Core/MediaFiles/MovieFileRepository.cs
Normal file
32
src/NzbDrone.Core/MediaFiles/MovieFileRepository.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
{
|
||||||
|
public interface IMovieFileRepository : IBasicRepository<MovieFile>
|
||||||
|
{
|
||||||
|
List<MovieFile> GetFilesByMovie(int movieId);
|
||||||
|
List<MovieFile> GetFilesWithoutMediaInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class MovieFileRepository : BasicRepository<MovieFile>, IMovieFileRepository
|
||||||
|
{
|
||||||
|
public MovieFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MovieFile> GetFilesByMovie(int movieId)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.MovieId == movieId).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MovieFile> GetFilesWithoutMediaInfo()
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.MediaInfo == null).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
147
src/NzbDrone.Core/MediaFiles/UpdateMovieFileService.cs
Normal file
147
src/NzbDrone.Core/MediaFiles/UpdateMovieFileService.cs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Exceptron;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Instrumentation.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
{
|
||||||
|
public interface IUpdateMovieFileService
|
||||||
|
{
|
||||||
|
void ChangeFileDateForFile(MovieFile movieFile, Movie movie);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateMovieFileService : IUpdateMovieFileService,
|
||||||
|
IHandle<SeriesScannedEvent>
|
||||||
|
{
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly IMovieService _movieService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public UpdateMovieFileService(IDiskProvider diskProvider,
|
||||||
|
IConfigService configService,
|
||||||
|
IMovieService movieService,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_configService = configService;
|
||||||
|
_movieService = movieService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeFileDateForFile(MovieFile movieFile, Movie movie)
|
||||||
|
{
|
||||||
|
ChangeFileDate(movieFile, movie);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ChangeFileDate(MovieFile movieFile, Movie movie)
|
||||||
|
{
|
||||||
|
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(SeriesScannedEvent message)
|
||||||
|
{
|
||||||
|
if (_configService.FileDate == FileDateType.None)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* var movies = _movieService.MoviesWithFiles(message.Series.Id);
|
||||||
|
|
||||||
|
var movieFiles = new List<MovieFile>();
|
||||||
|
var updated = new List<MovieFile>();
|
||||||
|
|
||||||
|
foreach (var group in movies.GroupBy(e => e.MovieFileId))
|
||||||
|
{
|
||||||
|
var moviesInFile = group.Select(e => e).ToList();
|
||||||
|
var movieFile = moviesInFile.First().MovieFile;
|
||||||
|
|
||||||
|
movieFiles.Add(movieFile);
|
||||||
|
|
||||||
|
if (ChangeFileDate(movieFile, message.Series, moviesInFile))
|
||||||
|
{
|
||||||
|
updated.Add(movieFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated.Any())
|
||||||
|
{
|
||||||
|
_logger.ProgressDebug("Changed file date for {0} files of {1} in {2}", updated.Count, movieFiles.Count, message.Series.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.ProgressDebug("No file dates changed for {0}", message.Series.Title);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ChangeFileDateToLocalAirDate(string filePath, string fileDate, string fileTime)
|
||||||
|
{
|
||||||
|
DateTime airDate;
|
||||||
|
|
||||||
|
if (DateTime.TryParse(fileDate + ' ' + fileTime, out airDate))
|
||||||
|
{
|
||||||
|
// avoiding false +ve checks and set date skewing by not using UTC (Windows)
|
||||||
|
DateTime oldDateTime = _diskProvider.FileGetLastWrite(filePath);
|
||||||
|
|
||||||
|
if (!DateTime.Equals(airDate, oldDateTime))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_diskProvider.FileSetLastWriteTime(filePath, airDate);
|
||||||
|
_logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldDateTime, airDate);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Warn(ex, "Unable to set date of file [" + filePath + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Debug("Could not create valid date to change file [{0}]", filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ChangeFileDateToUtcAirDate(string filePath, DateTime airDateUtc)
|
||||||
|
{
|
||||||
|
DateTime oldLastWrite = _diskProvider.FileGetLastWrite(filePath);
|
||||||
|
|
||||||
|
if (!DateTime.Equals(airDateUtc, oldLastWrite))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_diskProvider.FileSetLastWriteTime(filePath, airDateUtc);
|
||||||
|
_logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldLastWrite, airDateUtc);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ex.ExceptronIgnoreOnMono();
|
||||||
|
_logger.Warn(ex, "Unable to set date of file [" + filePath + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
public interface IUpgradeMediaFiles
|
public interface IUpgradeMediaFiles
|
||||||
{
|
{
|
||||||
EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false);
|
EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false);
|
||||||
|
MovieFileMoveResult UpgradeMovieFile(MovieFile movieFile, LocalMovie localMovie, bool copyOnly = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpgradeMediaFileService : IUpgradeMediaFiles
|
public class UpgradeMediaFileService : IUpgradeMediaFiles
|
||||||
@ -16,22 +17,59 @@ public class UpgradeMediaFileService : IUpgradeMediaFiles
|
|||||||
private readonly IRecycleBinProvider _recycleBinProvider;
|
private readonly IRecycleBinProvider _recycleBinProvider;
|
||||||
private readonly IMediaFileService _mediaFileService;
|
private readonly IMediaFileService _mediaFileService;
|
||||||
private readonly IMoveEpisodeFiles _episodeFileMover;
|
private readonly IMoveEpisodeFiles _episodeFileMover;
|
||||||
|
private readonly IMoveMovieFiles _movieFileMover;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider,
|
public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider,
|
||||||
IMediaFileService mediaFileService,
|
IMediaFileService mediaFileService,
|
||||||
IMoveEpisodeFiles episodeFileMover,
|
IMoveEpisodeFiles episodeFileMover,
|
||||||
|
IMoveMovieFiles movieFileMover,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_recycleBinProvider = recycleBinProvider;
|
_recycleBinProvider = recycleBinProvider;
|
||||||
_mediaFileService = mediaFileService;
|
_mediaFileService = mediaFileService;
|
||||||
_episodeFileMover = episodeFileMover;
|
_episodeFileMover = episodeFileMover;
|
||||||
|
_movieFileMover = movieFileMover;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MovieFileMoveResult UpgradeMovieFile(MovieFile episodeFile, LocalMovie localEpisode, bool copyOnly = false)
|
||||||
|
{
|
||||||
|
var moveFileResult = new MovieFileMoveResult();
|
||||||
|
var existingFile = localEpisode.Movie.MovieFile;
|
||||||
|
|
||||||
|
if (existingFile.IsLoaded)
|
||||||
|
{
|
||||||
|
var file = existingFile.Value;
|
||||||
|
var episodeFilePath = Path.Combine(localEpisode.Movie.Path, file.RelativePath);
|
||||||
|
|
||||||
|
if (_diskProvider.FileExists(episodeFilePath))
|
||||||
|
{
|
||||||
|
_logger.Debug("Removing existing episode file: {0}", file);
|
||||||
|
_recycleBinProvider.DeleteFile(episodeFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveFileResult.OldFiles.Add(file);
|
||||||
|
_mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (copyOnly)
|
||||||
|
{
|
||||||
|
moveFileResult.MovieFile = _movieFileMover.CopyMovieFile(episodeFile, localEpisode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
moveFileResult.MovieFile= _movieFileMover.MoveMovieFile(episodeFile, localEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return moveFileResult;
|
||||||
|
}
|
||||||
|
|
||||||
public EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false)
|
public EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false)
|
||||||
{
|
{
|
||||||
var moveFileResult = new EpisodeFileMoveResult();
|
var moveFileResult = new EpisodeFileMoveResult();
|
||||||
|
@ -249,6 +249,7 @@
|
|||||||
<Compile Include="Datastore\Migration\068_add_release_restrictions.cs" />
|
<Compile Include="Datastore\Migration\068_add_release_restrictions.cs" />
|
||||||
<Compile Include="Datastore\Migration\069_quality_proper.cs" />
|
<Compile Include="Datastore\Migration\069_quality_proper.cs" />
|
||||||
<Compile Include="Datastore\Migration\070_delay_profile.cs" />
|
<Compile Include="Datastore\Migration\070_delay_profile.cs" />
|
||||||
|
<Compile Include="Datastore\Migration\104_add_moviefiles_table.cs" />
|
||||||
<Compile Include="Datastore\Migration\096_disable_kickass.cs" />
|
<Compile Include="Datastore\Migration\096_disable_kickass.cs" />
|
||||||
<Compile Include="Datastore\Migration\095_add_additional_episodes_index.cs" />
|
<Compile Include="Datastore\Migration\095_add_additional_episodes_index.cs" />
|
||||||
<Compile Include="Datastore\Migration\103_fix_metadata_file_extensions.cs" />
|
<Compile Include="Datastore\Migration\103_fix_metadata_file_extensions.cs" />
|
||||||
@ -700,6 +701,17 @@
|
|||||||
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
|
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
|
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
|
||||||
|
<Compile Include="MediaFiles\DownloadedMovieCommandService.cs" />
|
||||||
|
<Compile Include="MediaFiles\MovieFileMovingService.cs" />
|
||||||
|
<Compile Include="MediaFiles\Events\MovieDownloadedEvent.cs" />
|
||||||
|
<Compile Include="MediaFiles\Events\MovieFileAddedEvent.cs" />
|
||||||
|
<Compile Include="MediaFiles\Events\MovieFileDeletedEvent.cs" />
|
||||||
|
<Compile Include="MediaFiles\Events\MovieFolderCreatedEvent.cs" />
|
||||||
|
<Compile Include="MediaFiles\Events\MovieImportedEvent.cs" />
|
||||||
|
<Compile Include="MediaFiles\MovieFileRepository.cs" />
|
||||||
|
<Compile Include="MediaFiles\MovieFileMoveResult.cs" />
|
||||||
|
<Compile Include="MediaFiles\MovieFile.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\ImportApprovedMovie.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\ImportMode.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\ImportMode.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
|
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\RenameSeriesCommand.cs" />
|
<Compile Include="MediaFiles\Commands\RenameSeriesCommand.cs" />
|
||||||
@ -762,6 +774,7 @@
|
|||||||
<Compile Include="MediaFiles\RenameEpisodeFilePreview.cs" />
|
<Compile Include="MediaFiles\RenameEpisodeFilePreview.cs" />
|
||||||
<Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
|
<Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
|
||||||
<Compile Include="MediaFiles\SameFilenameException.cs" />
|
<Compile Include="MediaFiles\SameFilenameException.cs" />
|
||||||
|
<Compile Include="MediaFiles\UpdateMovieFileService.cs" />
|
||||||
<Compile Include="MediaFiles\UpdateEpisodeFileService.cs" />
|
<Compile Include="MediaFiles\UpdateEpisodeFileService.cs" />
|
||||||
<Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
|
<Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
|
||||||
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
|
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
|
||||||
@ -875,6 +888,7 @@
|
|||||||
<Compile Include="Parser\IsoLanguage.cs" />
|
<Compile Include="Parser\IsoLanguage.cs" />
|
||||||
<Compile Include="Parser\IsoLanguages.cs" />
|
<Compile Include="Parser\IsoLanguages.cs" />
|
||||||
<Compile Include="Parser\LanguageParser.cs" />
|
<Compile Include="Parser\LanguageParser.cs" />
|
||||||
|
<Compile Include="Parser\Model\LocalMovie.cs" />
|
||||||
<Compile Include="Parser\Model\RemoteMovie.cs" />
|
<Compile Include="Parser\Model\RemoteMovie.cs" />
|
||||||
<Compile Include="Profiles\Delay\DelayProfile.cs" />
|
<Compile Include="Profiles\Delay\DelayProfile.cs" />
|
||||||
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
||||||
|
@ -17,6 +17,8 @@ namespace NzbDrone.Core.Organizer
|
|||||||
public interface IBuildFileNames
|
public interface IBuildFileNames
|
||||||
{
|
{
|
||||||
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
|
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
|
||||||
|
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 BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
||||||
string BuildSeasonPath(Series series, int seasonNumber);
|
string BuildSeasonPath(Series series, int seasonNumber);
|
||||||
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
||||||
@ -137,6 +139,66 @@ public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile e
|
|||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null)
|
||||||
|
{
|
||||||
|
if (namingConfig == null)
|
||||||
|
{
|
||||||
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!namingConfig.RenameEpisodes)
|
||||||
|
{
|
||||||
|
return GetOriginalTitle(movieFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard)
|
||||||
|
{
|
||||||
|
throw new NamingFormatException("Standard episode format cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily)
|
||||||
|
{
|
||||||
|
throw new NamingFormatException("Daily episode format cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namingConfig.AnimeEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Anime)
|
||||||
|
{
|
||||||
|
throw new NamingFormatException("Anime episode format cannot be empty");
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*var pattern = namingConfig.StandardEpisodeFormat;
|
||||||
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
|
episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList();
|
||||||
|
|
||||||
|
if (series.SeriesType == SeriesTypes.Daily)
|
||||||
|
{
|
||||||
|
pattern = namingConfig.DailyEpisodeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series.SeriesType == SeriesTypes.Anime && episodes.All(e => e.AbsoluteEpisodeNumber.HasValue))
|
||||||
|
{
|
||||||
|
pattern = namingConfig.AnimeEpisodeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
|
||||||
|
pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
|
||||||
|
|
||||||
|
AddSeriesTokens(tokenHandlers, series);
|
||||||
|
AddEpisodeTokens(tokenHandlers, episodes);
|
||||||
|
AddEpisodeFileTokens(tokenHandlers, episodeFile);
|
||||||
|
AddQualityTokens(tokenHandlers, series, episodeFile);
|
||||||
|
AddMediaInfoTokens(tokenHandlers, episodeFile);
|
||||||
|
|
||||||
|
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
||||||
|
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
||||||
|
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);*/
|
||||||
|
|
||||||
|
//TODO: Update namingConfig for Movies!
|
||||||
|
|
||||||
|
return GetOriginalTitle(movieFile);
|
||||||
|
}
|
||||||
|
|
||||||
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
||||||
{
|
{
|
||||||
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
||||||
@ -146,6 +208,15 @@ public string BuildFilePath(Series series, int seasonNumber, string fileName, st
|
|||||||
return Path.Combine(path, fileName + extension);
|
return Path.Combine(path, fileName + extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string BuildFilePath(Movie movie, string fileName, string extension)
|
||||||
|
{
|
||||||
|
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
||||||
|
|
||||||
|
var path = movie.Path;
|
||||||
|
|
||||||
|
return Path.Combine(path, fileName + extension);
|
||||||
|
}
|
||||||
|
|
||||||
public string BuildSeasonPath(Series series, int seasonNumber)
|
public string BuildSeasonPath(Series series, int seasonNumber)
|
||||||
{
|
{
|
||||||
var path = series.Path;
|
var path = series.Path;
|
||||||
@ -246,7 +317,7 @@ public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig nami
|
|||||||
|
|
||||||
public string GetMovieFolder(Movie movie)
|
public string GetMovieFolder(Movie movie)
|
||||||
{
|
{
|
||||||
return CleanFolderName(Parser.Parser.CleanSeriesTitle(movie.Title));
|
return CleanFolderName(movie.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string CleanTitle(string title)
|
public static string CleanTitle(string title)
|
||||||
@ -774,6 +845,26 @@ private string GetOriginalFileName(EpisodeFile episodeFile)
|
|||||||
|
|
||||||
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetOriginalTitle(MovieFile episodeFile)
|
||||||
|
{
|
||||||
|
if (episodeFile.SceneName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return GetOriginalFileName(episodeFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return episodeFile.SceneName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetOriginalFileName(MovieFile episodeFile)
|
||||||
|
{
|
||||||
|
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return Path.GetFileNameWithoutExtension(episodeFile.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class TokenMatch
|
internal sealed class TokenMatch
|
||||||
|
29
src/NzbDrone.Core/Parser/Model/LocalMovie.cs
Normal file
29
src/NzbDrone.Core/Parser/Model/LocalMovie.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Parser.Model
|
||||||
|
{
|
||||||
|
public class LocalMovie
|
||||||
|
{
|
||||||
|
public LocalMovie()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
|
||||||
|
public Movie Movie { get; set; }
|
||||||
|
public QualityModel Quality { get; set; }
|
||||||
|
public MediaInfoModel MediaInfo { get; set; }
|
||||||
|
public bool ExistingFile { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@ public interface IParsingService
|
|||||||
{
|
{
|
||||||
LocalEpisode GetLocalEpisode(string filename, Series series);
|
LocalEpisode GetLocalEpisode(string filename, Series series);
|
||||||
LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
|
LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
|
||||||
|
LocalMovie GetLocalMovie(string filename, Movie movie);
|
||||||
|
LocalMovie GetLocalMovie(string filename, Movie movie, ParsedEpisodeInfo folderInfo, bool sceneSource);
|
||||||
Series GetSeries(string title);
|
Series GetSeries(string title);
|
||||||
Movie GetMovie(string title);
|
Movie GetMovie(string title);
|
||||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||||
@ -99,6 +101,46 @@ public LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisod
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalMovie GetLocalMovie(string filename, Movie movie)
|
||||||
|
{
|
||||||
|
return GetLocalMovie(filename, movie, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalMovie GetLocalMovie(string filename, Movie movie, ParsedEpisodeInfo folderInfo, bool sceneSource)
|
||||||
|
{
|
||||||
|
ParsedEpisodeInfo parsedEpisodeInfo;
|
||||||
|
|
||||||
|
if (folderInfo != null)
|
||||||
|
{
|
||||||
|
parsedEpisodeInfo = folderInfo.JsonClone();
|
||||||
|
parsedEpisodeInfo.Quality = QualityParser.ParseQuality(Path.GetFileName(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parsedEpisodeInfo = Parser.ParsePath(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedEpisodeInfo == null)
|
||||||
|
{
|
||||||
|
if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(filename)))
|
||||||
|
{
|
||||||
|
_logger.Warn("Unable to parse episode info from path {0}", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LocalMovie
|
||||||
|
{
|
||||||
|
Movie = movie,
|
||||||
|
Quality = parsedEpisodeInfo.Quality,
|
||||||
|
Path = filename,
|
||||||
|
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||||
|
ExistingFile = movie.Path.IsParentPath(filename)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public Series GetSeries(string title)
|
public Series GetSeries(string title)
|
||||||
{
|
{
|
||||||
var parsedEpisodeInfo = Parser.ParseTitle(title);
|
var parsedEpisodeInfo = Parser.ParseTitle(title);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Profiles;
|
using NzbDrone.Core.Profiles;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Tv
|
namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
@ -41,6 +42,8 @@ public Movie()
|
|||||||
public LazyLoaded<Profile> Profile { get; set; }
|
public LazyLoaded<Profile> Profile { get; set; }
|
||||||
public HashSet<int> Tags { get; set; }
|
public HashSet<int> Tags { get; set; }
|
||||||
public AddMovieOptions AddOptions { get; set; }
|
public AddMovieOptions AddOptions { get; set; }
|
||||||
|
public LazyLoaded<MovieFile> MovieFile { get; set; }
|
||||||
|
public int MovieFileId { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
@ -11,6 +13,8 @@ public interface IMovieRepository : IBasicRepository<Movie>
|
|||||||
Movie FindByTitle(string cleanTitle);
|
Movie FindByTitle(string cleanTitle);
|
||||||
Movie FindByTitle(string cleanTitle, int year);
|
Movie FindByTitle(string cleanTitle, int year);
|
||||||
Movie FindByImdbId(string imdbid);
|
Movie FindByImdbId(string imdbid);
|
||||||
|
List<Movie> GetMoviesByFileId(int fileId);
|
||||||
|
void SetFileId(int fileId, int movieId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MovieRepository : BasicRepository<Movie>, IMovieRepository
|
public class MovieRepository : BasicRepository<Movie>, IMovieRepository
|
||||||
@ -46,5 +50,16 @@ public Movie FindByImdbId(string imdbid)
|
|||||||
{
|
{
|
||||||
return Query.Where(s => s.ImdbId == imdbid).SingleOrDefault();
|
return Query.Where(s => s.ImdbId == imdbid).SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Movie> GetMoviesByFileId(int fileId)
|
||||||
|
{
|
||||||
|
return Query.Where(m => m.MovieFileId == fileId).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFileId(int episodeId, int fileId)
|
||||||
|
{
|
||||||
|
SetFields(new Movie { Id = episodeId, MovieFileId = fileId }, movie => movie.MovieFileId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,6 +10,8 @@
|
|||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Tv.Events;
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Tv
|
namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
@ -22,6 +24,7 @@ public interface IMovieService
|
|||||||
Movie FindByTitle(string title);
|
Movie FindByTitle(string title);
|
||||||
Movie FindByTitle(string title, int year);
|
Movie FindByTitle(string title, int year);
|
||||||
Movie FindByTitleInexact(string title);
|
Movie FindByTitleInexact(string title);
|
||||||
|
Movie GetMovieByFileId(int fileId);
|
||||||
void DeleteMovie(int movieId, bool deleteFiles);
|
void DeleteMovie(int movieId, bool deleteFiles);
|
||||||
List<Movie> GetAllMovies();
|
List<Movie> GetAllMovies();
|
||||||
Movie UpdateMovie(Movie movie);
|
Movie UpdateMovie(Movie movie);
|
||||||
@ -30,7 +33,8 @@ public interface IMovieService
|
|||||||
void RemoveAddOptions(Movie movie);
|
void RemoveAddOptions(Movie movie);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MovieService : IMovieService
|
public class MovieService : IMovieService, IHandle<MovieFileAddedEvent>,
|
||||||
|
IHandle<MovieFileDeletedEvent>
|
||||||
{
|
{
|
||||||
private readonly IMovieRepository _movieRepository;
|
private readonly IMovieRepository _movieRepository;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
@ -195,5 +199,24 @@ public void RemoveAddOptions(Movie movie)
|
|||||||
{
|
{
|
||||||
_movieRepository.SetFields(movie, s => s.AddOptions);
|
_movieRepository.SetFields(movie, s => s.AddOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Handle(MovieFileAddedEvent message)
|
||||||
|
{
|
||||||
|
_movieRepository.SetFileId(message.MovieFile.Id, message.MovieFile.Movie.Value.Id);
|
||||||
|
_logger.Debug("Linking [{0}] > [{1}]", message.MovieFile.RelativePath, message.MovieFile.Movie.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MovieFileDeletedEvent message)
|
||||||
|
{
|
||||||
|
var movie = _movieRepository.GetMoviesByFileId(message.MovieFile.Id).First();
|
||||||
|
movie.MovieFileId = 0;
|
||||||
|
|
||||||
|
UpdateMovie(movie);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie GetMovieByFileId(int fileId)
|
||||||
|
{
|
||||||
|
return _movieRepository.GetMoviesByFileId(fileId).First();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user