Merge remote-tracking branch 'refs/remotes/galli-leo/develop' into develop

pull/23/head
Tim Turner 8 years ago
commit 96332978a0

BIN
.DS_Store vendored

Binary file not shown.

3
.gitignore vendored

@ -127,6 +127,9 @@ bin
obj
output/*
#Packages
Radarr_*/
Radarr_*.zip
#OS X metadata files
._*

@ -7,3 +7,6 @@ script: # the following commands are just examples, use whatever your build p
install:
- sudo apt-get install nodejs
- sudo apt-get install npm
after_success:
- chmod +x package.sh
- ./package.sh

@ -0,0 +1,50 @@
if [ $# -eq 0 ]; then
if [ "$TRAVIS_PULL_REQUEST" != false ]; then
echo "Need to supply version argument" && exit;
fi
fi
if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
VERSION="`date +%H:%M:%S`"
YEAR="`date +%Y`"
MONTH="`date +%m`"
DAY="`date +%d`"
else
VERSION=$1
fi
outputFolder='./_output'
outputFolderMono='./_output_mono'
outputFolderOsx='./_output_osx'
outputFolderOsxApp='./_output_osx_app'
tr -d "\r" < $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr > $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2
rm $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr
chmod +x $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2
mv $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr2 $outputFolderOsxApp/Sonarr.app/Contents/MacOS/Sonarr >& error.log
cp -r $outputFolder/ Radarr_Windows_$VERSION
cp -r $outputFolderMono/ Radarr_Mono_$VERSION
cp -r $outputFolderOsxApp/ Radarr_OSX_$VERSION
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_OSX_$VERSION.zip Radarr_OSX_$VERSION >& /dev/null
ftp -n ftp.leonardogalli.ch << END_SCRIPT
passive
quote USER $FTP_USER
quote PASS $FTP_PASS
mkdir builds
cd builds
mkdir $YEAR
cd $YEAR
mkdir $MONTH
cd $MONTH
mkdir $DAY
cd $DAY
binary
put Radarr_Windows_$VERSION.zip
put Radarr_Mono_$VERSION.zip
put Radarr_OSX_$VERSION.zip
quit
END_SCRIPT

@ -48,6 +48,7 @@ namespace NzbDrone.Api.Movie
public List<string> Genres { get; set; }
public HashSet<int> Tags { get; set; }
public DateTime Added { get; set; }
public AddMovieOptions AddOptions { get; set; }
public Ratings Ratings { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
@ -110,6 +111,7 @@ namespace NzbDrone.Api.Movie
Genres = model.Genres,
Tags = model.Tags,
Added = model.Added,
AddOptions = model.AddOptions,
Ratings = model.Ratings
};
}
@ -152,6 +154,7 @@ namespace NzbDrone.Api.Movie
Genres = resource.Genres,
Tags = resource.Tags,
Added = resource.Added,
AddOptions = resource.AddOptions,
Ratings = resource.Ratings
};
}
@ -167,6 +170,7 @@ namespace NzbDrone.Api.Movie
movie.RootFolderPath = resource.RootFolderPath;
movie.Tags = resource.Tags;
movie.AddOptions = resource.AddOptions;
return movie;
}

@ -40,7 +40,7 @@ namespace NzbDrone.Automation.Test
_runner.KillAll();
_runner.Start();
driver.Url = "http://localhost:8989";
driver.Url = "http://localhost:7878";
var page = new PageBase(driver);
page.WaitForNoSpinner();

@ -49,7 +49,7 @@ namespace NzbDrone.Common.Test
public void GetValue_Success()
{
const string key = "Port";
const string value = "8989";
const string value = "7878";
var result = Subject.GetValue(key, value);
@ -60,7 +60,7 @@ namespace NzbDrone.Common.Test
public void GetInt_Success()
{
const string key = "Port";
const int value = 8989;
const int value = 7878;
var result = Subject.GetValueInt(key, value);
@ -95,7 +95,7 @@ namespace NzbDrone.Common.Test
[Test]
public void GetPort_Success()
{
const int value = 8989;
const int value = 7878;
var result = Subject.Port;

@ -23,7 +23,7 @@ namespace NzbDrone.Console
{
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: 8989) 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.ReadLine();
Environment.Exit(1);

@ -133,7 +133,7 @@ namespace NzbDrone.Core.Configuration
}
}
public int Port => GetValueInt("Port", 8989);
public int Port => GetValueInt("Port", 7878);
public int SslPort => GetValueInt("SslPort", 9898);
@ -303,12 +303,12 @@ namespace NzbDrone.Core.Configuration
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))
{
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));
@ -323,7 +323,7 @@ namespace NzbDrone.Core.Configuration
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);
}
}

@ -63,7 +63,8 @@ namespace NzbDrone.Core.Datastore.Migration
.WithColumn("Ratings").AsString().Nullable()
.WithColumn("Genres").AsString().Nullable()
.WithColumn("Tags").AsString().Nullable()
.WithColumn("Certification").AsString().Nullable();
.WithColumn("Certification").AsString().Nullable()
.WithColumn("AddOptions").AsString().Nullable();
Create.TableForModel("Seasons")

@ -21,11 +21,12 @@ namespace NzbDrone.Core.Datastore.Migration
//Add HeldReleases
Create.TableForModel("PendingReleases")
.WithColumn("SeriesId").AsInt32()
.WithColumn("SeriesId").AsInt32().WithDefaultValue(0)
.WithColumn("Title").AsString()
.WithColumn("Added").AsDateTime()
.WithColumn("ParsedEpisodeInfo").AsString()
.WithColumn("Release").AsString();
.WithColumn("Release").AsString()
.WithColumn("MovieId").AsInt32().WithDefaultValue(0);
}
}
}

@ -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 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
sw.Stop();
_announcer.ElapsedTime(sw.Elapsed);
}
}

@ -76,10 +76,7 @@ namespace NzbDrone.Core.Datastore
.Relationship()
.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")
.Ignore(f => f.Path)
@ -89,6 +86,21 @@ namespace NzbDrone.Core.Datastore
query: (db, parent) => db.Query<Episode>().Where(c => c.EpisodeFileId == parent.Id).ToList())
.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")
.Ignore(e => e.SeriesTitle)
.Ignore(e => e.Series)

@ -60,7 +60,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
{
DownloadClient = Definition.Name,
DownloadId = Definition.Name + "_" + item.DownloadId,
Category = "sonarr",
Category = "Radarr",
Title = item.Title,
TotalSize = item.TotalSize,

@ -250,7 +250,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
return new NzbDroneValidationFailure("TvCategory", "Category is recommended")
{
IsWarning = true,
DetailedDescription = "Sonarr will not attempt to import completed downloads without a category."
DetailedDescription = "Radarr will not attempt to import completed downloads without a category."
};
}
@ -260,7 +260,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{
return new NzbDroneValidationFailure(String.Empty, "QBittorrent is configured to remove torrents when they reach their Share Ratio Limit")
{
DetailedDescription = "Sonarr will be unable to perform Completed Download Handling as configured. You can fix this in qBittorrent ('Tools -> Options...' in the menu) by changing 'Options -> BitTorrent -> Share Ratio Limiting' from 'Remove them' to 'Pause them'."
DetailedDescription = "Radarr will be unable to perform Completed Download Handling as configured. You can fix this in qBittorrent ('Tools -> Options...' in the menu) by changing 'Options -> BitTorrent -> Share Ratio Limiting' from 'Remove them' to 'Pause them'."
};
}
}

@ -27,7 +27,9 @@ namespace NzbDrone.Core.Download
private readonly IEventAggregator _eventAggregator;
private readonly IHistoryService _historyService;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
private readonly IParsingService _parsingService;
private readonly IMovieService _movieService;
private readonly Logger _logger;
private readonly ISeriesService _seriesService;
@ -35,15 +37,19 @@ namespace NzbDrone.Core.Download
IEventAggregator eventAggregator,
IHistoryService historyService,
IDownloadedEpisodesImportService downloadedEpisodesImportService,
IDownloadedMovieImportService downloadedMovieImportService,
IParsingService parsingService,
ISeriesService seriesService,
IMovieService movieService,
Logger logger)
{
_configService = configService;
_eventAggregator = eventAggregator;
_historyService = historyService;
_downloadedEpisodesImportService = downloadedEpisodesImportService;
_downloadedMovieImportService = downloadedMovieImportService;
_parsingService = parsingService;
_movieService = movieService;
_logger = logger;
_seriesService = seriesService;
}
@ -61,7 +67,7 @@ namespace NzbDrone.Core.Download
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;
}
@ -88,19 +94,31 @@ namespace NzbDrone.Core.Download
return;
}
var series = _parsingService.GetSeries(trackedDownload.DownloadItem.Title);
if (series == null)
{
if (historyItem != null)
{
series = _seriesService.GetSeries(historyItem.SeriesId);
//series = _seriesService.GetSeries(historyItem.SeriesId);
}
if (series == null)
{
trackedDownload.Warn("Series title mismatch, automatic import is not possible.");
return;
var movie = _parsingService.GetMovie(trackedDownload.DownloadItem.Title);
if (movie == null)
{
movie = _movieService.GetMovie(historyItem.MovieId);
if (movie == null)
{
trackedDownload.Warn("Movie title mismatch, automatic import is not possible.");
}
}
//trackedDownload.Warn("Series title mismatch, automatic import is not possible.");
//return;
}
}
}
@ -111,29 +129,59 @@ namespace NzbDrone.Core.Download
private void Import(TrackedDownload trackedDownload)
{
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
if (importResults.Empty())
if (trackedDownload.RemoteMovie.Movie != null)
{
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
return;
}
var importResults = _downloadedMovieImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteMovie.Movie, trackedDownload.DownloadItem);
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.Empty())
{
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
return;
}
if (importResults.Any(c => c.Result != ImportResultType.Imported))
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
{
var statusMessages = importResults
.Where(v => v.Result != ImportResultType.Imported)
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
.ToArray();
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
if (importResults.Empty())
{
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
return;
}
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 @@ namespace NzbDrone.Core.Download
{
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 @@ namespace NzbDrone.Core.Download
_logger.Error("Folder '{0}' is not writable.", 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)
};
}

@ -7,6 +7,7 @@ namespace NzbDrone.Core.Download.Pending
public class PendingRelease : ModelBase
{
public int SeriesId { get; set; }
public int MovieId { get; set; }
public string Title { get; set; }
public DateTime Added { get; set; }
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
@ -14,5 +15,6 @@ namespace NzbDrone.Core.Download.Pending
//Not persisted
public RemoteEpisode RemoteEpisode { get; set; }
public RemoteMovie RemoteMovie { get; set; }
}
}

@ -344,7 +344,7 @@ namespace NzbDrone.Core.Download.Pending
public void Handle(MovieGrabbedEvent message)
{
//RemoveGrabbed(message.Movie);
}
public void Handle(RssSyncCompleteEvent message)

@ -32,53 +32,112 @@ namespace NzbDrone.Core.Download
public ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions)
{
var qualifiedReports = GetQualifiedReports(decisions);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports);
//var qualifiedReports = GetQualifiedReports(decisions);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
var grabbed = new List<DownloadDecision>();
var pending = new List<DownloadDecision>();
foreach (var report in prioritizedDecisions)
{
var remoteEpisode = report.RemoteEpisode;
var episodeIds = remoteEpisode.Episodes.Select(e => e.Id).ToList();
//Skip if already grabbed
if (grabbed.SelectMany(r => r.RemoteEpisode.Episodes)
.Select(e => e.Id)
.ToList()
.Intersect(episodeIds)
.Any())
if (report.IsForMovie)
{
continue;
}
var remoteMovie = report.RemoteMovie;
if (report.TemporarilyRejected)
{
_pendingReleaseService.Add(report);
pending.Add(report);
continue;
}
if (report.TemporarilyRejected)
{
_pendingReleaseService.Add(report);
pending.Add(report);
continue;
}
if (pending.SelectMany(r => r.RemoteEpisode.Episodes)
.Select(e => e.Id)
.ToList()
.Intersect(episodeIds)
.Any())
{
continue;
}
if (remoteMovie == null || remoteMovie.Movie == null)
{
continue;
}
try
{
_downloadService.DownloadReport(remoteEpisode);
grabbed.Add(report);
List<int> movieIds = new List<int> { remoteMovie.Movie.Id };
//Skip if already grabbed
if (grabbed.Select(r => r.RemoteMovie.Movie)
.Select(e => e.Id)
.ToList()
.Intersect(movieIds)
.Any())
{
continue;
}
if (pending.Select(r => r.RemoteMovie.Movie)
.Select(e => e.Id)
.ToList()
.Intersect(movieIds)
.Any())
{
continue;
}
try
{
_downloadService.DownloadReport(remoteMovie);
grabbed.Add(report);
}
catch (Exception e)
{
//TODO: support for store & forward
//We'll need to differentiate between a download client error and an indexer error
_logger.Warn(e, "Couldn't add report to download queue. " + remoteMovie);
}
}
catch (Exception e)
else
{
//TODO: support for store & forward
//We'll need to differentiate between a download client error and an indexer error
_logger.Warn(e, "Couldn't add report to download queue. " + remoteEpisode);
var remoteEpisode = report.RemoteEpisode;
if (remoteEpisode == null || remoteEpisode.Episodes == null)
{
continue;
}
var episodeIds = remoteEpisode.Episodes.Select(e => e.Id).ToList();
//Skip if already grabbed
if (grabbed.SelectMany(r => r.RemoteEpisode.Episodes)
.Select(e => e.Id)
.ToList()
.Intersect(episodeIds)
.Any())
{
continue;
}
if (report.TemporarilyRejected)
{
_pendingReleaseService.Add(report);
pending.Add(report);
continue;
}
if (pending.SelectMany(r => r.RemoteEpisode.Episodes)
.Select(e => e.Id)
.ToList()
.Intersect(episodeIds)
.Any())
{
continue;
}
try
{
_downloadService.DownloadReport(remoteEpisode);
grabbed.Add(report);
}
catch (Exception e)
{
//TODO: support for store & forward
//We'll need to differentiate between a download client error and an indexer error
_logger.Warn(e, "Couldn't add report to download queue. " + remoteEpisode);
}
}
}

@ -271,7 +271,7 @@ namespace NzbDrone.Core.Download
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
{
_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);
}
@ -302,7 +302,7 @@ namespace NzbDrone.Core.Download
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
{
_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);
}
@ -371,7 +371,7 @@ namespace NzbDrone.Core.Download
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
{
_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);
}
@ -402,7 +402,7 @@ namespace NzbDrone.Core.Download
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
{
_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);
}

@ -0,0 +1,11 @@
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.IndexerSearch
{
public class MoviesSearchCommand : Command
{
public int MovieId { get; set; }
public override bool SendUpdatesToClient => true;
}
}

@ -0,0 +1,46 @@
using System.Linq;
using NLog;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.IndexerSearch
{
public class MovieSearchService : IExecute<MoviesSearchCommand>
{
private readonly IMovieService _seriesService;
private readonly ISearchForNzb _nzbSearchService;
private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly Logger _logger;
public MovieSearchService(IMovieService seriesService,
ISearchForNzb nzbSearchService,
IProcessDownloadDecisions processDownloadDecisions,
Logger logger)
{
_seriesService = seriesService;
_nzbSearchService = nzbSearchService;
_processDownloadDecisions = processDownloadDecisions;
_logger = logger;
}
public void Execute(MoviesSearchCommand message)
{
var series = _seriesService.GetMovie(message.MovieId);
var downloadedCount = 0;
if (!series.Monitored)
{
_logger.Debug("Movie {0} is not monitored, skipping search", series.Title);
}
var decisions = _nzbSearchService.MovieSearch(message.MovieId, false);//_nzbSearchService.SeasonSearch(message.MovieId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual);
downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count;
_logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount);
}
}
}

@ -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
{
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
@ -79,6 +80,57 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
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)
{
if (_largeSampleSizeQualities.Contains(quality.Quality))
@ -99,6 +151,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return false;
}
private int GetMinimumAllowedRuntime(Movie movie)
{
return 120; //2 minutes
}
private int GetMinimumAllowedRuntime(Series series)
{
//Webisodes - 90 seconds

@ -6,5 +6,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public interface IImportDecisionEngineSpecification
{
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 LocalEpisode LocalEpisode { get; private set; }
public LocalMovie LocalMovie { get; private set; }
public IEnumerable<Rejection> Rejections { get; private set; }
public bool Approved => Rejections.Empty();
@ -18,5 +19,20 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
LocalEpisode = localEpisode;
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 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
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);
localEpisode.Size = _diskProvider.GetFileSize(file);
localMovie.Quality = GetQuality(folderInfo, localMovie.Quality, movie);
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
if (sceneSource)
{
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
}
if (localEpisode.Episodes.Empty())
{
decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode"));
localMovie.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
decision = GetDecision(localMovie);
}
else
{
decision = GetDecision(localEpisode);
decision = GetDecision(localMovie);
}
}
else
{
localEpisode = new LocalEpisode();
var localEpisode = new LocalEpisode();
localEpisode.Path = file;
decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file"));
@ -139,13 +135,23 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
var localEpisode = new LocalEpisode { Path = file };
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file"));
}*/
}
//LocalMovie nullMovie = null;
decision = new ImportDecision(null, new Rejection("IMPLEMENTATION MISSING!!!"));
//decision = new ImportDecision(nullMovie, new Rejection("IMPLEMENTATION MISSING!!!"));
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)
{
ImportDecision decision = null;
@ -204,6 +210,33 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
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)
{
try
@ -292,6 +325,17 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
}) == 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)
{
if (UseFolderQuality(folderInfo, fileQuality, series))
@ -303,6 +347,31 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
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)
{
if (folderInfo == null)

@ -63,5 +63,48 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
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 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
if (localEpisode.ParsedEpisodeInfo.FullSeason)
{
//_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");
_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.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 NLog;
using NzbDrone.Core.DecisionEngine;
@ -14,6 +15,37 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
_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)
{
if (localEpisode.ExistingFile)

@ -37,5 +37,27 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
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 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
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 NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
@ -27,5 +28,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_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");
}
public Decision IsSatisfiedBy(LocalMovie localMovie)
{
return Decision.Accept();
}
}
}

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
@ -13,6 +14,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger = logger;
}
public Decision IsSatisfiedBy(LocalMovie localMovie)
{
return Decision.Accept();
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{
if (localEpisode.ExistingFile)

@ -26,5 +26,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
return Decision.Accept();
}
public Decision IsSatisfiedBy(LocalMovie localEpisode)
{
return Decision.Accept();
}
}
}

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

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

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

@ -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.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Common;
using System;
namespace NzbDrone.Core.MediaFiles
{
public interface IMediaFileService
{
MovieFile Add(MovieFile episodeFile);
void Update(MovieFile episodeFile);
void Delete(MovieFile episodeFile, DeleteMediaFileReason reason);
EpisodeFile Add(EpisodeFile episodeFile);
void Update(EpisodeFile episodeFile);
void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason);
List<EpisodeFile> GetFilesBySeries(int seriesId);
List<EpisodeFile> GetFilesByMovie(int movieId);
List<MovieFile> GetFilesByMovie(int movieId);
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
List<EpisodeFile> GetFilesWithoutMediaInfo();
List<string> FilterExistingFiles(List<string> files, Series series);
@ -30,12 +34,14 @@ namespace NzbDrone.Core.MediaFiles
{
private readonly IEventAggregator _eventAggregator;
private readonly IMediaFileRepository _mediaFileRepository;
private readonly IMovieFileRepository _movieFileRepository;
private readonly Logger _logger;
public MediaFileService(IMediaFileRepository mediaFileRepository, IEventAggregator eventAggregator, Logger logger)
public MediaFileService(IMediaFileRepository mediaFileRepository, IMovieFileRepository movieFileRepository, IEventAggregator eventAggregator, Logger logger)
{
_mediaFileRepository = mediaFileRepository;
_eventAggregator = eventAggregator;
_movieFileRepository = movieFileRepository;
_logger = logger;
}
@ -61,14 +67,24 @@ namespace NzbDrone.Core.MediaFiles
_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)
{
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)
@ -114,5 +130,18 @@ namespace NzbDrone.Core.MediaFiles
var files = GetFilesBySeries(message.Series.Id);
_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);
}
}
}

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

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

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

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

@ -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
{
EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false);
MovieFileMoveResult UpgradeMovieFile(MovieFile movieFile, LocalMovie localMovie, bool copyOnly = false);
}
public class UpgradeMediaFileService : IUpgradeMediaFiles
@ -16,22 +17,59 @@ namespace NzbDrone.Core.MediaFiles
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IMediaFileService _mediaFileService;
private readonly IMoveEpisodeFiles _episodeFileMover;
private readonly IMoveMovieFiles _movieFileMover;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider,
IMediaFileService mediaFileService,
IMoveEpisodeFiles episodeFileMover,
IMoveMovieFiles movieFileMover,
IDiskProvider diskProvider,
Logger logger)
{
_recycleBinProvider = recycleBinProvider;
_mediaFileService = mediaFileService;
_episodeFileMover = episodeFileMover;
_movieFileMover = movieFileMover;
_diskProvider = diskProvider;
_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)
{
var moveFileResult = new EpisodeFileMoveResult();

@ -249,6 +249,7 @@
<Compile Include="Datastore\Migration\068_add_release_restrictions.cs" />
<Compile Include="Datastore\Migration\069_quality_proper.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\095_add_additional_episodes_index.cs" />
<Compile Include="Datastore\Migration\103_fix_metadata_file_extensions.cs" />
@ -564,6 +565,8 @@
<Compile Include="Http\HttpProxySettingsProvider.cs" />
<Compile Include="Http\TorcacheHttpInterceptor.cs" />
<Compile Include="IndexerSearch\Definitions\MovieSearchCriteria.cs" />
<Compile Include="IndexerSearch\MoviesSearchCommand.cs" />
<Compile Include="IndexerSearch\MoviesSearchService.cs" />
<Compile Include="Indexers\BitMeTv\BitMeTv.cs" />
<Compile Include="Indexers\BitMeTv\BitMeTvSettings.cs" />
<Compile Include="Indexers\BitMeTv\BitMeTvRequestGenerator.cs" />
@ -698,6 +701,17 @@
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.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\Commands\RenameFilesCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameSeriesCommand.cs" />
@ -760,6 +774,7 @@
<Compile Include="MediaFiles\RenameEpisodeFilePreview.cs" />
<Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
<Compile Include="MediaFiles\SameFilenameException.cs" />
<Compile Include="MediaFiles\UpdateMovieFileService.cs" />
<Compile Include="MediaFiles\UpdateEpisodeFileService.cs" />
<Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
@ -873,6 +888,7 @@
<Compile Include="Parser\IsoLanguage.cs" />
<Compile Include="Parser\IsoLanguages.cs" />
<Compile Include="Parser\LanguageParser.cs" />
<Compile Include="Parser\Model\LocalMovie.cs" />
<Compile Include="Parser\Model\RemoteMovie.cs" />
<Compile Include="Profiles\Delay\DelayProfile.cs" />
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
@ -1094,6 +1110,7 @@
<Compile Include="Tv\SeriesAddedHandler.cs" />
<Compile Include="Tv\MovieRepository.cs" />
<Compile Include="Tv\MovieEditedService.cs" />
<Compile Include="Tv\MovieScannedHandler.cs" />
<Compile Include="Tv\SeriesScannedHandler.cs" />
<Compile Include="Tv\SeriesEditedService.cs" />
<Compile Include="Tv\SeriesRepository.cs" />

@ -17,6 +17,8 @@ namespace NzbDrone.Core.Organizer
public interface IBuildFileNames
{
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 BuildSeasonPath(Series series, int seasonNumber);
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
@ -137,6 +139,66 @@ namespace NzbDrone.Core.Organizer
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)
{
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
@ -146,6 +208,15 @@ namespace NzbDrone.Core.Organizer
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)
{
var path = series.Path;
@ -246,7 +317,7 @@ namespace NzbDrone.Core.Organizer
public string GetMovieFolder(Movie movie)
{
return CleanFolderName(Parser.Parser.CleanSeriesTitle(movie.Title));
return CleanFolderName(movie.Title);
}
public static string CleanTitle(string title)
@ -774,6 +845,26 @@ namespace NzbDrone.Core.Organizer
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

@ -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,7 +15,10 @@ namespace NzbDrone.Core.Parser
{
LocalEpisode GetLocalEpisode(string filename, Series series);
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);
Movie GetMovie(string title);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
RemoteMovie Map(ParsedEpisodeInfo parsedEpisodeInfo, string imdbId, SearchCriteriaBase searchCriteria = null);
@ -98,13 +101,53 @@ namespace NzbDrone.Core.Parser
};
}
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)
{
var parsedEpisodeInfo = Parser.ParseTitle(title);
if (parsedEpisodeInfo == null)
{
return _seriesService.FindByTitle(title); //Here we have a problem since it is not possible for movies to find a scene mapping, so these releases are always rejected :(
return _seriesService.FindByTitle(title);
}
var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
@ -118,6 +161,26 @@ namespace NzbDrone.Core.Parser
return series;
}
public Movie GetMovie(string title)
{
var parsedEpisodeInfo = Parser.ParseTitle(title);
if (parsedEpisodeInfo == null)
{
return _movieService.FindByTitle(title);
}
var series = _movieService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
if (series == null)
{
series = _movieService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear,
parsedEpisodeInfo.SeriesTitleInfo.Year);
}
return series;
}
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
{
var remoteEpisode = new RemoteEpisode

@ -4,6 +4,7 @@ using Marr.Data;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Core.Tv
{
@ -40,11 +41,18 @@ namespace NzbDrone.Core.Tv
public DateTime? InCinemas { get; set; }
public LazyLoaded<Profile> Profile { 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()
{
return string.Format("[{0}][{1}]", ImdbId, Title.NullSafe());
}
}
public class AddMovieOptions : MonitoringOptions
{
public bool SearchForMovie { get; set; }
}
}

@ -1,4 +1,6 @@
using System.Linq;
using System;
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
@ -11,6 +13,8 @@ namespace NzbDrone.Core.Tv
Movie FindByTitle(string cleanTitle);
Movie FindByTitle(string cleanTitle, int year);
Movie FindByImdbId(string imdbid);
List<Movie> GetMoviesByFileId(int fileId);
void SetFileId(int fileId, int movieId);
}
public class MovieRepository : BasicRepository<Movie>, IMovieRepository
@ -46,5 +50,16 @@ namespace NzbDrone.Core.Tv
{
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);
}
}
}

@ -0,0 +1,57 @@
using NLog;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Tv
{
public class MovieScannedHandler : IHandle<MovieScannedEvent>,
IHandle<MovieScanSkippedEvent>
{
private readonly IMovieService _movieService;
private readonly IManageCommandQueue _commandQueueManager;
private readonly Logger _logger;
public MovieScannedHandler( IMovieService movieService,
IManageCommandQueue commandQueueManager,
Logger logger)
{
_movieService = movieService;
_commandQueueManager = commandQueueManager;
_logger = logger;
}
private void HandleScanEvents(Movie movie)
{
if (movie.AddOptions == null)
{
//_episodeAddedService.SearchForRecentlyAdded(movie.Id);
return;
}
_logger.Info("[{0}] was recently added, performing post-add actions", movie.Title);
//_episodeMonitoredService.SetEpisodeMonitoredStatus(movie, movie.AddOptions);
if (movie.AddOptions.SearchForMovie)
{
_commandQueueManager.Push(new MoviesSearchCommand { MovieId = movie.Id});
}
movie.AddOptions = null;
_movieService.RemoveAddOptions(movie);
}
public void Handle(MovieScannedEvent message)
{
HandleScanEvents(message.Movie);
}
public void Handle(MovieScanSkippedEvent message)
{
HandleScanEvents(message.Movie);
}
}
}

@ -10,6 +10,8 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
namespace NzbDrone.Core.Tv
{
@ -22,14 +24,17 @@ namespace NzbDrone.Core.Tv
Movie FindByTitle(string title);
Movie FindByTitle(string title, int year);
Movie FindByTitleInexact(string title);
Movie GetMovieByFileId(int fileId);
void DeleteMovie(int movieId, bool deleteFiles);
List<Movie> GetAllMovies();
Movie UpdateMovie(Movie movie);
List<Movie> UpdateMovie(List<Movie> movie);
bool MoviePathExists(string folder);
void RemoveAddOptions(Movie movie);
}
public class MovieService : IMovieService
public class MovieService : IMovieService, IHandle<MovieFileAddedEvent>,
IHandle<MovieFileDeletedEvent>
{
private readonly IMovieRepository _movieRepository;
private readonly IEventAggregator _eventAggregator;
@ -190,5 +195,28 @@ namespace NzbDrone.Core.Tv
return _movieRepository.MoviePathExists(folder);
}
public void RemoveAddOptions(Movie movie)
{
_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();
}
}
}

@ -31,9 +31,9 @@ namespace NzbDrone.Host
{
if (IsAlreadyRunning())
{
_logger.Warn("Another instance of Sonarr is already running.");
_logger.Warn("Another instance of Sonarr or Radarr is already running.");
_browserService.LaunchWebUI();
throw new TerminateApplicationException("Another instance is already running");
//throw new TerminateApplicationException("Another instance is already running"); TODO: detect only radarr
}
}

@ -10,7 +10,7 @@ namespace NzbDrone.Integration.Test
public override string SeriesRootFolder => GetTempDirectory("SeriesRootFolder");
protected override string RootUrl => "http://localhost:8989/";
protected override string RootUrl => "http://localhost:7878/";
protected override string ApiKey => _runner.ApiKey;

@ -159,7 +159,7 @@ namespace NzbDrone.Integration.Test
protected void ConnectSignalR()
{
_signalRReceived = new List<SignalRMessage>();
_signalrConnection = new Connection("http://localhost:8989/signalr");
_signalrConnection = new Connection("http://localhost:7878/signalr");
_signalrConnection.Start(new LongPollingTransport()).ContinueWith(task =>
{
if (task.IsFaulted)

@ -22,10 +22,10 @@ namespace NzbDrone.Test.Common
public string AppData { get; private set; }
public string ApiKey { get; private set; }
public NzbDroneRunner(Logger logger, int port = 8989)
public NzbDroneRunner(Logger logger, int port = 7878)
{
_processProvider = new ProcessProvider(logger);
_restClient = new RestClient("http://localhost:8989/api");
_restClient = new RestClient("http://localhost:7878/api");
}
public void Start()

@ -38,7 +38,7 @@ namespace NzbDrone.SysTray
_trayMenu.MenuItems.Add("-");
_trayMenu.MenuItems.Add("Exit", OnExit);
_trayIcon.Text = string.Format("Sonarr - {0}", BuildInfo.Version);
_trayIcon.Text = string.Format("Radarr - {0}", BuildInfo.Version);
_trayIcon.Icon = Properties.Resources.NzbDroneIcon;
_trayIcon.ContextMenu = _trayMenu;

@ -120,16 +120,17 @@ module.exports = Marionette.Layout.extend({
},
_onMoviesAdded : function(options) {
if (this.isExisting && options.movies.get('path') === this.model.get('folder').path) {
if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) {
this.close();
}
else if (!this.isExisting) {
this.collection.term = '';
this.resultCollectionView.setExisting(options.movie.get('imdbId'))
/*this.collection.term = '';
this.collection.reset();
this._clearResults();
this.ui.moviesSearch.val('');
this.ui.moviesSearch.focus();
this.ui.moviesSearch.focus();*/ //TODO: Maybe add option wheter to clear search result.
}
},
@ -143,6 +144,7 @@ module.exports = Marionette.Layout.extend({
},
_clearResults : function() {
if (!this.isExisting) {
this.searchResult.show(new EmptyView());
} else {

@ -21,9 +21,21 @@ module.exports = Marionette.CollectionView.extend({
return this.showing >= this.collection.length;
},
setExisting : function(imdbid) {
var movies = this.collection.where({ imdbId : imdbid });
console.warn(movies)
//debugger;
if (movies.length > 0) {
this.children.findByModel(movies[0])._configureTemplateHelpers();
//this.children.findByModel(movies[0])._configureTemplateHelpers();
this.children.findByModel(movies[0]).render();
//this.templateHelpers.existing = existingMovies[0].toJSON();
}
},
appendHtml : function(collectionView, itemView, index) {
if (!this.isExisting || index < this.showing || index === 0) {
collectionView.$el.append(itemView.el);
}
}
});
});

@ -153,14 +153,14 @@ var view = Marionette.ItemView.extend({
},
_addWithoutSearch : function() {
this._addMovies(true);
this._addMovies(false);
},
_addAndSearch : function() {
this._addMovies(true);
},
_addMovies : function(searchForMissingEpisodes) {
_addMovies : function(searchForMovie) {
var addButton = this.ui.addButton;
var addSearchButton = this.ui.addSearchButton;
@ -171,7 +171,8 @@ var view = Marionette.ItemView.extend({
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
var options = this._getAddMoviesOptions();
options.searchForMissingEpisodes = searchForMissingEpisodes;
options.searchForMovie = searchForMovie;
console.warn(searchForMovie);
this.model.set({
profileId : profile,
@ -186,7 +187,7 @@ var view = Marionette.ItemView.extend({
console.log(this.model.save);
console.log(promise);
if (searchForMissingEpisodes) {
if (searchForMovie) {
this.ui.addSearchButton.spinForPromise(promise);
}

@ -2,7 +2,11 @@
<div class="row">
<div class="col-md-2">
<a href="{{imdbUrl}}" target="_blank">
{{#if remotePoster}}
{{remotePoster}}
{{else}}
{{poster}}
{{/if}}
</a>
</div>
<div class="col-md-10">
@ -74,7 +78,7 @@
<i class="icon-sonarr-add"></i>
</button>
<button class="btn btn-success add x-add-search" title="Add and Search for missing episodes">
<button class="btn btn-success add x-add-search" title="Add and Search for movie">
<i class="icon-sonarr-search"></i>
</button>
</div>

@ -86,7 +86,7 @@ var singleton = function() {
}
}
});
console.warn(options)
options.element.startSpin();
}
};

@ -19,6 +19,22 @@ Handlebars.registerHelper('poster', function() {
return new Handlebars.SafeString('<img class="series-poster placeholder-image" src="{0}">'.format(placeholder));
});
Handlebars.registerHelper('remotePoster', function() {
var placeholder = StatusModel.get('urlBase') + '/Content/Images/poster-dark.png';
var poster = this.remotePoster;
if (poster) {
if (!poster.match(/^https?:\/\//)) {
return new Handlebars.SafeString('<img class="series-poster x-series-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, poster, 250)));
} else {
var url = poster.replace(/^https?\:/, 'https://'); //IMDb posters need https to work, k?
return new Handlebars.SafeString('<img class="series-poster x-series-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, url)));
}
}
return new Handlebars.SafeString('<img class="series-poster placeholder-image" src="{0}">'.format(placeholder));
})
Handlebars.registerHelper('traktUrl', function() {
return 'http://trakt.tv/search/tvdb/' + this.tvdbId + '?id_type=show';
});

@ -32,7 +32,7 @@ module.exports = Marionette.Layout.extend({
edit : '.x-edit',
refresh : '.x-refresh',
rename : '.x-rename',
search : '.x-search',
searchAuto : '.x-search',
poster : '.x-movie-poster',
manualSearch : '.x-manual-search',
history : '.x-movie-history',
@ -86,8 +86,9 @@ module.exports = Marionette.Layout.extend({
name : 'refreshMovie'
}
});
CommandController.bindToCommand({
element : this.ui.search,
element : this.ui.searchAuto,
command : {
name : 'moviesSearch'
}

Loading…
Cancel
Save