diff --git a/Logo/1024.png b/Logo/1024.png index 6923c2554..0e3753b4a 100644 Binary files a/Logo/1024.png and b/Logo/1024.png differ diff --git a/Logo/128.png b/Logo/128.png index 5e143b52e..02f00f08f 100644 Binary files a/Logo/128.png and b/Logo/128.png differ diff --git a/Logo/16.png b/Logo/16.png index 0a042f4bb..61841ab86 100644 Binary files a/Logo/16.png and b/Logo/16.png differ diff --git a/Logo/256.png b/Logo/256.png index c958e1bbf..c053975a4 100644 Binary files a/Logo/256.png and b/Logo/256.png differ diff --git a/Logo/32.png b/Logo/32.png index f1fe93db5..41a6dd279 100644 Binary files a/Logo/32.png and b/Logo/32.png differ diff --git a/Logo/400.png b/Logo/400.png index dac41bfd8..f413f967e 100644 Binary files a/Logo/400.png and b/Logo/400.png differ diff --git a/Logo/48.png b/Logo/48.png index 8b9d0fc88..45cf3047c 100644 Binary files a/Logo/48.png and b/Logo/48.png differ diff --git a/Logo/512.png b/Logo/512.png index d2f56252f..16f7068a0 100644 Binary files a/Logo/512.png and b/Logo/512.png differ diff --git a/Logo/64.png b/Logo/64.png index 80edc7894..483e3d809 100644 Binary files a/Logo/64.png and b/Logo/64.png differ diff --git a/Logo/800.png b/Logo/800.png index 4a1d25228..516222468 100644 Binary files a/Logo/800.png and b/Logo/800.png differ diff --git a/Logo/Radarr.svg b/Logo/Radarr.svg index a9ce35970..575ae24da 100644 --- a/Logo/Radarr.svg +++ b/Logo/Radarr.svg @@ -1,572 +1,25 @@ - - - - SVG - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/README.md b/README.md index e307f83c1..8b25677f7 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ To connect to the UI, fire up your browser and open or < * Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114)) * Full integration with Kodi, Plex (notification, library update, metadata) +##Feature Requests +[![Feature Requests](http://feathub.com/Radarr/Radarr?format=svg)](http://feathub.com/Radarr/Radarr) ## Configuring Development Environment diff --git a/src/NzbDrone.Api/Authentication/EnableAuthInNancy.cs b/src/NzbDrone.Api/Authentication/EnableAuthInNancy.cs index f6efc16ce..7aa1bce2a 100644 --- a/src/NzbDrone.Api/Authentication/EnableAuthInNancy.cs +++ b/src/NzbDrone.Api/Authentication/EnableAuthInNancy.cs @@ -64,6 +64,8 @@ namespace NzbDrone.Api.Authentication new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase, Encoding.ASCII.GetBytes(_configService.HmacSalt))) ); + FormsAuthentication.FormsAuthenticationCookieName = "_ncfaradarr"; //For those people that both have sonarr and radarr. + FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration { RedirectUrl = _configFileProvider.UrlBase + "/login", diff --git a/src/NzbDrone.Api/Movies/MovieFileModule.cs b/src/NzbDrone.Api/Movies/MovieFileModule.cs index a45fbefad..f8108a45c 100644 --- a/src/NzbDrone.Api/Movies/MovieFileModule.cs +++ b/src/NzbDrone.Api/Movies/MovieFileModule.cs @@ -35,21 +35,21 @@ namespace NzbDrone.Api.EpisodeFiles _seriesService = seriesService; _qualityUpgradableSpecification = qualityUpgradableSpecification; _logger = logger; - /*GetResourceById = GetEpisodeFile; - GetResourceAll = GetEpisodeFiles; + GetResourceById = GetMovieFile; + /*GetResourceAll = GetEpisodeFiles; UpdateResource = SetQuality;*/ + UpdateResource = SetQuality; DeleteResource = DeleteEpisodeFile; } - /*private EpisodeFileResource GetEpisodeFile(int id) + private MovieFileResource GetMovieFile(int id) { - var episodeFile = _mediaFileService.Get(id); - var series = _seriesService.GetSeries(episodeFile.SeriesId); + var episodeFile = _mediaFileService.GetMovie(id); - return episodeFile.ToResource(series, _qualityUpgradableSpecification); + return episodeFile.ToResource(); } - private List GetEpisodeFiles() + /*private List GetEpisodeFiles() { if (!Request.Query.SeriesId.HasValue) { @@ -62,13 +62,13 @@ namespace NzbDrone.Api.EpisodeFiles return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification)); } - - private void SetQuality(EpisodeFileResource episodeFileResource) + */ + private void SetQuality(MovieFileResource episodeFileResource) { - var episodeFile = _mediaFileService.Get(episodeFileResource.Id); + var episodeFile = _mediaFileService.GetMovie(episodeFileResource.Id); episodeFile.Quality = episodeFileResource.Quality; _mediaFileService.Update(episodeFile); - }*/ + } private void DeleteEpisodeFile(int id) { diff --git a/src/NzbDrone.Api/Profiles/ProfileResource.cs b/src/NzbDrone.Api/Profiles/ProfileResource.cs index ee02bcb32..65e560b59 100644 --- a/src/NzbDrone.Api/Profiles/ProfileResource.cs +++ b/src/NzbDrone.Api/Profiles/ProfileResource.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Api.Profiles { public string Name { get; set; } public Quality Cutoff { get; set; } + public string PreferredTags { get; set; } public List Items { get; set; } public Language Language { get; set; } } @@ -33,6 +34,7 @@ namespace NzbDrone.Api.Profiles Name = model.Name, Cutoff = model.Cutoff, + PreferredTags = model.PreferredTags != null ? string.Join(",", model.PreferredTags) : "", Items = model.Items.ConvertAll(ToResource), Language = model.Language }; @@ -59,6 +61,7 @@ namespace NzbDrone.Api.Profiles Name = resource.Name, Cutoff = (Quality)resource.Cutoff.Id, + PreferredTags = resource.PreferredTags.Split(',').ToList(), Items = resource.Items.ConvertAll(ToModel), Language = resource.Language }; diff --git a/src/NzbDrone.Api/Validation/RssSyncIntervalValidator.cs b/src/NzbDrone.Api/Validation/RssSyncIntervalValidator.cs index 8a3f2d54c..fce86cd86 100644 --- a/src/NzbDrone.Api/Validation/RssSyncIntervalValidator.cs +++ b/src/NzbDrone.Api/Validation/RssSyncIntervalValidator.cs @@ -5,7 +5,7 @@ namespace NzbDrone.Api.Validation public class RssSyncIntervalValidator : PropertyValidator { public RssSyncIntervalValidator() - : base("Must be between 10 and 120 or 0 to disable") + : base("Must be between 10 and 720 or 0 to disable") { } @@ -23,7 +23,7 @@ namespace NzbDrone.Api.Validation return true; } - if (value >= 10 && value <= 120) + if (value >= 10 && value <= 720) { return true; } diff --git a/src/NzbDrone.Console/Radarr.ico b/src/NzbDrone.Console/Radarr.ico index 6f0a8b50e..7d20c6f5a 100644 Binary files a/src/NzbDrone.Console/Radarr.ico and b/src/NzbDrone.Console/Radarr.ico differ diff --git a/src/NzbDrone.Core.Test/Configuration/ConfigServiceFixture.cs b/src/NzbDrone.Core.Test/Configuration/ConfigServiceFixture.cs index 8ad51f1e7..ee834d507 100644 --- a/src/NzbDrone.Core.Test/Configuration/ConfigServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Configuration/ConfigServiceFixture.cs @@ -32,7 +32,7 @@ namespace NzbDrone.Core.Test.Configuration [Test] public void Get_value_should_return_default_when_no_value() { - Subject.RssSyncInterval.Should().Be(15); + Subject.RssSyncInterval.Should().Be(60); } [Test] @@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.Configuration public void get_value_with_out_persist_should_not_store_default_value() { var interval = Subject.RssSyncInterval; - interval.Should().Be(15); + interval.Should().Be(60); Mocker.GetMock().Verify(c => c.Insert(It.IsAny()), Times.Never()); } diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 4eae607ae..d19cddd67 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -100,7 +100,7 @@ namespace NzbDrone.Core.Configuration public int RssSyncInterval { - get { return GetValueInt("RssSyncInterval", 15); } + get { return GetValueInt("RssSyncInterval", 60); } set { SetValue("RssSyncInterval", value); } } diff --git a/src/NzbDrone.Core/Datastore/Migration/124_add_preferred_tags_to_profile.cs b/src/NzbDrone.Core/Datastore/Migration/124_add_preferred_tags_to_profile.cs new file mode 100644 index 000000000..531af0eb6 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/124_add_preferred_tags_to_profile.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using System.Data; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(124)] + public class add_preferred_tags_to_profile : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Profiles").AddColumn("PreferredTags").AsString().Nullable(); + } + + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/125_fix_imdb_unique.cs b/src/NzbDrone.Core/Datastore/Migration/125_fix_imdb_unique.cs new file mode 100644 index 000000000..407ad06c4 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/125_fix_imdb_unique.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using System.Data; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(125)] + public class fix_imdb_unique : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.WithConnection(DeleteUniqueIndex); + } + + private void DeleteUniqueIndex(IDbConnection conn, IDbTransaction tran) + { + using (IDbCommand getSeriesCmd = conn.CreateCommand()) + { + getSeriesCmd.Transaction = tran; + getSeriesCmd.CommandText = @"DROP INDEX 'IX_Movies_ImdbId'"; + + getSeriesCmd.ExecuteNonQuery(); + } + } + + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs index aba427cbf..fdb45f6d4 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs @@ -23,6 +23,7 @@ namespace NzbDrone.Core.DecisionEngine var comparers = new List { CompareQuality, + ComparePreferredWords, CompareProtocol, ComparePeersIfTorrent, CompareAgeIfUsenet, @@ -65,6 +66,26 @@ namespace NzbDrone.Core.DecisionEngine CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version)); } + private int ComparePreferredWords(DownloadDecision x, DownloadDecision y) + { + return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => + { + var title = remoteMovie.Release.Title; + remoteMovie.Movie.Profile.LazyLoad(); + var preferredWords = remoteMovie.Movie.Profile.Value.PreferredTags; + + if (preferredWords == null) + { + return 0; + } + + var num = preferredWords.AsEnumerable().Count(w => title.ToLower().Contains(w.ToLower())); + + return num; + + }); +; } + private int CompareProtocol(DownloadDecision x, DownloadDecision y) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs index ccb87c414..fe03e89a0 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs @@ -19,7 +19,21 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) { - throw new NotImplementedException(); + if (searchCriteria != null) + { + if (searchCriteria.UserInvokedSearch) + { + _logger.Debug("Skipping monitored check during search"); + return Decision.Accept(); + } + } + + if (!subject.Movie.Monitored) + { + return Decision.Reject("Movie is not monitored"); + } + + return Decision.Accept(); } public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs index 70aba1c41..88dfcb164 100644 --- a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs +++ b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs @@ -33,7 +33,7 @@ namespace NzbDrone.Core.Download public ProcessedDecisions ProcessDecisions(List decisions) { //var qualifiedReports = GetQualifiedReports(decisions); - var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); + var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisionsForMovies(decisions); var grabbed = new List(); var pending = new List(); diff --git a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs index 0e5b3a13a..f49f3772e 100644 --- a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs @@ -5,6 +5,7 @@ using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; +using System.Collections.Generic; namespace NzbDrone.Core.Download { @@ -38,7 +39,7 @@ namespace NzbDrone.Core.Download { _logger.Debug("Failed download contains a movie, searching again."); - _commandQueueManager.Push(new MoviesSearchCommand { MovieId = message.MovieId }); + _commandQueueManager.Push(new MoviesSearchCommand { MovieIds = new List { message.MovieId } }); return; } diff --git a/src/NzbDrone.Core/Extras/ExtraService.cs b/src/NzbDrone.Core/Extras/ExtraService.cs index 012e8f921..3b3a1f7c7 100644 --- a/src/NzbDrone.Core/Extras/ExtraService.cs +++ b/src/NzbDrone.Core/Extras/ExtraService.cs @@ -102,13 +102,13 @@ namespace NzbDrone.Core.Extras public void Handle(MediaCoversUpdatedEvent message) { - var series = message.Series; - var episodeFiles = GetEpisodeFiles(series.Id); + //var series = message.Series; + //var episodeFiles = GetEpisodeFiles(series.Id); - foreach (var extraFileManager in _extraFileManagers) - { - extraFileManager.CreateAfterSeriesScan(series, episodeFiles); - } + //foreach (var extraFileManager in _extraFileManagers) + //{ + // extraFileManager.CreateAfterSeriesScan(series, episodeFiles); + //} } //TODO: Implementing this will fix a lot of our warning exceptions diff --git a/src/NzbDrone.Core/IndexerSearch/MissingMoviesSearchCommand.cs b/src/NzbDrone.Core/IndexerSearch/MissingMoviesSearchCommand.cs new file mode 100644 index 000000000..7c53532c3 --- /dev/null +++ b/src/NzbDrone.Core/IndexerSearch/MissingMoviesSearchCommand.cs @@ -0,0 +1,13 @@ +using NzbDrone.Core.Messaging.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.IndexerSearch +{ + public class MissingMoviesSearchCommand : Command + { + public override bool SendUpdatesToClient => true; + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/MoviesSearchCommand.cs b/src/NzbDrone.Core/IndexerSearch/MoviesSearchCommand.cs index da0b9a8c1..214c59d6b 100644 --- a/src/NzbDrone.Core/IndexerSearch/MoviesSearchCommand.cs +++ b/src/NzbDrone.Core/IndexerSearch/MoviesSearchCommand.cs @@ -1,10 +1,11 @@ using NzbDrone.Core.Messaging.Commands; +using System.Collections.Generic; namespace NzbDrone.Core.IndexerSearch { public class MoviesSearchCommand : Command { - public int MovieId { get; set; } + public List MovieIds { get; set; } public override bool SendUpdatesToClient => true; } diff --git a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs b/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs index 656423178..5b8e4dd37 100644 --- a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs @@ -4,22 +4,23 @@ using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Download; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Tv; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.IndexerSearch { - public class MovieSearchService : IExecute + public class MovieSearchService : IExecute, IExecute { - private readonly IMovieService _seriesService; + private readonly IMovieService _movieService; private readonly ISearchForNzb _nzbSearchService; private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly Logger _logger; - public MovieSearchService(IMovieService seriesService, + public MovieSearchService(IMovieService movieService, ISearchForNzb nzbSearchService, IProcessDownloadDecisions processDownloadDecisions, Logger logger) { - _seriesService = seriesService; + _movieService = movieService; _nzbSearchService = nzbSearchService; _processDownloadDecisions = processDownloadDecisions; _logger = logger; @@ -27,20 +28,35 @@ namespace NzbDrone.Core.IndexerSearch public void Execute(MoviesSearchCommand message) { - var series = _seriesService.GetMovie(message.MovieId); - var downloadedCount = 0; - + foreach (var movieId in message.MovieIds) + { + var series = _movieService.GetMovie(movieId); + 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); + var decisions = _nzbSearchService.MovieSearch(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); } + + public void Execute(MissingMoviesSearchCommand message) + { + var movies = _movieService.MoviesWithoutFiles(new PagingSpec + { + Page = 1, + PageSize = 100000, + SortDirection = SortDirection.Ascending, + SortKey = "Id", + FilterExpression = + v => + v.Monitored == true + }).Records.ToList(); + } } } diff --git a/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDRequestGenerator.cs b/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDRequestGenerator.cs index 00289d7e4..73b36335b 100644 --- a/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDRequestGenerator.cs @@ -54,13 +54,19 @@ namespace NzbDrone.Core.Indexers.AwesomeHD private IEnumerable GetRequest(string searchParameters) { + var onlyInternal = ""; + if (Settings.Internal) + { + onlyInternal = "&internal=true"; + } + if (searchParameters != null) { - yield return new IndexerRequest(string.Format("{0}/searchapi.php?action=imdbsearch&passkey={1}&imdb={2}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.Passkey.Trim(), searchParameters), HttpAccept.Rss); + yield return new IndexerRequest($"{Settings.BaseUrl.Trim().TrimEnd('/')}/searchapi.php?action=imdbsearch&passkey={Settings.Passkey.Trim()}&imdb={searchParameters}", HttpAccept.Rss); } else { - yield return new IndexerRequest(string.Format("{0}/searchapi.php?action=latestmovies&passkey={1}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.Passkey.Trim()), HttpAccept.Rss); + yield return new IndexerRequest($"{Settings.BaseUrl.Trim().TrimEnd('/')}/searchapi.php?action=latestmovies&passkey={Settings.Passkey.Trim()}{onlyInternal}", HttpAccept.Rss); } } diff --git a/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDSettings.cs b/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDSettings.cs index 3c6f525c4..1d1b259f1 100644 --- a/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDSettings.cs +++ b/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDSettings.cs @@ -29,6 +29,9 @@ namespace NzbDrone.Core.Indexers.AwesomeHD [FieldDefinition(1, Label = "Passkey")] public string Passkey { get; set; } + [FieldDefinition(2, Type = FieldType.Checkbox, Label = "Require Internal", HelpText = "Will only include internal releases for RSS Sync.")] + public bool Internal { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs index 3abee626c..52e564e5d 100644 --- a/src/NzbDrone.Core/Jobs/TaskManager.cs +++ b/src/NzbDrone.Core/Jobs/TaskManager.cs @@ -31,12 +31,14 @@ namespace NzbDrone.Core.Jobs { private readonly IScheduledTaskRepository _scheduledTaskRepository; private readonly IConfigService _configService; + private readonly IConfigFileProvider _configFileProvider; private readonly Logger _logger; - public TaskManager(IScheduledTaskRepository scheduledTaskRepository, IConfigService configService, Logger logger) + public TaskManager(IScheduledTaskRepository scheduledTaskRepository, IConfigService configService, IConfigFileProvider configFileProvider, Logger logger) { _scheduledTaskRepository = scheduledTaskRepository; _configService = configService; + _configFileProvider = configFileProvider; _logger = logger; } @@ -60,11 +62,19 @@ namespace NzbDrone.Core.Jobs public void Handle(ApplicationStartedEvent message) { + float updateInterval = 6 * 60; + + if (_configFileProvider.Branch == "nightly") + { + updateInterval = 30; + } + var defaultTasks = new[] { new ScheduledTask{ Interval = 0.25f, TypeName = typeof(CheckForFinishedDownloadCommand).FullName}, new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName}, - new ScheduledTask{ Interval = 6*60, TypeName = typeof(ApplicationUpdateCommand).FullName}, + new ScheduledTask{ Interval = updateInterval, TypeName = typeof(ApplicationUpdateCommand).FullName}, + // new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName}, new ScheduledTask{ Interval = 12*60, TypeName = typeof(NetImportSyncCommand).FullName}, new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName}, new ScheduledTask{ Interval = 24*60, TypeName = typeof(RefreshMovieCommand).FullName}, diff --git a/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs b/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs index 6e29988a6..54fc79a0a 100644 --- a/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs +++ b/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs @@ -46,11 +46,18 @@ namespace NzbDrone.Core.MediaCover { try { + GdiPlusInterop.CheckGdiPlus(); + using (var bmp = new Bitmap(filename)) { } return true; } + catch (DllNotFoundException ex) + { + _logger.Error(ex, "Could not find libgdiplus. Cannot test if image is corrupt."); + return true; + } catch (Exception ex) { _logger.Debug(ex, "Corrupted image found at: {0}. Redownloading...", filename); diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs index e1f095791..9821e8419 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs @@ -128,7 +128,48 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport var current = localMovie.Quality; var qualityName = current.Quality.Name.ToLower(); QualityModel updated = null; - if (width > 1400) + if (width > 2000) + { + if (qualityName.Contains("bluray")) + { + updated = new QualityModel(Quality.Bluray2160p); + } + + else if (qualityName.Contains("webdl")) + { + updated = new QualityModel(Quality.WEBDL2160p); + } + + else if (qualityName.Contains("hdtv")) + { + updated = new QualityModel(Quality.HDTV2160p); + } + + else + { + var def = _qualitiesService.Get(Quality.HDTV2160p); + if (localMovie.Size > def.MinSize && def.MaxSize > localMovie.Size) + { + updated = new QualityModel(Quality.HDTV2160p); + } + def = _qualitiesService.Get(Quality.WEBDL2160p); + if (localMovie.Size > def.MinSize && def.MaxSize > localMovie.Size) + { + updated = new QualityModel(Quality.WEBDL2160p); + } + def = _qualitiesService.Get(Quality.Bluray2160p); + if (localMovie.Size > def.MinSize && def.MaxSize > localMovie.Size) + { + updated = new QualityModel(Quality.Bluray2160p); + } + if (updated == null) + { + updated = new QualityModel(Quality.Bluray2160p); + } + } + + } + else if (width > 1400) { if (qualityName.Contains("bluray")) { @@ -147,7 +188,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport else { - var def = _qualitiesService.Get(Quality.HDTV1080p); if (localMovie.Size > def.MinSize && def.MaxSize > localMovie.Size) { diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs index 98439c4c0..371d43d35 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs @@ -49,14 +49,12 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo return AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? AudioChannels - 1 + 0.1m : AudioChannels; } - return - AudioChannelPositions.Replace(" / ", "$") - .Split('$') - .First() - .Split('/') - .Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture)); - - + return + AudioChannelPositions.Replace("Object Based /", "").Replace(" / ", "$") + .Split('$') + .First() + .Split('/') + .Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture)); } } } diff --git a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index b0fde064d..fb00c5c89 100644 --- a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -60,7 +60,7 @@ namespace NzbDrone.Core.MediaFiles } else { - _logger.Warn("The existing movie file was not lazy loaded."); + //_logger.Warn("The existing movie file was not lazy loaded."); } diff --git a/src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs b/src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs index e5256a6dc..20a2738a5 100644 --- a/src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs +++ b/src/NzbDrone.Core/MetadataSource/IProvideMovieInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Tv; namespace NzbDrone.Core.MetadataSource @@ -7,6 +8,6 @@ namespace NzbDrone.Core.MetadataSource public interface IProvideMovieInfo { Movie GetMovieInfo(string ImdbId); - Movie GetMovieInfo(int TmdbId); + Movie GetMovieInfo(int TmdbId, Profile profile); } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 70d3dda89..909881773 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -14,6 +14,8 @@ using NzbDrone.Core.Tv; using Newtonsoft.Json; using System.Text.RegularExpressions; using System.Text; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Profiles; namespace NzbDrone.Core.MetadataSource.SkyHook { @@ -67,21 +69,23 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return new Tuple>(series, episodes.ToList()); } - public Movie GetMovieInfo(int TmdbId) + public Movie GetMovieInfo(int TmdbId, Profile profile = null) { + var langCode = profile != null ? IsoLanguages.Get(profile.Language).TwoLetterCode : "us"; + var request = _movieBuilder.Create() .SetSegment("route", "movie") .SetSegment("id", TmdbId.ToString()) .SetSegment("secondaryRoute", "") .AddQueryParam("append_to_response", "alternative_titles,release_dates,videos") - .AddQueryParam("country", "US") + .AddQueryParam("language", langCode.ToUpper()) + // .AddQueryParam("country", "US") .Build(); request.AllowAutoRedirect = true; request.SuppressHttpError = true; var response = _httpClient.Get(request); - var resource = response.Resource; if (resource.status_message != null) @@ -98,6 +102,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var movie = new Movie(); + foreach (var alternativeTitle in resource.alternative_titles.titles) + { + if (alternativeTitle.iso_3166_1.ToLower() == langCode) + { + movie.AlternativeTitles.Add(alternativeTitle.title); + } + else if (alternativeTitle.iso_3166_1.ToLower() == "us") + { + movie.AlternativeTitles.Add(alternativeTitle.title); + } + } + movie.TmdbId = TmdbId; movie.ImdbId = resource.imdb_id; movie.Title = resource.title; @@ -118,10 +134,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook movie.Images.Add(_configService.GetCoverForURL(resource.backdrop_path, MediaCoverTypes.Banner)); movie.Runtime = resource.runtime; - foreach(Title title in resource.alternative_titles.titles) - { - movie.AlternativeTitles.Add(title.title); - } + //foreach(Title title in resource.alternative_titles.titles) + //{ + // movie.AlternativeTitles.Add(title.title); + //} foreach(ReleaseDates releaseDates in resource.release_dates.results) { @@ -161,7 +177,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook { movie.Status = MovieStatusType.Announced; } - + if (resource.videos != null) { foreach (Video video in resource.videos.results) diff --git a/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs b/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs index c3443c33b..aa83d5c90 100644 --- a/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs +++ b/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs @@ -18,18 +18,22 @@ namespace NzbDrone.Core.Notifications.Boxcar public override void OnGrab(GrabMessage grabMessage) { - const string title = "Episode Grabbed"; + const string title = "Movie Grabbed"; _proxy.SendNotification(title, grabMessage.Message, Settings); } public override void OnDownload(DownloadMessage message) { - const string title = "Episode Downloaded"; + const string title = "Movie Downloaded"; _proxy.SendNotification(title, message.Message, Settings); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index a160963c7..8ff95a672 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Linq; @@ -24,77 +25,68 @@ namespace NzbDrone.Core.Notifications.CustomScript _logger = logger; } - public override string Link => "https://github.com/Sonarr/Sonarr/wiki/Custom-Post-Processing-Scripts"; + public override string Link => "https://github.com/Radarr/Radarr/wiki/Custom-Post-Processing-Scripts"; public override void OnGrab(GrabMessage message) { - var series = message.Series; - var remoteEpisode = message.Episode; - var releaseGroup = remoteEpisode.ParsedEpisodeInfo.ReleaseGroup; + var movie = message.Movie; + var remoteMovie = message.RemoteMovie; + var releaseGroup = remoteMovie.ParsedMovieInfo.ReleaseGroup; var environmentVariables = new StringDictionary(); - environmentVariables.Add("Sonarr_EventType", "Grab"); - environmentVariables.Add("Sonarr_Series_Id", series.Id.ToString()); - environmentVariables.Add("Sonarr_Series_Title", series.Title); - environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); - environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); - environmentVariables.Add("Sonarr_Release_EpisodeCount", remoteEpisode.Episodes.Count.ToString()); - environmentVariables.Add("Sonarr_Release_SeasonNumber", remoteEpisode.ParsedEpisodeInfo.SeasonNumber.ToString()); - environmentVariables.Add("Sonarr_Release_EpisodeNumbers", string.Join(",", remoteEpisode.Episodes.Select(e => e.EpisodeNumber))); - environmentVariables.Add("Sonarr_Release_Title", remoteEpisode.Release.Title); - environmentVariables.Add("Sonarr_Release_Indexer", remoteEpisode.Release.Indexer); - environmentVariables.Add("Sonarr_Release_Size", remoteEpisode.Release.Size.ToString()); - environmentVariables.Add("Sonarr_Release_ReleaseGroup", releaseGroup); + environmentVariables.Add("Radarr_EventType", "Grab"); + environmentVariables.Add("Radarr_Movie_Id", movie.Id.ToString()); + environmentVariables.Add("Radarr_Movie_Title", movie.Title); + environmentVariables.Add("Radarr_Movie_ImdbId", movie.ImdbId.ToString()); + environmentVariables.Add("Radarr_Release_Title", remoteMovie.Release.Title); + environmentVariables.Add("Radarr_Release_Indexer", remoteMovie.Release.Indexer); + environmentVariables.Add("Radarr_Release_Size", remoteMovie.Release.Size.ToString()); + environmentVariables.Add("Radarr_Release_ReleaseGroup", releaseGroup); ExecuteScript(environmentVariables); } public override void OnDownload(DownloadMessage message) { - var series = message.Series; - var episodeFile = message.EpisodeFile; + var movie = message.Movie; + var movieFile = message.MovieFile; var sourcePath = message.SourcePath; var environmentVariables = new StringDictionary(); - environmentVariables.Add("Sonarr_EventType", "Download"); - environmentVariables.Add("Sonarr_Series_Id", series.Id.ToString()); - environmentVariables.Add("Sonarr_Series_Title", series.Title); - environmentVariables.Add("Sonarr_Series_Path", series.Path); - environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); - environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); - environmentVariables.Add("Sonarr_EpisodeFile_Id", episodeFile.Id.ToString()); - environmentVariables.Add("Sonarr_EpisodeFile_EpisodeCount", episodeFile.Episodes.Value.Count.ToString()); - environmentVariables.Add("Sonarr_EpisodeFile_RelativePath", episodeFile.RelativePath); - environmentVariables.Add("Sonarr_EpisodeFile_Path", Path.Combine(series.Path, episodeFile.RelativePath)); - environmentVariables.Add("Sonarr_EpisodeFile_SeasonNumber", episodeFile.SeasonNumber.ToString()); - environmentVariables.Add("Sonarr_EpisodeFile_EpisodeNumbers", string.Join(",", episodeFile.Episodes.Value.Select(e => e.EpisodeNumber))); - environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDates", string.Join(",", episodeFile.Episodes.Value.Select(e => e.AirDate))); - environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDatesUtc", string.Join(",", episodeFile.Episodes.Value.Select(e => e.AirDateUtc))); - environmentVariables.Add("Sonarr_EpisodeFile_EpisodeTitles", string.Join("|", episodeFile.Episodes.Value.Select(e => e.Title))); - environmentVariables.Add("Sonarr_EpisodeFile_Quality", episodeFile.Quality.Quality.Name); - environmentVariables.Add("Sonarr_EpisodeFile_QualityVersion", episodeFile.Quality.Revision.Version.ToString()); - environmentVariables.Add("Sonarr_EpisodeFile_ReleaseGroup", episodeFile.ReleaseGroup ?? string.Empty); - environmentVariables.Add("Sonarr_EpisodeFile_SceneName", episodeFile.SceneName ?? string.Empty); - environmentVariables.Add("Sonarr_EpisodeFile_SourcePath", sourcePath); - environmentVariables.Add("Sonarr_EpisodeFile_SourceFolder", Path.GetDirectoryName(sourcePath)); + environmentVariables.Add("Radarr_EventType", "Download"); + environmentVariables.Add("Radarr_Movie_Id", movie.Id.ToString()); + environmentVariables.Add("Radarr_Movie_Title", movie.Title); + environmentVariables.Add("Radarr_Movie_Path", movie.Path); + environmentVariables.Add("Radarr_Movie_ImdbId", movie.ImdbId.ToString()); + environmentVariables.Add("Radarr_MovieFile_Id", movieFile.Id.ToString()); + environmentVariables.Add("Radarr_MovieFile_RelativePath", movieFile.RelativePath); + environmentVariables.Add("Radarr_MovieFile_Path", Path.Combine(movie.Path, movieFile.RelativePath)); + environmentVariables.Add("Radarr_MovieFile_Quality", movieFile.Quality.Quality.Name); + environmentVariables.Add("Radarr_MovieFile_QualityVersion", movieFile.Quality.Revision.Version.ToString()); + environmentVariables.Add("Radarr_MovieFile_ReleaseGroup", movieFile.ReleaseGroup ?? string.Empty); + environmentVariables.Add("Radarr_MovieFile_SceneName", movieFile.SceneName ?? string.Empty); + environmentVariables.Add("Radarr_MovieFile_SourcePath", sourcePath); + environmentVariables.Add("Radarr_MovieFile_SourceFolder", Path.GetDirectoryName(sourcePath)); ExecuteScript(environmentVariables); } - public override void OnRename(Series series) + public override void OnMovieRename(Movie movie) { var environmentVariables = new StringDictionary(); - environmentVariables.Add("Sonarr_EventType", "Rename"); - environmentVariables.Add("Sonarr_Series_Id", series.Id.ToString()); - environmentVariables.Add("Sonarr_Series_Title", series.Title); - environmentVariables.Add("Sonarr_Series_Path", series.Path); - environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); - environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); - + environmentVariables.Add("Radarr_EventType", "Rename"); + environmentVariables.Add("Radarr_Movie_Id", movie.Id.ToString()); + environmentVariables.Add("Radarr_Movie_Title", movie.Title); + environmentVariables.Add("Radarr_Movie_Path", movie.Path); + environmentVariables.Add("Radarr_Movie_TvdbId", movie.ImdbId.ToString()); ExecuteScript(environmentVariables); } + public override void OnRename(Series series) + { + } + public override string Name => "Custom Script"; public override ValidationResult Test() diff --git a/src/NzbDrone.Core/Notifications/DownloadMessage.cs b/src/NzbDrone.Core/Notifications/DownloadMessage.cs index a16ecea80..dd9343eeb 100644 --- a/src/NzbDrone.Core/Notifications/DownloadMessage.cs +++ b/src/NzbDrone.Core/Notifications/DownloadMessage.cs @@ -8,8 +8,11 @@ namespace NzbDrone.Core.Notifications { public string Message { get; set; } public Series Series { get; set; } + public Movie Movie { get; set; } public EpisodeFile EpisodeFile { get; set; } public List OldFiles { get; set; } + public MovieFile MovieFile { get; set; } + public List OldMovieFiles { get; set; } public string SourcePath { get; set; } public override string ToString() diff --git a/src/NzbDrone.Core/Notifications/Email/Email.cs b/src/NzbDrone.Core/Notifications/Email/Email.cs index 27e991332..06cb6b250 100644 --- a/src/NzbDrone.Core/Notifications/Email/Email.cs +++ b/src/NzbDrone.Core/Notifications/Email/Email.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Notifications.Email public override void OnGrab(GrabMessage grabMessage) { - const string subject = "Radarr [TV] - Grabbed"; + const string subject = "Radarr [Movie] - Grabbed"; var body = string.Format("{0} sent to queue.", grabMessage.Message); _emailService.SendEmail(Settings, subject, body); @@ -26,12 +26,16 @@ namespace NzbDrone.Core.Notifications.Email public override void OnDownload(DownloadMessage message) { - const string subject = "Radarr [TV] - Downloaded"; + const string subject = "Radarr [Movie] - Downloaded"; var body = string.Format("{0} Downloaded and sorted.", message.Message); _emailService.SendEmail(Settings, subject, body); } - + + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/GrabMessage.cs b/src/NzbDrone.Core/Notifications/GrabMessage.cs index e62dbe701..d2a2ac25d 100644 --- a/src/NzbDrone.Core/Notifications/GrabMessage.cs +++ b/src/NzbDrone.Core/Notifications/GrabMessage.cs @@ -8,6 +8,8 @@ namespace NzbDrone.Core.Notifications { public string Message { get; set; } public Series Series { get; set; } + public Movie Movie { get; set; } + public RemoteMovie RemoteMovie { get; set; } public RemoteEpisode Episode { get; set; } public QualityModel Quality { get; set; } diff --git a/src/NzbDrone.Core/Notifications/Growl/Growl.cs b/src/NzbDrone.Core/Notifications/Growl/Growl.cs index 99b43f625..85d1cb012 100644 --- a/src/NzbDrone.Core/Notifications/Growl/Growl.cs +++ b/src/NzbDrone.Core/Notifications/Growl/Growl.cs @@ -18,18 +18,22 @@ namespace NzbDrone.Core.Notifications.Growl public override void OnGrab(GrabMessage grabMessage) { - const string title = "Episode Grabbed"; + const string title = "Movie Grabbed"; _growlService.SendNotification(title, grabMessage.Message, "GRAB", Settings.Host, Settings.Port, Settings.Password); } public override void OnDownload(DownloadMessage message) { - const string title = "Episode Downloaded"; + const string title = "Movie Downloaded"; _growlService.SendNotification(title, message.Message, "DOWNLOAD", Settings.Host, Settings.Port, Settings.Password); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/INotification.cs b/src/NzbDrone.Core/Notifications/INotification.cs index 7c4e105b9..ec3ef1464 100644 --- a/src/NzbDrone.Core/Notifications/INotification.cs +++ b/src/NzbDrone.Core/Notifications/INotification.cs @@ -10,6 +10,7 @@ namespace NzbDrone.Core.Notifications void OnGrab(GrabMessage grabMessage); void OnDownload(DownloadMessage message); void OnRename(Series series); + void OnMovieRename(Movie movie); bool SupportsOnGrab { get; } bool SupportsOnDownload { get; } bool SupportsOnUpgrade { get; } diff --git a/src/NzbDrone.Core/Notifications/Join/Join.cs b/src/NzbDrone.Core/Notifications/Join/Join.cs index 4e1f81105..ae392e5df 100644 --- a/src/NzbDrone.Core/Notifications/Join/Join.cs +++ b/src/NzbDrone.Core/Notifications/Join/Join.cs @@ -18,18 +18,22 @@ namespace NzbDrone.Core.Notifications.Join public override void OnGrab(GrabMessage grabMessage) { - const string title = "Radarr - Episode Grabbed"; + const string title = "Radarr - Movie Grabbed"; _proxy.SendNotification(title, grabMessage.Message, Settings); } public override void OnDownload(DownloadMessage message) { - const string title = "Radarr - Episode Downloaded"; + const string title = "Radarr - Movie Downloaded"; _proxy.SendNotification(title, message.Message, Settings); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs b/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs index 437d10625..e50feb89a 100644 --- a/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs +++ b/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs @@ -87,7 +87,7 @@ namespace NzbDrone.Core.Notifications.Join request.AddParameter("apikey", settings.ApiKey); request.AddParameter("title", title); request.AddParameter("text", message); - request.AddParameter("icon", "https://cdn.rawgit.com/Sonarr/Sonarr/develop/Logo/256.png"); // Use the Sonarr logo. + request.AddParameter("icon", "https://cdn.rawgit.com/Radarr/Radarr/develop/Logo/256.png"); // Use the Radarr logo. var response = client.ExecuteAndValidate(request); var res = Json.Deserialize(response.Content); diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowser.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowser.cs index 7c68fc306..f293a6ecd 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowser.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowser.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Notifications.MediaBrowser public override void OnGrab(GrabMessage grabMessage) { - const string title = "Radarr - Grabbed"; + const string title = "Radarr - Movie Grabbed"; if (Settings.Notify) { @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Notifications.MediaBrowser public override void OnDownload(DownloadMessage message) { - const string title = "Radarr - Downloaded"; + const string title = "Radarr - Movie Downloaded"; if (Settings.Notify) { @@ -37,7 +37,15 @@ namespace NzbDrone.Core.Notifications.MediaBrowser if (Settings.UpdateLibrary) { - _mediaBrowserService.Update(Settings, message.Series); + _mediaBrowserService.UpdateMovies(Settings, message.Movie); + } + } + + public override void OnMovieRename(Movie movie) + { + if (Settings.UpdateLibrary) + { + _mediaBrowserService.UpdateMovies(Settings, movie); } } diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs index 251488d87..dafccb99a 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs @@ -40,6 +40,16 @@ namespace NzbDrone.Core.Notifications.MediaBrowser ProcessRequest(request, settings); } + + public void UpdateMovies(MediaBrowserSettings settings, string imdbid) + { + var path = string.Format("/Library/Movies/Updated?ImdbId={0}", imdbid); + var request = BuildRequest(path, settings); + request.Headers.Add("Content-Length", "0"); + + ProcessRequest(request, settings); + } + private string ProcessRequest(HttpRequest request, MediaBrowserSettings settings) { request.Headers.Add("X-MediaBrowser-Token", settings.ApiKey); diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs index 748d2a67f..9c76145cd 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.Notifications.MediaBrowser { void Notify(MediaBrowserSettings settings, string title, string message); void Update(MediaBrowserSettings settings, Series series); + void UpdateMovies(MediaBrowserSettings settings, Movie movie); ValidationFailure Test(MediaBrowserSettings settings); } @@ -35,6 +36,13 @@ namespace NzbDrone.Core.Notifications.MediaBrowser _proxy.Update(settings, series.TvdbId); } + + public void UpdateMovies(MediaBrowserSettings settings, Movie movie) + { + _proxy.UpdateMovies(settings, movie.ImdbId); + } + + public ValidationFailure Test(MediaBrowserSettings settings) { try diff --git a/src/NzbDrone.Core/Notifications/NotificationBase.cs b/src/NzbDrone.Core/Notifications/NotificationBase.cs index 197fadae0..c6a415cda 100644 --- a/src/NzbDrone.Core/Notifications/NotificationBase.cs +++ b/src/NzbDrone.Core/Notifications/NotificationBase.cs @@ -24,6 +24,7 @@ namespace NzbDrone.Core.Notifications public abstract void OnGrab(GrabMessage grabMessage); public abstract void OnDownload(DownloadMessage message); public abstract void OnRename(Series series); + public abstract void OnMovieRename(Movie movie); public virtual bool SupportsOnGrab => true; public virtual bool SupportsOnDownload => true; diff --git a/src/NzbDrone.Core/Notifications/NotificationService.cs b/src/NzbDrone.Core/Notifications/NotificationService.cs index 6ae201fb2..9b0c80f3d 100644 --- a/src/NzbDrone.Core/Notifications/NotificationService.cs +++ b/src/NzbDrone.Core/Notifications/NotificationService.cs @@ -15,7 +15,11 @@ namespace NzbDrone.Core.Notifications public class NotificationService : IHandle, IHandle, - IHandle + IHandle, + IHandle, + IHandle, + IHandle + { private readonly INotificationFactory _notificationFactory; private readonly Logger _logger; @@ -67,6 +71,42 @@ namespace NzbDrone.Core.Notifications qualityString); } + private string GetMessage(Movie movie, QualityModel quality) + { + var qualityString = quality.Quality.ToString(); + + if (quality.Revision.Version > 1) + { + qualityString += " Proper"; + } + + return string.Format("{0} ({1}) [{2}]", + movie.Title, + movie.Year, + qualityString); + } + + private bool ShouldHandleMovie(ProviderDefinition definition, Movie movie) + { + var notificationDefinition = (NotificationDefinition)definition; + + if (notificationDefinition.Tags.Empty()) + { + _logger.Debug("No tags set for this notification."); + return true; + } + + if (notificationDefinition.Tags.Intersect(movie.Tags).Any()) + { + _logger.Debug("Notification and series have one or more matching tags."); + return true; + } + + //TODO: this message could be more clear + _logger.Debug("{0} does not have any tags that match {1}'s tags", notificationDefinition.Name, movie.Title); + return false; + } + private bool ShouldHandleSeries(ProviderDefinition definition, Series series) { var notificationDefinition = (NotificationDefinition) definition; @@ -112,6 +152,33 @@ namespace NzbDrone.Core.Notifications } } + public void Handle(MovieGrabbedEvent message) + { + var grabMessage = new GrabMessage + { + Message = GetMessage(message.Movie.Movie, message.Movie.ParsedMovieInfo.Quality), + Series = null, + Quality = message.Movie.ParsedMovieInfo.Quality, + Episode = null, + Movie = message.Movie.Movie, + RemoteMovie = message.Movie + }; + + foreach (var notification in _notificationFactory.OnGrabEnabled()) + { + try + { + if (!ShouldHandleMovie(notification.Definition, message.Movie.Movie)) continue; + notification.OnGrab(grabMessage); + } + + catch (Exception ex) + { + _logger.Error(ex, "Unable to send OnGrab notification to: " + notification.Definition.Name); + } + } + } + public void Handle(EpisodeDownloadedEvent message) { var downloadMessage = new DownloadMessage(); @@ -141,6 +208,38 @@ namespace NzbDrone.Core.Notifications } } + public void Handle(MovieDownloadedEvent message) + { + var downloadMessage = new DownloadMessage(); + downloadMessage.Message = GetMessage(message.Movie.Movie, message.Movie.Quality); + downloadMessage.Series = null; + downloadMessage.EpisodeFile = null; + downloadMessage.MovieFile = message.MovieFile; + downloadMessage.Movie = message.Movie.Movie; + downloadMessage.OldFiles = null; + downloadMessage.OldMovieFiles = message.OldFiles; + downloadMessage.SourcePath = message.Movie.Path; + + foreach (var notification in _notificationFactory.OnDownloadEnabled()) + { + try + { + if (ShouldHandleMovie(notification.Definition, message.Movie.Movie)) + { + if (downloadMessage.OldMovieFiles.Empty() || ((NotificationDefinition)notification.Definition).OnUpgrade) + { + notification.OnDownload(downloadMessage); + } + } + } + + catch (Exception ex) + { + _logger.Warn(ex, "Unable to send OnDownload notification to: " + notification.Definition.Name); + } + } + } + public void Handle(SeriesRenamedEvent message) { foreach (var notification in _notificationFactory.OnRenameEnabled()) @@ -159,5 +258,24 @@ namespace NzbDrone.Core.Notifications } } } + + public void Handle(MovieRenamedEvent message) + { + foreach (var notification in _notificationFactory.OnRenameEnabled()) + { + try + { + if (ShouldHandleMovie(notification.Definition, message.Movie)) + { + notification.OnMovieRename(message.Movie); + } + } + + catch (Exception ex) + { + _logger.Warn(ex, "Unable to send OnRename notification to: " + notification.Definition.Name); + } + } + } } } diff --git a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs index 176612065..502fa8f5f 100644 --- a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs +++ b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs @@ -19,18 +19,22 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid public override void OnGrab(GrabMessage grabMessage) { - const string title = "Episode Grabbed"; + const string title = "Movie Grabbed"; _proxy.SendNotification(title, grabMessage.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority); } public override void OnDownload(DownloadMessage message) { - const string title = "Episode Downloaded"; + const string title = "Movie Downloaded"; _proxy.SendNotification(title, message.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs b/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs index e38e87f96..a330468a4 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs @@ -28,6 +28,10 @@ namespace NzbDrone.Core.Notifications.Plex _plexClientService.Notify(Settings, header, message.Message); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexHomeTheater.cs b/src/NzbDrone.Core/Notifications/Plex/PlexHomeTheater.cs index e96c2c4f2..817d4f50c 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexHomeTheater.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexHomeTheater.cs @@ -35,6 +35,10 @@ namespace NzbDrone.Core.Notifications.Plex Notify(Settings, header, message.Message); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs b/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs index 2f3da8822..cf52620d7 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs @@ -22,19 +22,24 @@ namespace NzbDrone.Core.Notifications.Plex public override void OnDownload(DownloadMessage message) { - UpdateIfEnabled(message.Series); + UpdateIfEnabled(message.Movie); } + public override void OnMovieRename(Movie movie) + { + UpdateIfEnabled(movie); + } + public override void OnRename(Series series) { - UpdateIfEnabled(series); + //UpdateIfEnabled(movie); } - private void UpdateIfEnabled(Series series) + private void UpdateIfEnabled(Movie movie) { if (Settings.UpdateLibrary) { - _plexServerService.UpdateLibrary(series, Settings); + _plexServerService.UpdateMovieSections(movie, Settings); } } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexServerProxy.cs b/src/NzbDrone.Core/Notifications/Plex/PlexServerProxy.cs index 0742ca049..aa9e660fd 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexServerProxy.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexServerProxy.cs @@ -17,6 +17,7 @@ namespace NzbDrone.Core.Notifications.Plex public interface IPlexServerProxy { List GetTvSections(PlexServerSettings settings); + List GetMovieSections(PlexServerSettings settings); void Update(int sectionId, PlexServerSettings settings); void UpdateSeries(int metadataId, PlexServerSettings settings); string Version(PlexServerSettings settings); @@ -66,6 +67,37 @@ namespace NzbDrone.Core.Notifications.Plex .ToList(); } + public List GetMovieSections(PlexServerSettings settings) + { + var request = GetPlexServerRequest("library/sections", Method.GET, settings); + var client = GetPlexServerClient(settings); + var response = client.Execute(request); + + _logger.Trace("Sections response: {0}", response.Content); + CheckForError(response, settings); + + if (response.Content.Contains("_children")) + { + return Json.Deserialize(response.Content) + .Sections + .Where(d => d.Type == "movie") + .Select(s => new PlexSection + { + Id = s.Id, + Language = s.Language, + Locations = s.Locations, + Type = s.Type + }) + .ToList(); + } + + return Json.Deserialize>(response.Content) + .MediaContainer + .Sections + .Where(d => d.Type == "movie") + .ToList(); + } + public void Update(int sectionId, PlexServerSettings settings) { var resource = string.Format("library/sections/{0}/refresh", sectionId); diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexServerService.cs b/src/NzbDrone.Core/Notifications/Plex/PlexServerService.cs index 67b8efe23..cb58e9040 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexServerService.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexServerService.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.Notifications.Plex public interface IPlexServerService { void UpdateLibrary(Series series, PlexServerSettings settings); + void UpdateMovieSections(Movie movie, PlexServerSettings settings); ValidationFailure Test(PlexServerSettings settings); } @@ -62,11 +63,43 @@ namespace NzbDrone.Core.Notifications.Plex } } + public void UpdateMovieSections(Movie movie, PlexServerSettings settings) + { + try + { + _logger.Debug("Sending Update Request to Plex Server"); + + var version = _versionCache.Get(settings.Host, () => GetVersion(settings), TimeSpan.FromHours(2)); + ValidateVersion(version); + + var sections = GetSections(settings); + var partialUpdates = _partialUpdateCache.Get(settings.Host, () => PartialUpdatesAllowed(settings, version), TimeSpan.FromHours(2)); + + // TODO: Investiate partial updates later, for now just update all movie sections... + + //if (partialUpdates) + //{ + // UpdatePartialSection(series, sections, settings); + //} + + //else + //{ + sections.ForEach(s => UpdateSection(s.Id, settings)); + //} + } + + catch (Exception ex) + { + _logger.Warn(ex, "Failed to Update Plex host: " + settings.Host); + throw; + } + } + private List GetSections(PlexServerSettings settings) { _logger.Debug("Getting sections from Plex host: {0}", settings.Host); - return _plexServerProxy.GetTvSections(settings).ToList(); + return _plexServerProxy.GetMovieSections(settings).ToList(); } private bool PartialUpdatesAllowed(PlexServerSettings settings, Version version) diff --git a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs index 59bba6f43..17357df0d 100644 --- a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs +++ b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs @@ -19,18 +19,22 @@ namespace NzbDrone.Core.Notifications.Prowl public override void OnGrab(GrabMessage grabMessage) { - const string title = "Episode Grabbed"; + const string title = "Movie Grabbed"; _prowlService.SendNotification(title, grabMessage.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority); } public override void OnDownload(DownloadMessage message) { - const string title = "Episode Downloaded"; + const string title = "Movie Downloaded"; _prowlService.SendNotification(title, message.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs index be2afe912..b8a2e9736 100644 --- a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs +++ b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs @@ -18,18 +18,22 @@ namespace NzbDrone.Core.Notifications.PushBullet public override void OnGrab(GrabMessage grabMessage) { - const string title = "Radarr - Episode Grabbed"; + const string title = "Radarr - Movie Grabbed"; _proxy.SendNotification(title, grabMessage.Message, Settings); } public override void OnDownload(DownloadMessage message) { - const string title = "Radarr - Episode Downloaded"; + const string title = "Radarr - Movie Downloaded"; _proxy.SendNotification(title, message.Message, Settings); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/Pushalot/Pushalot.cs b/src/NzbDrone.Core/Notifications/Pushalot/Pushalot.cs index f2969952a..953c08a8c 100644 --- a/src/NzbDrone.Core/Notifications/Pushalot/Pushalot.cs +++ b/src/NzbDrone.Core/Notifications/Pushalot/Pushalot.cs @@ -18,18 +18,22 @@ namespace NzbDrone.Core.Notifications.Pushalot public override void OnGrab(GrabMessage grabMessage) { - const string title = "Episode Grabbed"; + const string title = "Movie Grabbed"; _proxy.SendNotification(title, grabMessage.Message, Settings); } public override void OnDownload(DownloadMessage message) { - const string title = "Episode Downloaded"; + const string title = "Movie Downloaded"; _proxy.SendNotification(title, message.Message, Settings); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs index ee8f61053..d590099f5 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs @@ -18,18 +18,22 @@ namespace NzbDrone.Core.Notifications.Pushover public override void OnGrab(GrabMessage grabMessage) { - const string title = "Episode Grabbed"; + const string title = "Movie Grabbed"; _proxy.SendNotification(title, grabMessage.Message, Settings); } public override void OnDownload(DownloadMessage message) { - const string title = "Episode Downloaded"; + const string title = "Movie Downloaded"; _proxy.SendNotification(title, message.Message, Settings); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/Slack/Slack.cs b/src/NzbDrone.Core/Notifications/Slack/Slack.cs index 5e581a25f..13e69f5a0 100644 --- a/src/NzbDrone.Core/Notifications/Slack/Slack.cs +++ b/src/NzbDrone.Core/Notifications/Slack/Slack.cs @@ -37,7 +37,7 @@ namespace NzbDrone.Core.Notifications.Slack new Attachment { Fallback = message.Message, - Title = message.Series.Title, + Title = message.Movie.Title, Text = message.Message, Color = "warning" } @@ -59,7 +59,7 @@ namespace NzbDrone.Core.Notifications.Slack new Attachment { Fallback = message.Message, - Title = message.Series.Title, + Title = message.Movie.Title, Text = message.Message, Color = "good" } @@ -69,6 +69,25 @@ namespace NzbDrone.Core.Notifications.Slack NotifySlack(payload); } + public override void OnMovieRename(Movie movie) + { + var payload = new SlackPayload + { + IconEmoji = Settings.Icon, + Username = Settings.Username, + Text = "Renamed", + Attachments = new List + { + new Attachment + { + Title = movie.Title, + } + } + }; + + NotifySlack(payload); + } + public override void OnRename(Series series) { var payload = new SlackPayload diff --git a/src/NzbDrone.Core/Notifications/Synology/SynologyIndexer.cs b/src/NzbDrone.Core/Notifications/Synology/SynologyIndexer.cs index 4994ce00a..a05eab45c 100644 --- a/src/NzbDrone.Core/Notifications/Synology/SynologyIndexer.cs +++ b/src/NzbDrone.Core/Notifications/Synology/SynologyIndexer.cs @@ -27,21 +27,29 @@ namespace NzbDrone.Core.Notifications.Synology { if (Settings.UpdateLibrary) { - foreach (var oldFile in message.OldFiles) + foreach (var oldFile in message.OldMovieFiles) { - var fullPath = Path.Combine(message.Series.Path, oldFile.RelativePath); + var fullPath = Path.Combine(message.Movie.Path, oldFile.RelativePath); _indexerProxy.DeleteFile(fullPath); } { - var fullPath = Path.Combine(message.Series.Path, message.EpisodeFile.RelativePath); + var fullPath = Path.Combine(message.Movie.Path, message.MovieFile.RelativePath); _indexerProxy.AddFile(fullPath); } } } + public override void OnMovieRename(Movie movie) + { + if (Settings.UpdateLibrary) + { + _indexerProxy.UpdateFolder(movie.Path); + } + } + public override void OnRename(Series series) { if (Settings.UpdateLibrary) diff --git a/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs index 240008c5e..477872409 100644 --- a/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs +++ b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs @@ -18,18 +18,22 @@ namespace NzbDrone.Core.Notifications.Telegram public override void OnGrab(GrabMessage grabMessage) { - const string title = "Episode Grabbed"; + const string title = "Movie Grabbed"; _proxy.SendNotification(title, grabMessage.Message, Settings); } public override void OnDownload(DownloadMessage message) { - const string title = "Episode Downloaded"; + const string title = "Movie Downloaded"; _proxy.SendNotification(title, message.Message, Settings); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs b/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs index b19c7725f..e789654dc 100644 --- a/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs +++ b/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs @@ -29,6 +29,10 @@ namespace NzbDrone.Core.Notifications.Twitter _twitterService.SendNotification($"Imported: {message.Message}", Settings); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { } diff --git a/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs b/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs index 4bfcb867c..74ae13f89 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs @@ -27,6 +27,10 @@ namespace NzbDrone.Core.Notifications.Webhook _service.OnDownload(message.Series, message.EpisodeFile, Settings); } + public override void OnMovieRename(Movie movie) + { + } + public override void OnRename(Series series) { _service.OnRename(series, Settings); diff --git a/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs b/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs index 76f2bc91f..528728cdf 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs @@ -51,6 +51,24 @@ namespace NzbDrone.Core.Notifications.Xbmc UpdateLibrary(settings, series); } + public void UpdateMovie(XbmcSettings settings, Movie movie) + { + if (!settings.AlwaysUpdate) + { + _logger.Debug("Determining if there are any active players on XBMC host: {0}", settings.Address); + var activePlayers = GetActivePlayers(settings); + + if (activePlayers.Any(a => a.Type.Equals("video"))) + { + _logger.Debug("Video is currently playing, skipping library update"); + return; + } + } + + UpdateMovieLibrary(settings, movie); + } + + public void Clean(XbmcSettings settings) { const string cleanVideoLibrary = "CleanLibrary(video)"; @@ -167,6 +185,37 @@ namespace NzbDrone.Core.Notifications.Xbmc } } + private void UpdateMovieLibrary(XbmcSettings settings, Movie movie) + { + try + { + //_logger.Debug("Sending Update DB Request to XBMC Host: {0}", settings.Address); + //var xbmcSeriesPath = GetSeriesPath(settings, series); + + ////If the path is found update it, else update the whole library + //if (!string.IsNullOrEmpty(xbmcSeriesPath)) + //{ + // _logger.Debug("Updating series [{0}] on XBMC host: {1}", series, settings.Address); + // var command = BuildExecBuiltInCommand(string.Format("UpdateLibrary(video,{0})", xbmcSeriesPath)); + // SendCommand(settings, command); + //} + + //else + //{ + //Update the entire library + _logger.Debug("Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library", movie, settings.Address); + var command = BuildExecBuiltInCommand("UpdateLibrary(video)"); + SendCommand(settings, command); + //} + } + + catch (Exception ex) + { + _logger.Debug(ex, ex.Message); + } + } + + private string SendCommand(XbmcSettings settings, string command) { var url = string.Format("http://{0}/xbmcCmds/xbmcHttp?command={1}", settings.Address, command); diff --git a/src/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs b/src/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs index bf250edc3..94bf80862 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs @@ -7,6 +7,7 @@ namespace NzbDrone.Core.Notifications.Xbmc { void Notify(XbmcSettings settings, string title, string message); void Update(XbmcSettings settings, Series series); + void UpdateMovie(XbmcSettings settings, Movie movie); void Clean(XbmcSettings settings); bool CanHandle(XbmcVersion version); } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs b/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs index 1a0674908..378bb0774 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs @@ -44,7 +44,25 @@ namespace NzbDrone.Core.Notifications.Xbmc UpdateLibrary(settings, series); } - + + public void UpdateMovie(XbmcSettings settings, Movie movie) + { + if (!settings.AlwaysUpdate) + { + _logger.Debug("Determining if there are any active players on XBMC host: {0}", settings.Address); + var activePlayers = _proxy.GetActivePlayers(settings); + + if (activePlayers.Any(a => a.Type.Equals("video"))) + { + _logger.Debug("Video is currently playing, skipping library update"); + return; + } + } + + UpdateMovieLibrary(settings, movie); + } + + public void Clean(XbmcSettings settings) { _proxy.CleanLibrary(settings); @@ -108,5 +126,23 @@ namespace NzbDrone.Core.Notifications.Xbmc _logger.Debug(ex, ex.Message); } } + + private void UpdateMovieLibrary(XbmcSettings settings, Movie movie) + { + try + { + var response = _proxy.UpdateLibrary(settings, null); + + if (!response.Equals("OK", StringComparison.InvariantCultureIgnoreCase)) + { + _logger.Debug("Failed to update library for: {0}", settings.Address); + } + } + + catch (Exception ex) + { + _logger.Debug(ex, ex.Message); + } + } } } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs index 08fdbfaa4..890e0516d 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs @@ -33,7 +33,12 @@ namespace NzbDrone.Core.Notifications.Xbmc const string header = "Radarr - Downloaded"; Notify(Settings, header, message.Message); - UpdateAndClean(message.Series, message.OldFiles.Any()); + UpdateAndCleanMovie(message.Movie, message.OldMovieFiles.Any()); + } + + public override void OnMovieRename(Movie movie) + { + UpdateAndCleanMovie(movie); } public override void OnRename(Series series) @@ -88,5 +93,26 @@ namespace NzbDrone.Core.Notifications.Xbmc _logger.Debug(ex, logMessage); } } + + private void UpdateAndCleanMovie(Movie movie, bool clean = true) + { + try + { + if (Settings.UpdateLibrary) + { + _xbmcService.UpdateMovie(Settings, movie); + } + + if (clean && Settings.CleanLibrary) + { + _xbmcService.Clean(Settings); + } + } + catch (SocketException ex) + { + var logMessage = string.Format("Unable to connect to XBMC Host: {0}:{1}", Settings.Host, Settings.Port); + _logger.Debug(ex, logMessage); + } + } } } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs index 84127f69f..85dbc99c5 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.Notifications.Xbmc { void Notify(XbmcSettings settings, string title, string message); void Update(XbmcSettings settings, Series series); + void UpdateMovie(XbmcSettings settings, Movie movie); void Clean(XbmcSettings settings); ValidationFailure Test(XbmcSettings settings, string message); } @@ -51,6 +52,12 @@ namespace NzbDrone.Core.Notifications.Xbmc provider.Update(settings, series); } + public void UpdateMovie(XbmcSettings settings, Movie movie) + { + var provider = GetApiProvider(settings); + provider.UpdateMovie(settings, movie); + } + public void Clean(XbmcSettings settings) { var provider = GetApiProvider(settings); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 1b6ef7f6f..90fc824d6 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -218,6 +218,8 @@ + + @@ -618,6 +620,7 @@ + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index df327bee5..f6d5b1ecd 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -1,3 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using NLog; +using NzbDrone.Common.Cache; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Organizer +{ + public interface IBuildFileNames + { + string BuildFileName(List 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); + string GetSeriesFolder(Series series, NamingConfig namingConfig = null); + string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null); + string GetMovieFolder(Movie movie, NamingConfig namingConfig = null); + } + + public class FileNameBuilder : IBuildFileNames + { + private readonly INamingConfigService _namingConfigService; + private readonly IQualityDefinitionService _qualityDefinitionService; + private readonly ICached _episodeFormatCache; + private readonly ICached _absoluteEpisodeFormatCache; + private readonly Logger _logger; + + private static readonly Regex TitleRegex = new Regex(@"\{(?[- ._\[(]*)(?(?:[a-z0-9]+)(?:(?[- ._]+)(?:[a-z0-9]+))?)(?::(?[a-z0-9]+))?(?[- ._)\]]*)\}", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex EpisodeRegex = new Regex(@"(?\{episode(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex SeasonRegex = new Regex(@"(?\{season(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex AbsoluteEpisodeRegex = new Regex(@"(?\{absolute(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?s?{season(?:\:0+)?}(?[- ._]?[ex])(?{episode(?:\:0+)?}))(?[- ._]+?(?={))?", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?{absolute(?:\:0+)?})(?[- ._]+?(?={))?", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?[- ._])(Clean)?Title\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex MovieTitleRegex = new Regex(@"(?\{((?:(Movie|Original))(?[- ._])(Clean)?Title(The)?)\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); + private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled); + + private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + //TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc) + private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' }; + + public FileNameBuilder(INamingConfigService namingConfigService, + IQualityDefinitionService qualityDefinitionService, + ICacheManager cacheManager, + Logger logger) + { + _namingConfigService = namingConfigService; + _qualityDefinitionService = qualityDefinitionService; + //_movieFormatCache = cacheManager.GetCache(GetType(), "movieFormat"); + _episodeFormatCache = cacheManager.GetCache(GetType(), "episodeFormat"); + _absoluteEpisodeFormatCache = cacheManager.GetCache(GetType(), "absoluteEpisodeFormat"); + _logger = logger; + } + + public string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + if (!namingConfig.RenameEpisodes) + { + return GetOriginalTitle(episodeFile); + } + + 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>(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); + + return fileName; + } + + public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + if (!namingConfig.RenameEpisodes) + { + return GetOriginalTitle(movieFile); + } + + //TODO: Update namingConfig for Movies! + var pattern = namingConfig.StandardMovieFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddMovieTokens(tokenHandlers, movie); + AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year + AddImdbIdTokens(tokenHandlers, movie.ImdbId); + AddQualityTokens(tokenHandlers, movie, movieFile); + AddMediaInfoTokens(tokenHandlers, movieFile); + AddMovieFileTokens(tokenHandlers, movieFile); + + var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); + fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); + fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); + + return fileName; + } + + public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension) + { + Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); + + var path = BuildSeasonPath(series, seasonNumber); + + 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; + + if (series.SeasonFolder) + { + if (seasonNumber == 0) + { + path = Path.Combine(path, "Specials"); + } + else + { + var seasonFolder = GetSeasonFolder(series, seasonNumber); + + seasonFolder = CleanFileName(seasonFolder); + + path = Path.Combine(path, seasonFolder); + } + } + + return path; + } + + public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) + { + return new BasicNamingConfig(); //For now let's be lazy + + var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault(); + + if (episodeFormat == null) + { + return new BasicNamingConfig(); + } + + var basicNamingConfig = new BasicNamingConfig + { + Separator = episodeFormat.Separator, + NumberStyle = episodeFormat.SeasonEpisodePattern + }; + + var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat); + + foreach (Match match in titleTokens) + { + var separator = match.Groups["separator"].Value; + var token = match.Groups["token"].Value; + + if (!separator.Equals(" ")) + { + basicNamingConfig.ReplaceSpaces = true; + } + + if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase)) + { + basicNamingConfig.IncludeSeriesTitle = true; + } + + if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase)) + { + basicNamingConfig.IncludeEpisodeTitle = true; + } + + if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase)) + { + basicNamingConfig.IncludeQuality = true; + } + } + + return basicNamingConfig; + } + + public string GetSeriesFolder(Series series, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddSeriesTokens(tokenHandlers, series); + + return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig)); + } + + public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddSeriesTokens(tokenHandlers, series); + AddSeasonTokens(tokenHandlers, seasonNumber); + + return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); + } + + public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null) + { + if(namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddMovieTokens(tokenHandlers, movie); + AddReleaseDateTokens(tokenHandlers, movie.Year); + AddImdbIdTokens(tokenHandlers, movie.ImdbId); + + return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig)); + } + + public static string CleanTitle(string title) + { + title = title.Replace("&", "and"); + title = ScenifyReplaceChars.Replace(title, " "); + title = ScenifyRemoveChars.Replace(title, string.Empty); + + return title; + } + + public static string TitleThe(string title) + { + string[] prefixes = { "The ", "An ", "A " }; + foreach (string prefix in prefixes) + { + int prefix_length = prefix.Length; + if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower()) + { + title = title.Substring(prefix_length) + ", " + prefix.Trim(); + break; + } + } + + return title.Trim(); + } + using System; using System.Collections.Generic; using System.Globalization; @@ -41,6 +375,9 @@ namespace NzbDrone.Core.Organizer private static readonly Regex EpisodeRegex = new Regex(@"(?\{episode(?:\:0+)?})", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex TagsRegex = new Regex(@"(?\{tags(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex SeasonRegex = new Regex(@"(?\{season(?:\:0+)?})", RegexOptions.Compiled | RegexOptions.IgnoreCase); @@ -165,6 +502,7 @@ namespace NzbDrone.Core.Organizer AddQualityTokens(tokenHandlers, movie, movieFile); AddMediaInfoTokens(tokenHandlers, movieFile); AddMovieFileTokens(tokenHandlers, movieFile); + AddTagsTokens(tokenHandlers, movieFile); var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); @@ -316,20 +654,6 @@ namespace NzbDrone.Core.Organizer return title; } - public static string TitleThe(string title) - { - string[] prefixes = { "The ", "An ", "A " }; - foreach (string prefix in prefixes) - { - int prefix_length = prefix.Length; - if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower()) - { - title = title.Substring(prefix_length) + ", " + prefix.Trim(); - break; - } - } - - return title.Trim(); } public static string CleanFileName(string name, bool replace = true) @@ -491,6 +815,14 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Movie Title The}"] = m => TitleThe(movie.Title); } + private void AddTagsTokens(Dictionary> tokenHandlers, MovieFile movieFile) + { + if (movieFile.Edition.IsNotNullOrWhiteSpace()) + { + tokenHandlers["{Edition Tags}"] = m => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(movieFile.Edition.ToLower()); + } + } + private void AddReleaseDateTokens(Dictionary> tokenHandlers, int releaseYear) { tokenHandlers["{Release Year}"] = m => string.Format("{0}", releaseYear.ToString()); //Do I need m.CustomFormat? @@ -1064,4 +1396,4 @@ namespace NzbDrone.Core.Organizer Range = 4, PrefixedRange = 5 } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index 83360063c..b06c4964f 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -125,7 +125,8 @@ namespace NzbDrone.Core.Organizer RelativePath = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE.mkv", SceneName = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE", ReleaseGroup = "RlsGrp", - MediaInfo = mediaInfo + MediaInfo = mediaInfo, + Edition = "Ultimate extended edition", }; _singleEpisodeFile = new EpisodeFile diff --git a/src/NzbDrone.Core/Parser/IsoLanguage.cs b/src/NzbDrone.Core/Parser/IsoLanguage.cs index 1bd198e50..7a8e3251c 100644 --- a/src/NzbDrone.Core/Parser/IsoLanguage.cs +++ b/src/NzbDrone.Core/Parser/IsoLanguage.cs @@ -12,5 +12,6 @@ ThreeLetterCode = threeLetterCode; Language = language; } + } } diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index c5472a7e0..6b2e6e705 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -18,20 +18,20 @@ namespace NzbDrone.Core.Parser private static readonly Regex[] ReportMovieTitleRegex = new[] { //Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.Special.Edition.2011 - new Regex(@"^(?.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<edition>(\.?((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final|Extended|Rogue|Special|Despecialized).(Cut|Edition|Version)|Extended|Uncensored|Remastered|Unrated|Uncut|IMAX)))\.(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", + new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<edition>(\.?((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final|Extended|Rogue|Special|Despecialized).(Cut|Edition|Version)|Extended|Uncensored|Remastered|Unrated|Uncut|IMAX)))\.(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled), //Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.2011.Special.Edition //TODO: Seems to slow down parsing heavily! - new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)(?<edition>((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final|Extended|Rogue|Special|Despecialized).(Cut|Edition|Version)|Extended|Uncensored|Remastered|Unrated|Uncut|IMAX))", + new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)(?<edition>((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final|Extended|Rogue|Special|Despecialized).(Cut|Edition|Version)|Extended|Uncensored|Remastered|Unrated|Uncut|IMAX))", RegexOptions.IgnoreCase | RegexOptions.Compiled), //Normal movie format, e.g: Mission.Impossible.3.2011 - new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", + new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled), //PassThePopcorn Torrent names: Star.Wars[PassThePopcorn] new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled), //That did not work? Maybe some tool uses [] for years. Who would do that? - new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?!\\)", + new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled), }; @@ -696,6 +696,12 @@ namespace NzbDrone.Core.Parser private static ParsedMovieInfo ParseMovieMatchCollection(MatchCollection matchCollection) { + if (!matchCollection[0].Groups["title"].Success) + { + return null; + } + + var seriesName = matchCollection[0].Groups["title"].Value.Replace('.', ' ').Replace('_', ' '); seriesName = RequestInfoRegex.Replace(seriesName, "").Trim(' '); diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 9df110a88..2de0d0648 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -396,9 +396,15 @@ namespace NzbDrone.Core.Parser if (searchCriteria == null) { - - movie = _movieService.FindByTitle(parsedEpisodeInfo.MovieTitle); //Todo: same as above! - + if (parsedEpisodeInfo.Year > 1900) + { + movie = _movieService.FindByTitle(parsedEpisodeInfo.MovieTitle, parsedEpisodeInfo.Year); + //Todo: same as above! + } + else + { + movie = _movieService.FindByTitle(parsedEpisodeInfo.MovieTitle); //Todo: same as above! + } return movie; } diff --git a/src/NzbDrone.Core/Profiles/Profile.cs b/src/NzbDrone.Core/Profiles/Profile.cs index 6215e9474..d25104fb6 100644 --- a/src/NzbDrone.Core/Profiles/Profile.cs +++ b/src/NzbDrone.Core/Profiles/Profile.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.Profiles public string Name { get; set; } public Quality Cutoff { get; set; } public List<ProfileQualityItem> Items { get; set; } + public List<string> PreferredTags { get; set; } public Language Language { get; set; } public Quality LastAllowedQuality() diff --git a/src/NzbDrone.Core/Tv/MovieScannedHandler.cs b/src/NzbDrone.Core/Tv/MovieScannedHandler.cs index 151ef0559..2eba01239 100644 --- a/src/NzbDrone.Core/Tv/MovieScannedHandler.cs +++ b/src/NzbDrone.Core/Tv/MovieScannedHandler.cs @@ -3,6 +3,7 @@ using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; +using System.Collections.Generic; namespace NzbDrone.Core.Tv { @@ -37,7 +38,7 @@ namespace NzbDrone.Core.Tv if (movie.AddOptions.SearchForMovie) { - _commandQueueManager.Push(new MoviesSearchCommand { MovieId = movie.Id}); + _commandQueueManager.Push(new MoviesSearchCommand { MovieIds = new List<int> { movie.Id } }); } movie.AddOptions = null; diff --git a/src/NzbDrone.Core/Tv/RefreshMovieService.cs b/src/NzbDrone.Core/Tv/RefreshMovieService.cs index a086cf3ec..495f00e05 100644 --- a/src/NzbDrone.Core/Tv/RefreshMovieService.cs +++ b/src/NzbDrone.Core/Tv/RefreshMovieService.cs @@ -51,7 +51,7 @@ namespace NzbDrone.Core.Tv try { - movieInfo = _movieInfo.GetMovieInfo(movie.TmdbId); + movieInfo = _movieInfo.GetMovieInfo(movie.TmdbId, movie.Profile); } catch (MovieNotFoundException) { diff --git a/src/NzbDrone.Host/Radarr.ico b/src/NzbDrone.Host/Radarr.ico index 6f0a8b50e..7d20c6f5a 100644 Binary files a/src/NzbDrone.Host/Radarr.ico and b/src/NzbDrone.Host/Radarr.ico differ diff --git a/src/NzbDrone/Radarr.ico b/src/NzbDrone/Radarr.ico index 6f0a8b50e..7d20c6f5a 100644 Binary files a/src/NzbDrone/Radarr.ico and b/src/NzbDrone/Radarr.ico differ diff --git a/src/NzbDrone/Resources/Radarr.ico b/src/NzbDrone/Resources/Radarr.ico index 4903af4b1..7d20c6f5a 100644 Binary files a/src/NzbDrone/Resources/Radarr.ico and b/src/NzbDrone/Resources/Radarr.ico differ diff --git a/src/Radarr.ico b/src/Radarr.ico index 4903af4b1..7d20c6f5a 100644 Binary files a/src/Radarr.ico and b/src/Radarr.ico differ diff --git a/src/UI/Activity/Queue/QueueLayout.js b/src/UI/Activity/Queue/QueueLayout.js index 4416cb07b..0fc561165 100644 --- a/src/UI/Activity/Queue/QueueLayout.js +++ b/src/UI/Activity/Queue/QueueLayout.js @@ -1,7 +1,7 @@ var Marionette = require('marionette'); var Backgrid = require('backgrid'); var QueueCollection = require('./QueueCollection'); -var SeriesTitleCell = require('../../Cells/MovieTitleCell'); +var MovieTitleCell = require('../../Cells/MovieTitleCell'); var EpisodeNumberCell = require('../../Cells/EpisodeNumberCell'); var EpisodeTitleCell = require('../../Cells/EpisodeTitleCell'); var QualityCell = require('../../Cells/QualityCell'); @@ -30,7 +30,7 @@ module.exports = Marionette.Layout.extend({ { name : 'movie', label : 'Movie', - cell : SeriesTitleCell + cell : MovieTitleCell }, /*{ name : 'episode', diff --git a/src/UI/Cells/MovieTitleHistoryCell.js b/src/UI/Cells/MovieTitleHistoryCell.js index 174d5d361..3a3d6d927 100644 --- a/src/UI/Cells/MovieTitleHistoryCell.js +++ b/src/UI/Cells/MovieTitleHistoryCell.js @@ -6,9 +6,7 @@ module.exports = TemplatedCell.extend({ render : function() { - this.$el.html(this.model.get("movie").get("title")); //Hack, but somehow handlebar helper does not work. - debugger; + this.$el.html('<a href="movies/' + this.model.get("movie").get("titleSlug") +'">' + this.model.get("movie").get("title") + '</a>'); //Hack, but somehow handlebar helper does not work. return this; - } }); diff --git a/src/UI/Config.js b/src/UI/Config.js index 2115d076a..9ea57003b 100644 --- a/src/UI/Config.js +++ b/src/UI/Config.js @@ -2,23 +2,26 @@ var $ = require('jquery'); var vent = require('./vent'); module.exports = { + ConfigNamespace : 'Radarr.', + Events : { ConfigUpdatedEvent : 'ConfigUpdatedEvent' }, Keys : { - DefaultProfileId : 'DefaultProfileId', - DefaultRootFolderId : 'DefaultRootFolderId', - UseSeasonFolder : 'UseSeasonFolder', - DefaultSeriesType : 'DefaultSeriesType', - MonitorEpisodes : 'MonitorEpisodes', - AdvancedSettings : 'advancedSettings' + DefaultProfileId : 'RadarrDefaultProfileId', + DefaultRootFolderId : 'RadarrDefaultRootFolderId', + UseSeasonFolder : 'RadarrUseSeasonFolder', + DefaultSeriesType : 'RadarrDefaultSeriesType', + MonitorEpisodes : 'RadarrMonitorEpisodes', + AdvancedSettings : 'RadarradvancedSettings' }, getValueJson : function (key, defaultValue) { + var storeKey = this.ConfigNamespace + key; defaultValue = defaultValue || {}; - var storeValue = window.localStorage.getItem(key); + var storeValue = window.localStorage.getItem(storeKey); if (!storeValue) { return defaultValue; @@ -34,7 +37,8 @@ module.exports = { }, getValue : function(key, defaultValue) { - var storeValue = window.localStorage.getItem(key); + var storeKey = this.ConfigNamespace + key; + var storeValue = window.localStorage.getItem(storeKey); if (!storeValue) { return defaultValue; @@ -48,22 +52,22 @@ module.exports = { }, setValue : function(key, value) { - - console.log('Config: [{0}] => [{1}]'.format(key, value)); + var storeKey = this.ConfigNamespace + key; + console.log('Config: [{0}] => [{1}]'.format(storeKey, value)); if (this.getValue(key) === value.toString()) { return; } try { - window.localStorage.setItem(key, value); + window.localStorage.setItem(storeKey, value); vent.trigger(this.Events.ConfigUpdatedEvent, { key : key, value : value }); } catch (error) { - console.error('Unable to save config: [{0}] => [{1}]'.format(key, value)); + console.error('Unable to save config: [{0}] => [{1}]'.format(storeKey, value)); } } }; diff --git a/src/UI/Content/Images/favicon-debug.ico b/src/UI/Content/Images/favicon-debug.ico index 90bf72090..80e6bd51b 100644 Binary files a/src/UI/Content/Images/favicon-debug.ico and b/src/UI/Content/Images/favicon-debug.ico differ diff --git a/src/UI/Content/Images/favicon.ico b/src/UI/Content/Images/favicon.ico index 90bf72090..80e6bd51b 100644 Binary files a/src/UI/Content/Images/favicon.ico and b/src/UI/Content/Images/favicon.ico differ diff --git a/src/UI/Content/Images/favicon/android-chrome-144x144.png b/src/UI/Content/Images/favicon/android-chrome-144x144.png new file mode 100644 index 000000000..a30ab0209 Binary files /dev/null and b/src/UI/Content/Images/favicon/android-chrome-144x144.png differ diff --git a/src/UI/Content/Images/favicon/android-chrome-192x192.png b/src/UI/Content/Images/favicon/android-chrome-192x192.png new file mode 100644 index 000000000..8f7d9f655 Binary files /dev/null and b/src/UI/Content/Images/favicon/android-chrome-192x192.png differ diff --git a/src/UI/Content/Images/favicon/android-chrome-256x256.png b/src/UI/Content/Images/favicon/android-chrome-256x256.png new file mode 100644 index 000000000..52977292b Binary files /dev/null and b/src/UI/Content/Images/favicon/android-chrome-256x256.png differ diff --git a/src/UI/Content/Images/favicon/android-chrome-36x36.png b/src/UI/Content/Images/favicon/android-chrome-36x36.png new file mode 100644 index 000000000..d8da18abf Binary files /dev/null and b/src/UI/Content/Images/favicon/android-chrome-36x36.png differ diff --git a/src/UI/Content/Images/favicon/android-chrome-384x384.png b/src/UI/Content/Images/favicon/android-chrome-384x384.png new file mode 100644 index 000000000..358b6d510 Binary files /dev/null and b/src/UI/Content/Images/favicon/android-chrome-384x384.png differ diff --git a/src/UI/Content/Images/favicon/android-chrome-48x48.png b/src/UI/Content/Images/favicon/android-chrome-48x48.png new file mode 100644 index 000000000..e556972c9 Binary files /dev/null and b/src/UI/Content/Images/favicon/android-chrome-48x48.png differ diff --git a/src/UI/Content/Images/favicon/android-chrome-512x512.png b/src/UI/Content/Images/favicon/android-chrome-512x512.png new file mode 100644 index 000000000..16658df45 Binary files /dev/null and b/src/UI/Content/Images/favicon/android-chrome-512x512.png differ diff --git a/src/UI/Content/Images/favicon/android-chrome-72x72.png b/src/UI/Content/Images/favicon/android-chrome-72x72.png new file mode 100644 index 000000000..35534d1bb Binary files /dev/null and b/src/UI/Content/Images/favicon/android-chrome-72x72.png differ diff --git a/src/UI/Content/Images/favicon/android-chrome-96x96.png b/src/UI/Content/Images/favicon/android-chrome-96x96.png new file mode 100644 index 000000000..ae3d77034 Binary files /dev/null and b/src/UI/Content/Images/favicon/android-chrome-96x96.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-114x114-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-114x114-precomposed.png new file mode 100644 index 000000000..8861297ed Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-114x114-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-114x114.png b/src/UI/Content/Images/favicon/apple-touch-icon-114x114.png new file mode 100644 index 000000000..cb5e2a3fd Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-114x114.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-120x120-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-120x120-precomposed.png new file mode 100644 index 000000000..a870359d2 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-120x120-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-120x120.png b/src/UI/Content/Images/favicon/apple-touch-icon-120x120.png new file mode 100644 index 000000000..3d365dc5e Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-120x120.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-144x144-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-144x144-precomposed.png new file mode 100644 index 000000000..cf4be66e8 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-144x144-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-144x144.png b/src/UI/Content/Images/favicon/apple-touch-icon-144x144.png new file mode 100644 index 000000000..505314e93 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-144x144.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-152x152-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-152x152-precomposed.png new file mode 100644 index 000000000..e17f317c4 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-152x152-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-152x152.png b/src/UI/Content/Images/favicon/apple-touch-icon-152x152.png new file mode 100644 index 000000000..6fdc50ce5 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-152x152.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-180x180-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-180x180-precomposed.png new file mode 100644 index 000000000..12879bd44 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-180x180-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-180x180.png b/src/UI/Content/Images/favicon/apple-touch-icon-180x180.png new file mode 100644 index 000000000..c169b7c1e Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-180x180.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-57x57-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-57x57-precomposed.png new file mode 100644 index 000000000..3b3e2b88d Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-57x57-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-57x57.png b/src/UI/Content/Images/favicon/apple-touch-icon-57x57.png new file mode 100644 index 000000000..aecc105f9 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-57x57.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-60x60-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-60x60-precomposed.png new file mode 100644 index 000000000..18ff320c7 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-60x60-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-60x60.png b/src/UI/Content/Images/favicon/apple-touch-icon-60x60.png new file mode 100644 index 000000000..87feabcc4 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-60x60.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-72x72-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-72x72-precomposed.png new file mode 100644 index 000000000..9de51dbab Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-72x72-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-72x72.png b/src/UI/Content/Images/favicon/apple-touch-icon-72x72.png new file mode 100644 index 000000000..d19050b11 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-72x72.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-76x76-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-76x76-precomposed.png new file mode 100644 index 000000000..859123883 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-76x76-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-76x76.png b/src/UI/Content/Images/favicon/apple-touch-icon-76x76.png new file mode 100644 index 000000000..29c962e23 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-76x76.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon-precomposed.png b/src/UI/Content/Images/favicon/apple-touch-icon-precomposed.png new file mode 100644 index 000000000..12879bd44 Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon-precomposed.png differ diff --git a/src/UI/Content/Images/favicon/apple-touch-icon.png b/src/UI/Content/Images/favicon/apple-touch-icon.png new file mode 100644 index 000000000..c169b7c1e Binary files /dev/null and b/src/UI/Content/Images/favicon/apple-touch-icon.png differ diff --git a/src/UI/Content/Images/favicon/browserconfig.xml b/src/UI/Content/Images/favicon/browserconfig.xml new file mode 100644 index 000000000..ff37cd996 --- /dev/null +++ b/src/UI/Content/Images/favicon/browserconfig.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<browserconfig> + <msapplication> + <tile> + <square70x70logo src="/Content/Images/favicon/mstile-70x70.png"/> + <square150x150logo src="/Content/Images/favicon/mstile-150x150.png"/> + <square310x310logo src="/Content/Images/favicon/mstile-310x310.png"/> + <wide310x150logo src="/Content/Images/favicon/mstile-310x150.png"/> + <TileColor>#272727</TileColor> + </tile> + </msapplication> +</browserconfig> diff --git a/src/UI/Content/Images/favicon/favicon-16x16.png b/src/UI/Content/Images/favicon/favicon-16x16.png new file mode 100644 index 000000000..cba60fb4c Binary files /dev/null and b/src/UI/Content/Images/favicon/favicon-16x16.png differ diff --git a/src/UI/Content/Images/favicon/favicon-194x194.png b/src/UI/Content/Images/favicon/favicon-194x194.png new file mode 100644 index 000000000..ebed98d02 Binary files /dev/null and b/src/UI/Content/Images/favicon/favicon-194x194.png differ diff --git a/src/UI/Content/Images/favicon/favicon-32x32.png b/src/UI/Content/Images/favicon/favicon-32x32.png new file mode 100644 index 000000000..a9e24d6c4 Binary files /dev/null and b/src/UI/Content/Images/favicon/favicon-32x32.png differ diff --git a/src/UI/Content/Images/favicon/favicon.ico b/src/UI/Content/Images/favicon/favicon.ico new file mode 100644 index 000000000..a0269b014 Binary files /dev/null and b/src/UI/Content/Images/favicon/favicon.ico differ diff --git a/src/UI/Content/Images/favicon/manifest.json b/src/UI/Content/Images/favicon/manifest.json new file mode 100644 index 000000000..24c1f5dfc --- /dev/null +++ b/src/UI/Content/Images/favicon/manifest.json @@ -0,0 +1,53 @@ +{ + "name": "Radarr", + "icons": [ + { + "src": "/Content/Images/favicon/android-chrome-36x36.png", + "sizes": "36x36", + "type": "image/png" + }, + { + "src": "/Content/Images/favicon/android-chrome-48x48.png", + "sizes": "48x48", + "type": "image/png" + }, + { + "src": "/Content/Images/favicon/android-chrome-72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "/Content/Images/favicon/android-chrome-96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "/Content/Images/favicon/android-chrome-144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "/Content/Images/favicon/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/Content/Images/favicon/android-chrome-256x256.png", + "sizes": "256x256", + "type": "image/png" + }, + { + "src": "/Content/Images/favicon/android-chrome-384x384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "/Content/Images/favicon/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#272727", + "background_color": "#272727", + "display": "standalone" +} \ No newline at end of file diff --git a/src/UI/Content/Images/favicon/mstile-144x144.png b/src/UI/Content/Images/favicon/mstile-144x144.png new file mode 100644 index 000000000..bb8ff6ffc Binary files /dev/null and b/src/UI/Content/Images/favicon/mstile-144x144.png differ diff --git a/src/UI/Content/Images/favicon/mstile-150x150.png b/src/UI/Content/Images/favicon/mstile-150x150.png new file mode 100644 index 000000000..5daff8258 Binary files /dev/null and b/src/UI/Content/Images/favicon/mstile-150x150.png differ diff --git a/src/UI/Content/Images/favicon/mstile-310x150.png b/src/UI/Content/Images/favicon/mstile-310x150.png new file mode 100644 index 000000000..1d534151a Binary files /dev/null and b/src/UI/Content/Images/favicon/mstile-310x150.png differ diff --git a/src/UI/Content/Images/favicon/mstile-310x310.png b/src/UI/Content/Images/favicon/mstile-310x310.png new file mode 100644 index 000000000..1995d8691 Binary files /dev/null and b/src/UI/Content/Images/favicon/mstile-310x310.png differ diff --git a/src/UI/Content/Images/favicon/mstile-70x70.png b/src/UI/Content/Images/favicon/mstile-70x70.png new file mode 100644 index 000000000..87293e25f Binary files /dev/null and b/src/UI/Content/Images/favicon/mstile-70x70.png differ diff --git a/src/UI/Content/Images/favicon/safari-pinned-tab.svg b/src/UI/Content/Images/favicon/safari-pinned-tab.svg new file mode 100644 index 000000000..1d4f4e92e --- /dev/null +++ b/src/UI/Content/Images/favicon/safari-pinned-tab.svg @@ -0,0 +1,59 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.11, written by Peter Selinger 2001-2013 +</metadata> +<g transform="translate(0.000000,16.000000) scale(0.002286,-0.002286)" +fill="#000000" stroke="none"> +<path d="M3298 6995 c-1 -1 -45 -5 -97 -9 -51 -3 -96 -8 -100 -10 -3 -2 -40 +-7 -80 -10 -41 -4 -78 -9 -81 -11 -3 -2 -24 -6 -46 -9 -51 -8 -81 -14 -102 +-20 -9 -3 -33 -8 -52 -11 -96 -16 -424 -116 -527 -161 -18 -8 -48 -21 -67 -29 +-19 -8 -37 -15 -40 -15 -9 0 -274 -132 -332 -166 -127 -73 -278 -174 -388 +-257 -67 -50 -123 -94 -126 -98 -3 -4 -25 -23 -50 -44 -71 -58 -236 -221 -326 +-323 -316 -355 -553 -766 -705 -1223 -26 -81 -51 -158 -53 -170 -3 -13 -12 +-51 -21 -84 -8 -33 -18 -76 -21 -95 -3 -19 -7 -37 -9 -40 -5 -7 -34 -174 -41 +-232 -3 -24 -7 -59 -10 -78 -12 -85 -18 -209 -18 -395 -1 -318 12 -441 89 +-815 2 -8 9 -35 16 -60 7 -25 13 -51 15 -58 9 -48 85 -270 131 -382 189 -466 +458 -863 823 -1215 66 -63 101 -95 210 -189 33 -28 204 -154 255 -188 140 -91 +198 -126 312 -186 115 -60 166 -85 298 -142 40 -18 102 -41 206 -78 185 -67 +485 -138 664 -157 28 -3 56 -8 64 -11 74 -25 640 -25 821 1 14 2 48 6 75 10 +43 6 184 31 280 50 62 12 224 58 338 96 791 262 1462 798 1894 1515 61 99 157 +284 208 399 98 220 209 585 239 791 4 22 9 51 11 64 3 14 13 84 22 155 24 188 +23 692 -2 810 -2 11 -7 39 -9 63 -41 338 -165 738 -335 1082 -456 919 -1301 +1595 -2301 1839 -121 29 -308 63 -420 76 -30 4 -68 8 -85 11 -35 5 -522 14 +-527 9z m-1674 -897 c59 -62 246 -254 415 -427 168 -173 343 -353 389 -400 +141 -145 335 -341 338 -341 2 0 29 12 61 26 61 28 283 105 283 99 0 -2 14 0 +32 5 30 9 52 13 168 30 72 11 320 10 390 -1 30 -5 69 -11 85 -13 106 -17 243 +-62 426 -138 28 -12 31 -8 464 437 59 61 236 243 394 405 159 162 318 326 354 +364 l66 69 43 -34 c204 -157 437 -383 581 -564 291 -364 508 -782 624 -1205 +37 -134 72 -300 88 -415 3 -22 8 -51 10 -65 3 -14 7 -56 10 -95 3 -38 8 -90 +11 -115 7 -65 7 -363 -1 -445 -18 -202 -24 -248 -51 -390 -13 -71 -36 -173 +-50 -225 -13 -52 -27 -104 -29 -115 -22 -98 -137 -384 -225 -560 -167 -330 +-349 -582 -615 -850 -141 -142 -199 -194 -328 -294 l-70 -55 -170 175 c-93 96 +-306 314 -472 484 -166 171 -372 381 -456 468 l-154 158 -75 -34 c-113 -52 +-204 -81 -350 -112 -184 -39 -449 -39 -625 0 -144 31 -288 79 -381 125 l-39 +19 -140 -144 c-249 -255 -735 -753 -924 -947 -101 -103 -187 -188 -191 -188 +-3 0 -15 8 -26 18 -10 9 -42 34 -69 55 -256 196 -597 573 -750 829 -5 9 -34 +56 -63 105 -129 214 -277 560 -338 790 -9 32 -17 65 -20 73 -16 56 -72 347 +-78 400 -3 36 -8 81 -11 100 -15 125 -22 460 -11 560 2 19 7 73 11 120 4 46 8 +87 10 90 1 3 6 32 10 65 3 32 8 62 11 66 2 3 6 31 10 60 3 30 8 57 11 62 2 4 +7 20 9 35 26 150 117 429 206 632 128 294 310 584 523 836 114 134 339 350 +479 459 21 17 48 38 60 47 12 10 23 17 26 18 3 0 54 -51 114 -112z"/> +<path d="M1403 5507 c-251 -260 -468 -600 -603 -942 -59 -148 -133 -395 -146 +-485 -2 -14 -6 -36 -9 -50 -27 -128 -48 -349 -48 -515 -1 -94 12 -334 17 -344 +2 -3 7 -35 10 -70 21 -197 115 -543 203 -741 45 -101 120 -251 150 -298 18 +-29 33 -55 33 -58 0 -7 160 -247 169 -254 3 -3 31 -36 62 -75 89 -110 195 +-225 208 -225 3 0 311 306 684 679 373 374 687 687 697 696 19 17 21 16 58 +-17 227 -205 561 -282 862 -198 132 37 274 116 367 203 32 30 33 30 52 13 11 +-10 321 -319 688 -687 367 -369 676 -674 685 -679 17 -10 30 1 146 129 93 102 +255 329 329 461 180 319 293 637 347 970 23 147 25 160 32 271 17 253 7 429 +-41 744 -10 64 -64 272 -96 370 -134 403 -362 786 -654 1093 l-50 53 -701 +-701 -701 -700 -27 25 c-174 161 -398 246 -636 240 -149 -4 -180 -9 -284 -44 +-122 -42 -244 -113 -336 -198 l-24 -22 -698 697 c-384 383 -700 698 -701 699 +-2 1 -21 -17 -44 -40z"/> +</g> +</svg> diff --git a/src/UI/Content/Images/logos/128.png b/src/UI/Content/Images/logos/128.png index 5e143b52e..02f00f08f 100644 Binary files a/src/UI/Content/Images/logos/128.png and b/src/UI/Content/Images/logos/128.png differ diff --git a/src/UI/Content/Images/logos/32.png b/src/UI/Content/Images/logos/32.png index f1fe93db5..41a6dd279 100644 Binary files a/src/UI/Content/Images/logos/32.png and b/src/UI/Content/Images/logos/32.png differ diff --git a/src/UI/Content/Images/logos/48.png b/src/UI/Content/Images/logos/48.png index 8b9d0fc88..45cf3047c 100644 Binary files a/src/UI/Content/Images/logos/48.png and b/src/UI/Content/Images/logos/48.png differ diff --git a/src/UI/Content/Images/logos/64.png b/src/UI/Content/Images/logos/64.png index 80edc7894..483e3d809 100644 Binary files a/src/UI/Content/Images/logos/64.png and b/src/UI/Content/Images/logos/64.png differ diff --git a/src/UI/Movies/Details/MoviesDetailsLayout.js b/src/UI/Movies/Details/MoviesDetailsLayout.js index 119c6bd71..efd0ece09 100644 --- a/src/UI/Movies/Details/MoviesDetailsLayout.js +++ b/src/UI/Movies/Details/MoviesDetailsLayout.js @@ -16,302 +16,303 @@ require('backstrech'); require('../../Mixins/backbone.signalr.mixin'); module.exports = Marionette.Layout.extend({ - itemViewContainer : '.x-movie-seasons', - template : 'Movies/Details/MoviesDetailsTemplate', - - regions : { - seasons : '#seasons', - info : '#info', - search : '#movie-search', - history : '#movie-history', - files : "#movie-files" - }, - - - ui : { - header : '.x-header', - monitored : '.x-monitored', - edit : '.x-edit', - refresh : '.x-refresh', - rename : '.x-rename', - searchAuto : '.x-search', - poster : '.x-movie-poster', - manualSearch : '.x-manual-search', - history : '.x-movie-history', - search : '.x-movie-search', - files : ".x-movie-files" - }, - - events : { - 'click .x-episode-file-editor' : '_showFiles', - 'click .x-monitored' : '_toggleMonitored', - 'click .x-edit' : '_editMovie', - 'click .x-refresh' : '_refreshMovies', - 'click .x-rename' : '_renameMovies', - 'click .x-search' : '_moviesSearch', - 'click .x-manual-search' : '_showSearch', - 'click .x-movie-history' : '_showHistory', - 'click .x-movie-search' : '_showSearch', - "click .x-movie-files" : "_showFiles", - }, - - initialize : function() { - this.moviesCollection = MoviesCollection.clone(); - this.moviesCollection.shadowCollection.bindSignalR(); - - this.listenTo(this.model, 'change:monitored', this._setMonitoredState); - this.listenTo(this.model, 'remove', this._moviesRemoved); - this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); - - this.listenTo(this.model, 'change', function(model, options) { - if (options && options.changeSource === 'signalr') { - this._refresh(); - } - }); - - this.listenTo(this.model, 'change:images', this._updateImages); - }, - - onShow : function() { - this.searchLayout = new SearchLayout({ model : this.model }); - this.searchLayout.startManualSearch = true; - - this.filesLayout = new FilesLayout({ model : this.model }); - - this._showBackdrop(); - this._showSeasons(); - this._setMonitoredState(); - this._showInfo(); - if (this.model.get("movieFile")) { - this._showFiles() - } else { - this._showHistory(); - } - - }, - - onRender : function() { - CommandController.bindToCommand({ - element : this.ui.refresh, - command : { - name : 'refreshMovie' - } - }); - - CommandController.bindToCommand({ - element : this.ui.searchAuto, - command : { - name : 'moviesSearch' - } - }); - - CommandController.bindToCommand({ - element : this.ui.rename, - command : { - name : 'renameMovieFiles', - movieId : this.model.id, - seasonNumber : -1 - } - }); - }, - - onClose : function() { - if (this._backstrech) { - this._backstrech.destroy(); - delete this._backstrech; - } - - $('body').removeClass('backdrop'); - reqres.removeHandler(reqres.Requests.GetEpisodeFileById); - }, - - _getImage : function(type) { - var image = _.where(this.model.get('images'), { coverType : type }); - - if (image && image[0]) { - return image[0].url; - } - - return undefined; - }, - - _showHistory : function(e) { - if (e) { - e.preventDefault(); - } - - this.ui.history.tab('show'); - this.history.show(new HistoryLayout({ - model : this.model - })); - }, - - _showSearch : function(e) { - if (e) { - e.preventDefault(); - } - - this.ui.search.tab('show'); - this.search.show(this.searchLayout); - }, - - _showFiles : function(e) { - if (e) { - e.preventDefault(); - } - - this.ui.files.tab('show'); - this.files.show(this.filesLayout); - }, - - _toggleMonitored : function() { - var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true }); - - this.ui.monitored.spinForPromise(savePromise); - }, - - _setMonitoredState : function() { - var monitored = this.model.get('monitored'); - - this.ui.monitored.removeAttr('data-idle-icon'); - this.ui.monitored.removeClass('fa-spin icon-sonarr-spinner'); - - if (monitored) { - this.ui.monitored.addClass('icon-sonarr-monitored'); - this.ui.monitored.removeClass('icon-sonarr-unmonitored'); - this.$el.removeClass('movie-not-monitored'); - } else { - this.ui.monitored.addClass('icon-sonarr-unmonitored'); - this.ui.monitored.removeClass('icon-sonarr-monitored'); - this.$el.addClass('movie-not-monitored'); - } - }, - - _editMovie : function() { - vent.trigger(vent.Commands.EditMovieCommand, { movie : this.model }); - }, - - _refreshMovies : function() { - CommandController.Execute('refreshMovie', { - name : 'refreshMovie', - movieId : this.model.id - }); - }, - - _moviesRemoved : function() { - Backbone.history.navigate('/', { trigger : true }); - }, - - _renameMovies : function() { - vent.trigger(vent.Commands.ShowRenamePreview, { movie : this.model }); - }, - - _moviesSearch : function() { - CommandController.Execute('moviesSearch', { - name : 'moviesSearch', - movieId : this.model.id - }); - }, - - _showSeasons : function() { - var self = this; - - return; - - reqres.setHandler(reqres.Requests.GetEpisodeFileById, function(episodeFileId) { - return self.episodeFileCollection.get(episodeFileId); - }); - - reqres.setHandler(reqres.Requests.GetAlternateNameBySeasonNumber, function(moviesId, seasonNumber, sceneSeasonNumber) { - if (self.model.get('id') !== moviesId) { - return []; - } - - if (sceneSeasonNumber === undefined) { - sceneSeasonNumber = seasonNumber; - } - - return _.where(self.model.get('alternateTitles'), - function(alt) { - return alt.sceneSeasonNumber === sceneSeasonNumber || alt.seasonNumber === seasonNumber; - }); - }); - - $.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch()).done(function() { - var seasonCollectionView = new SeasonCollectionView({ - collection : self.seasonCollection, - episodeCollection : self.episodeCollection, - movies : self.model - }); - - if (!self.isClosed) { - self.seasons.show(seasonCollectionView); - } - }); - }, - - _showInfo : function() { - this.info.show(new InfoView({ - model : this.model - })); - }, - - _commandComplete : function(options) { - if (options.command.get('name') === 'renameMoviefiles') { - if (options.command.get('moviesId') === this.model.get('id')) { - this._refresh(); - } - } - }, - - _refresh : function() { - //this.seasonCollection.add(this.model.get('seasons'), { merge : true }); - //this.episodeCollection.fetch(); - //this.episodeFileCollection.fetch(); - - this._setMonitoredState(); - this._showInfo(); - }, - - _openEpisodeFileEditor : function() { - var view = new EpisodeFileEditorLayout({ - movies : this.model, - episodeCollection : this.episodeCollection - }); - - vent.trigger(vent.Commands.OpenModalCommand, view); - }, - - _updateImages : function () { - var poster = this._getImage('poster'); - - if (poster) { - this.ui.poster.attr('src', poster); - } - - this._showBackdrop(); - }, - - _showBackdrop : function () { - $('body').addClass('backdrop'); - var fanArt = this._getImage('banner'); - - if (fanArt) { - this._backstrech = $.backstretch(fanArt); - } else { - $('body').removeClass('backdrop'); - } - }, - - _manualSearchM : function() { - console.warn("Manual Search started"); - console.warn(this.model.id); - console.warn(this.model) - console.warn(this.episodeCollection); - vent.trigger(vent.Commands.ShowEpisodeDetails, { - episode : this.model, - hideMoviesLink : true, - openingTab : 'search' - }); - } + itemViewContainer : '.x-movie-seasons', + template : 'Movies/Details/MoviesDetailsTemplate', + + regions : { + seasons : '#seasons', + info : '#info', + search : '#movie-search', + history : '#movie-history', + files : "#movie-files" + }, + + + ui : { + header : '.x-header', + monitored : '.x-monitored', + edit : '.x-edit', + refresh : '.x-refresh', + rename : '.x-rename', + searchAuto : '.x-search', + poster : '.x-movie-poster', + manualSearch : '.x-manual-search', + history : '.x-movie-history', + search : '.x-movie-search', + files : ".x-movie-files" + }, + + events : { + 'click .x-episode-file-editor' : '_showFiles', + 'click .x-monitored' : '_toggleMonitored', + 'click .x-edit' : '_editMovie', + 'click .x-refresh' : '_refreshMovies', + 'click .x-rename' : '_renameMovies', + 'click .x-search' : '_moviesSearch', + 'click .x-manual-search' : '_showSearch', + 'click .x-movie-history' : '_showHistory', + 'click .x-movie-search' : '_showSearch', + "click .x-movie-files" : "_showFiles", + }, + + initialize : function() { + this.moviesCollection = MoviesCollection.clone(); + this.moviesCollection.shadowCollection.bindSignalR(); + + this.listenTo(this.model, 'change:monitored', this._setMonitoredState); + this.listenTo(this.model, 'remove', this._moviesRemoved); + this.listenTo(this.model, "change:movieFile", this._refreshFiles); + + this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); + + this.listenTo(this.model, 'change', function(model, options) { + if (options && options.changeSource === 'signalr') { + this._refresh(); + } + }); + + this.listenTo(this.model, 'change:images', this._updateImages); + }, + + onShow : function() { + this.searchLayout = new SearchLayout({ model : this.model }); + this.searchLayout.startManualSearch = true; + + this.filesLayout = new FilesLayout({ model : this.model }); + + this._showBackdrop(); + this._showSeasons(); + this._setMonitoredState(); + this._showInfo(); + if (this.model.get("movieFile")) { + this._showFiles() + } else { + this._showHistory(); + } + + }, + + onRender : function() { + CommandController.bindToCommand({ + element : this.ui.refresh, + command : { + name : 'refreshMovie' + } + }); + + CommandController.bindToCommand({ + element : this.ui.searchAuto, + command : { + name : 'moviesSearch' + } + }); + + CommandController.bindToCommand({ + element : this.ui.rename, + command : { + name : 'renameMovieFiles', + movieId : this.model.id, + seasonNumber : -1 + } + }); + }, + + onClose : function() { + if (this._backstrech) { + this._backstrech.destroy(); + delete this._backstrech; + } + + $('body').removeClass('backdrop'); + reqres.removeHandler(reqres.Requests.GetEpisodeFileById); + }, + + _getImage : function(type) { + var image = _.where(this.model.get('images'), { coverType : type }); + + if (image && image[0]) { + return image[0].url; + } + + return undefined; + }, + + _showHistory : function(e) { + if (e) { + e.preventDefault(); + } + + this.ui.history.tab('show'); + this.history.show(new HistoryLayout({ + model : this.model + })); + }, + + _showSearch : function(e) { + if (e) { + e.preventDefault(); + } + + this.ui.search.tab('show'); + this.search.show(this.searchLayout); + }, + + _showFiles : function(e) { + if (e) { + e.preventDefault(); + } + + this.ui.files.tab('show'); + this.files.show(this.filesLayout); + }, + + _toggleMonitored : function() { + var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true }); + + this.ui.monitored.spinForPromise(savePromise); + }, + + _setMonitoredState : function() { + var monitored = this.model.get('monitored'); + + this.ui.monitored.removeAttr('data-idle-icon'); + this.ui.monitored.removeClass('fa-spin icon-sonarr-spinner'); + + if (monitored) { + this.ui.monitored.addClass('icon-sonarr-monitored'); + this.ui.monitored.removeClass('icon-sonarr-unmonitored'); + this.$el.removeClass('movie-not-monitored'); + } else { + this.ui.monitored.addClass('icon-sonarr-unmonitored'); + this.ui.monitored.removeClass('icon-sonarr-monitored'); + this.$el.addClass('movie-not-monitored'); + } + }, + + _editMovie : function() { + vent.trigger(vent.Commands.EditMovieCommand, { movie : this.model }); + }, + + _refreshMovies : function() { + CommandController.Execute('refreshMovie', { + name : 'refreshMovie', + movieId : this.model.id + }); + }, + + _moviesRemoved : function() { + Backbone.history.navigate('/', { trigger : true }); + }, + + _renameMovies : function() { + vent.trigger(vent.Commands.ShowRenamePreview, { movie : this.model }); + }, + + _moviesSearch : function() { + CommandController.Execute('moviesSearch', { + name : 'moviesSearch', + movieIds : [this.model.id] + }); + }, + + _showSeasons : function() { + var self = this; + + return; + + reqres.setHandler(reqres.Requests.GetEpisodeFileById, function(episodeFileId) { + return self.episodeFileCollection.get(episodeFileId); + }); + + reqres.setHandler(reqres.Requests.GetAlternateNameBySeasonNumber, function(moviesId, seasonNumber, sceneSeasonNumber) { + if (self.model.get('id') !== moviesId) { + return []; + } + + if (sceneSeasonNumber === undefined) { + sceneSeasonNumber = seasonNumber; + } + + return _.where(self.model.get('alternateTitles'), + function(alt) { + return alt.sceneSeasonNumber === sceneSeasonNumber || alt.seasonNumber === seasonNumber; + }); + }); + + $.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch()).done(function() { + var seasonCollectionView = new SeasonCollectionView({ + collection : self.seasonCollection, + episodeCollection : self.episodeCollection, + movies : self.model + }); + + if (!self.isClosed) { + self.seasons.show(seasonCollectionView); + } + }); + }, + + _showInfo : function() { + this.info.show(new InfoView({ + model : this.model + })); + }, + + _commandComplete : function(options) { + if (options.command.get('name') === 'renameMoviefiles') { + if (options.command.get('moviesId') === this.model.get('id')) { + this._refresh(); + } + } + }, + + _refresh : function() { + //this.seasonCollection.add(this.model.get('seasons'), { merge : true }); + //this.episodeCollection.fetch(); + //this.episodeFileCollection.fetch(); + this._setMonitoredState(); + this._showInfo(); + }, + + _openEpisodeFileEditor : function() { + var view = new EpisodeFileEditorLayout({ + movies : this.model, + episodeCollection : this.episodeCollection + }); + + vent.trigger(vent.Commands.OpenModalCommand, view); + }, + + _updateImages : function () { + var poster = this._getImage('poster'); + + if (poster) { + this.ui.poster.attr('src', poster); + } + + this._showBackdrop(); + }, + + _showBackdrop : function () { + $('body').addClass('backdrop'); + var fanArt = this._getImage('banner'); + + if (fanArt) { + this._backstrech = $.backstretch(fanArt); + } else { + $('body').removeClass('backdrop'); + } + }, + + _manualSearchM : function() { + console.warn("Manual Search started"); + console.warn(this.model.id); + console.warn(this.model) + console.warn(this.episodeCollection); + vent.trigger(vent.Commands.ShowEpisodeDetails, { + episode : this.model, + hideMoviesLink : true, + openingTab : 'search' + }); + } }); diff --git a/src/UI/Movies/Files/Edit/EditFileTemplate.hbs b/src/UI/Movies/Files/Edit/EditFileTemplate.hbs new file mode 100644 index 000000000..e06c410d2 --- /dev/null +++ b/src/UI/Movies/Files/Edit/EditFileTemplate.hbs @@ -0,0 +1,32 @@ +<div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3>{{relativePath}}</h3> + </div> + <div class="modal-body edit-series-modal"> + <div class="row"> + <div class="col-sm-12"> + <div class="form-horizontal"> + <div class="form-group"> + <label class="col-sm-4 control-label">Quality</label> + + <div class="col-sm-4"> + <select class="form-control x-quality" id="inputProfile" name="qualityId"> + {{#each qualities}} + <option value="{{quality.id}}">{{quality.name}}</option> + {{/each}} + </select> + + </div> + </div> + </div> + </div> + </div> + </div> + <div class="modal-footer"> + + <span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span> + <button class="btn" data-dismiss="modal">Cancel</button> + <button class="btn btn-primary x-save">Save</button> + </div> +</div> diff --git a/src/UI/Movies/Files/Edit/EditFileView.js b/src/UI/Movies/Files/Edit/EditFileView.js new file mode 100644 index 000000000..8a0633fb8 --- /dev/null +++ b/src/UI/Movies/Files/Edit/EditFileView.js @@ -0,0 +1,60 @@ +var vent = require('vent'); +var Marionette = require('marionette'); +var Qualities = require('../../../Quality/QualityDefinitionCollection'); +var AsModelBoundView = require('../../../Mixins/AsModelBoundView'); +var AsValidatedView = require('../../../Mixins/AsValidatedView'); +var AsEditModalView = require('../../../Mixins/AsEditModalView'); +require('../../../Mixins/TagInput'); +require('../../../Mixins/FileBrowser'); + +var view = Marionette.ItemView.extend({ + template : 'Movies/Files/Edit/EditFileTemplate', + + ui : { + quality : '.x-quality', + path : '.x-path', + tags : '.x-tags' + }, + + events : { + + }, + + initialize : function() { + this.qualities = new Qualities(); + var self = this; + this.listenTo(this.qualities, 'all', this._qualitiesUpdated); + this.qualities.fetch() + + }, + + onRender : function() { + this.ui.quality.val(this.model.get("quality").quality.id) + }, + + _onBeforeSave : function() { + var qualityId = this.ui.quality.val(); + var quality = this.qualities.find(function(m){return m.get("quality").id == qualityId}).get("quality"); + var mQuality = this.model.get("quality"); + mQuality.quality = quality; + this.model.set({ quality : mQuality }); + }, + + _qualitiesUpdated : function() { + this.templateHelpers = {}; + this.templateHelpers.qualities = this.qualities.toJSON(); + this.render(); + }, + + _onAfterSave : function() { + this.trigger('saved'); + vent.trigger(vent.Commands.CloseModalCommand); + }, + +}); + +AsModelBoundView.call(view); +AsValidatedView.call(view); +AsEditModalView.call(view); + +module.exports = view; diff --git a/src/UI/Movies/Files/EditFileCell.js b/src/UI/Movies/Files/EditFileCell.js new file mode 100644 index 000000000..27b831799 --- /dev/null +++ b/src/UI/Movies/Files/EditFileCell.js @@ -0,0 +1,22 @@ +var vent = require('vent'); +var Backgrid = require('backgrid'); + +module.exports = Backgrid.Cell.extend({ + className : 'edit-episode-file-cell', + + events : { + 'click' : '_onClick' + }, + + render : function() { + this.$el.empty(); + this.$el.html('<i class="icon-sonarr-edit" title="Edit information about this file."></i>'); + + return this; + }, + + _onClick : function() { + var self = this; + vent.trigger(vent.Commands.EditFileCommand, { file : this.model }); + } +}); diff --git a/src/UI/Movies/Files/FilesLayout.js b/src/UI/Movies/Files/FilesLayout.js index 3e6dd2bdd..c30c59564 100644 --- a/src/UI/Movies/Files/FilesLayout.js +++ b/src/UI/Movies/Files/FilesLayout.js @@ -19,89 +19,117 @@ var ProtocolCell = require('../../Release/ProtocolCell'); var PeersCell = require('../../Release/PeersCell'); var EditionCell = require('../../Cells/EditionCell'); var DeleteFileCell = require("./DeleteFileCell"); +var EditFileCell = require("./EditFileCell"); module.exports = Marionette.Layout.extend({ - template : 'Movies/Files/FilesLayoutTemplate', + template : 'Movies/Files/FilesLayoutTemplate', - regions : { - main : '#movie-files-region', - grid : "#movie-files-grid" - }, + regions : { + main : '#movie-files-region', + grid : "#movie-files-grid" + }, - events : { - 'click .x-search-auto' : '_searchAuto', - 'click .x-search-manual' : '_searchManual', - 'click .x-search-back' : '_showButtons' - }, + events : { + 'click .x-search-auto' : '_searchAuto', + 'click .x-search-manual' : '_searchManual', + 'click .x-search-back' : '_showButtons' + }, - columns : [ - { - name : 'title', - label : 'Title', - cell : FileTitleCell - }, - { - name : "mediaInfo", - label : "Media Info", - cell : MediaInfoCell - }, - { - name : 'edition', - label : 'Edition', - cell : EditionCell, - title : "Edition", - }, - { - name : 'size', - label : 'Size', - cell : FileSizeCell - }, - { - name : 'quality', - label : 'Quality', - cell : QualityCell, - }, - { - name : "delete", - label : "", - cell : DeleteFileCell, - } - ], + columns : [ + { + name : 'title', + label : 'Title', + cell : FileTitleCell + }, + { + name : "mediaInfo", + label : "Media Info", + cell : MediaInfoCell + }, + { + name : 'edition', + label : 'Edition', + cell : EditionCell, + title : "Edition", + }, + { + name : 'size', + label : 'Size', + cell : FileSizeCell + }, + { + name : 'quality', + label : 'Quality', + cell : QualityCell, + }, + { + name : "delete", + label : "", + cell : DeleteFileCell, + }, + { + name : "edit", + label : "", + cell : EditFileCell, + } + ], - initialize : function(movie) { - this.filesCollection = new FilesCollection(); - var file = movie.model.get("movieFile"); - this.filesCollection.add(file); - //this.listenTo(this.releaseCollection, 'sync', this._showSearchResults); - }, + initialize : function(movie) { + this.filesCollection = new FilesCollection(); + var file = movie.model.get("movieFile"); + this.movie = movie; + this.filesCollection.add(file); + //this.listenTo(this.releaseCollection, 'sync', this._showSearchResults); + this.listenTo(this.model, 'change', function(model, options) { + if (options && options.changeSource === 'signalr') { + this._refresh(movie); + } + }); - onShow : function() { - this.grid.show(new Backgrid.Grid({ - row : Backgrid.Row, - columns : this.columns, - collection : this.filesCollection, - className : 'table table-hover' - })); - }, + vent.on(vent.Commands.CloseModalCommand, this._refreshClose, this); + }, - _showMainView : function() { - this.main.show(this.mainView); - }, + _refresh : function(movie) { + this.filesCollection = new FilesCollection(); + var file = movie.model.get("movieFile"); + this.filesCollection.add(file); + this.onShow(); + }, - _showButtons : function() { - this._showMainView(); - }, + _refreshClose : function(options) { + this.filesCollection = new FilesCollection(); + var file = this.movie.model.get("movieFile"); + this.filesCollection.add(file); + this.onShow(); + }, - _showSearchResults : function() { - if (this.releaseCollection.length === 0) { - this.mainView = new NoResultsView(); - } + onShow : function() { + this.grid.show(new Backgrid.Grid({ + row : Backgrid.Row, + columns : this.columns, + collection : this.filesCollection, + className : 'table table-hover' + })); + }, - else { - //this.mainView = new ManualSearchLayout({ collection : this.releaseCollection }); - } + _showMainView : function() { + this.main.show(this.mainView); + }, - this._showMainView(); - } + _showButtons : function() { + this._showMainView(); + }, + + _showSearchResults : function() { + if (this.releaseCollection.length === 0) { + this.mainView = new NoResultsView(); + } + + else { + //this.mainView = new ManualSearchLayout({ collection : this.releaseCollection }); + } + + this._showMainView(); + } }); diff --git a/src/UI/Movies/Index/MoviesIndexLayout.js b/src/UI/Movies/Index/MoviesIndexLayout.js index 87a2de772..479c6df12 100644 --- a/src/UI/Movies/Index/MoviesIndexLayout.js +++ b/src/UI/Movies/Index/MoviesIndexLayout.js @@ -182,6 +182,27 @@ module.exports = Marionette.Layout.extend({ tooltip : 'Missing Only', icon : 'icon-sonarr-missing', callback : this._setFilter + }, + { + key : 'released', + title : '', + tooltip : 'Released', + icon : 'icon-sonarr-movie-released', + callback : this._setFilter + }, + { + key : 'announced', + title : '', + tooltip : 'Announced', + icon : 'icon-sonarr-movie-announced', + callback : this._setFilter + }, + { + key : 'cinemas', + title : '', + tooltip : 'In Cinemas', + icon : 'icon-sonarr-movie-cinemas', + callback : this._setFilter } ] }; diff --git a/src/UI/Movies/MoviesCollection.js b/src/UI/Movies/MoviesCollection.js index 779fb4a0d..8ec279436 100644 --- a/src/UI/Movies/MoviesCollection.js +++ b/src/UI/Movies/MoviesCollection.js @@ -14,13 +14,13 @@ var Collection = PageableCollection.extend({ model : MovieModel, tableName : 'movie', - state : { - sortKey : 'sortTitle', - order : 1, - pageSize : 100000, - secondarySortKey : 'sortTitle', - secondarySortOrder : -1 - }, + state : { + sortKey : 'sortTitle', + order : -1, + pageSize : 100000, + secondarySortKey : 'sortTitle', + secondarySortOrder : -1 + }, mode : 'client', @@ -47,6 +47,44 @@ var Collection = PageableCollection.extend({ return proxy.save(); }, + filterModes : { + 'all' : [ + null, + null + ], + 'continuing' : [ + 'status', + 'continuing' + ], + 'ended' : [ + 'status', + 'ended' + ], + 'monitored' : [ + 'monitored', + true + ], + 'missing' : [ + 'downloaded', + false + ], + 'released' : [ + null, + null, + function(model) { return model.getStatus() == "released"; } + ], + 'announced' : [ + null, + null, + function(model) { return model.getStatus() == "announced"; } + ], + 'cinemas' : [ + null, + null, + function(model) { return model.getStatus() == "inCinemas"; } + ] + }, + importFromList : function(models) { var self = this; diff --git a/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs b/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs index a10f5d234..9043ad2f5 100644 --- a/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs +++ b/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs @@ -23,7 +23,7 @@ </div> <div class="col-sm-2 col-sm-pull-1"> - <input type="number" name="downloadedMovieScanInterval" class="form-control" /> + <input type="number" name="downloadedEpisodesScanInterval" class="form-control" /> </div> </div> </fieldset> \ No newline at end of file diff --git a/src/UI/Settings/General/GeneralViewTemplate.hbs b/src/UI/Settings/General/GeneralViewTemplate.hbs index e4b3e6b59..cd7c25f8b 100644 --- a/src/UI/Settings/General/GeneralViewTemplate.hbs +++ b/src/UI/Settings/General/GeneralViewTemplate.hbs @@ -328,7 +328,7 @@ </div> {{#if_mono}} - <div class="alert alert-warning">Please see: <a href="https://github.com/NzbDrone/NzbDrone/wiki/Updating">the wiki</a> for more information</div> + <div class="alert alert-warning">Please see: <a href="https://github.com/Radarr/Radarr/wiki">the wiki</a> for more information</div> <div class="form-group"> <label class="col-sm-3 control-label">Automatic</label> diff --git a/src/UI/Settings/Indexers/Options/IndexerOptionsViewTemplate.hbs b/src/UI/Settings/Indexers/Options/IndexerOptionsViewTemplate.hbs index 056d12648..7770ba4ee 100644 --- a/src/UI/Settings/Indexers/Options/IndexerOptionsViewTemplate.hbs +++ b/src/UI/Settings/Indexers/Options/IndexerOptionsViewTemplate.hbs @@ -34,7 +34,7 @@ </div> <div class="col-sm-2 col-sm-pull-1"> - <input type="number" name="rssSyncInterval" class="form-control" min="0" max="120"/> + <input type="number" name="rssSyncInterval" class="form-control" min="0" max="720"/> </div> </div> </fieldset> diff --git a/src/UI/Settings/Profile/Edit/EditProfileView.js b/src/UI/Settings/Profile/Edit/EditProfileView.js index 23535d9e6..056a23d2c 100644 --- a/src/UI/Settings/Profile/Edit/EditProfileView.js +++ b/src/UI/Settings/Profile/Edit/EditProfileView.js @@ -4,25 +4,38 @@ var LanguageCollection = require('../Language/LanguageCollection'); var Config = require('../../../Config'); var AsModelBoundView = require('../../../Mixins/AsModelBoundView'); var AsValidatedView = require('../../../Mixins/AsValidatedView'); +require('../../../Mixins/TagInput'); +require('bootstrap'); +require('bootstrap.tagsinput'); var view = Marionette.ItemView.extend({ - template : 'Settings/Profile/Edit/EditProfileViewTemplate', + template : 'Settings/Profile/Edit/EditProfileViewTemplate', - ui : { cutoff : '.x-cutoff' }, + ui : { cutoff : '.x-cutoff', + preferred : '.x-preferred', + }, - templateHelpers : function() { - return { - languages : LanguageCollection.toJSON() - }; - }, + onRender : function() { + this.ui.preferred.tagsinput({ + trimValue : true, + allowDuplicates: true, + tagClass : 'label label-success' + }); + }, - getCutoff : function() { - var self = this; + templateHelpers : function() { + return { + languages : LanguageCollection.toJSON() + }; + }, - return _.findWhere(_.pluck(this.model.get('items'), 'quality'), { id : parseInt(self.ui.cutoff.val(), 10) }); - } + getCutoff : function() { + var self = this; + + return _.findWhere(_.pluck(this.model.get('items'), 'quality'), { id : parseInt(self.ui.cutoff.val(), 10) }); + } }); AsValidatedView.call(view); -module.exports = AsModelBoundView.call(view); \ No newline at end of file +module.exports = AsModelBoundView.call(view); diff --git a/src/UI/Settings/Profile/Edit/EditProfileViewTemplate.hbs b/src/UI/Settings/Profile/Edit/EditProfileViewTemplate.hbs index c19d10e5c..072a70ed0 100644 --- a/src/UI/Settings/Profile/Edit/EditProfileViewTemplate.hbs +++ b/src/UI/Settings/Profile/Edit/EditProfileViewTemplate.hbs @@ -1,45 +1,59 @@ <div class="form-group"> - <label class="col-sm-3 control-label">Name</label> + <label class="col-sm-3 control-label">Name</label> - <div class="col-sm-5"> - <input type="text" name="name" class="form-control"> - </div> + <div class="col-sm-5"> + <input type="text" name="name" class="form-control"> + </div> </div> <hr> <div class="form-group"> - <label class="col-sm-3 control-label">Language</label> - - <div class="col-sm-5"> - <select class="form-control" name="language"> - {{#each languages}} - {{#unless_eq nameLower compare="unknown"}} - <option value="{{nameLower}}">{{name}}</option> - {{/unless_eq}} - {{/each}} - </select> - </div> - - <div class="col-sm-1 help-inline"> - <i class="icon-sonarr-form-info" title="Series assigned this profile will be look for episodes with the selected language"/> - </div> + <label class="col-sm-3 control-label">Language</label> + + <div class="col-sm-5"> + <select class="form-control" name="language"> + {{#each languages}} + {{#unless_eq nameLower compare="unknown"}} + <option value="{{nameLower}}">{{name}}</option> + {{/unless_eq}} + {{/each}} + </select> + </div> + + <div class="col-sm-1 help-inline"> + <i class="icon-sonarr-form-info" title="Series assigned this profile will be look for episodes with the selected language"/> + </div> </div> + + <div class="form-group"> + <label class="col-sm-3 control-label">Preferred Tags</label> + + <div class="col-sm-1 col-sm-push-5 help-inline"> + <i class="icon-sonarr-form-info" title="When the release contains these tags it will be preferred." /> + </div> + + <div class="col-sm-5 col-sm-pull-1"> + <input type="text" name="preferredTags" class="form-control x-preferred"/> + </div> + </div> + + <div class="form-group"> - <label class="col-sm-3 control-label">Cutoff</label> - - <div class="col-sm-5"> - <select class="form-control x-cutoff" name="cutoff.id" validation-name="cutoff"> - {{#eachReverse items}} - {{#if allowed}} - <option value="{{quality.id}}">{{quality.name}}</option> - {{/if}} - {{/eachReverse}} - </select> - </div> - - <div class="col-sm-1 help-inline"> - <i class="icon-sonarr-form-info" title="Once this quality is reached Radarr will no longer download episodes"/> - </div> + <label class="col-sm-3 control-label">Cutoff</label> + + <div class="col-sm-5"> + <select class="form-control x-cutoff" name="cutoff.id" validation-name="cutoff"> + {{#eachReverse items}} + {{#if allowed}} + <option value="{{quality.id}}">{{quality.name}}</option> + {{/if}} + {{/eachReverse}} + </select> + </div> + + <div class="col-sm-1 help-inline"> + <i class="icon-sonarr-form-info" title="Once this quality is reached Radarr will no longer download episodes"/> + </div> </div> diff --git a/src/UI/Settings/Profile/ProfileView.js b/src/UI/Settings/Profile/ProfileView.js index 4241c3f12..10a4a9be3 100644 --- a/src/UI/Settings/Profile/ProfileView.js +++ b/src/UI/Settings/Profile/ProfileView.js @@ -6,30 +6,32 @@ require('./AllowedLabeler'); require('./LanguageLabel'); require('bootstrap'); + var view = Marionette.ItemView.extend({ - template : 'Settings/Profile/ProfileViewTemplate', - tagName : 'li', - - ui : { - "progressbar" : '.progress .bar', - "deleteButton" : '.x-delete' - }, - - events : { - 'click' : '_editProfile' - }, - - initialize : function() { - this.listenTo(this.model, 'sync', this.render); - }, - - _editProfile : function() { - var view = new EditProfileView({ - model : this.model, - profileCollection : this.model.collection - }); - AppLayout.modalRegion.show(view); - } + template : 'Settings/Profile/ProfileViewTemplate', + tagName : 'li', + + ui : { + "progressbar" : '.progress .bar', + "deleteButton" : '.x-delete', + + }, + + events : { + 'click' : '_editProfile' + }, + + initialize : function() { + this.listenTo(this.model, 'sync', this.render); + }, + + _editProfile : function() { + var view = new EditProfileView({ + model : this.model, + profileCollection : this.model.collection + }); + AppLayout.modalRegion.show(view); + } }); -module.exports = AsModelBoundView.call(view); \ No newline at end of file +module.exports = AsModelBoundView.call(view); diff --git a/src/UI/Settings/Profile/ProfileViewTemplate.hbs b/src/UI/Settings/Profile/ProfileViewTemplate.hbs index 4f5b3eef0..2f827a351 100644 --- a/src/UI/Settings/Profile/ProfileViewTemplate.hbs +++ b/src/UI/Settings/Profile/ProfileViewTemplate.hbs @@ -1,13 +1,13 @@ <div class="profile-item thingy"> - <div> - <h3 name="name"></h3> - </div> + <div> + <h3 name="name"></h3> + </div> - <div class="language"> - {{languageLabel}} - </div> + <div class="language"> + {{languageLabel}} + </div> - <ul class="allowed-qualities"> - {{allowedLabeler}} - </ul> -</div> \ No newline at end of file + <ul class="allowed-qualities"> + {{allowedLabeler}} + </ul> +</div> diff --git a/src/UI/Shared/Modal/ModalController.js b/src/UI/Shared/Modal/ModalController.js index 4392967df..f94f1d2e4 100644 --- a/src/UI/Shared/Modal/ModalController.js +++ b/src/UI/Shared/Modal/ModalController.js @@ -11,101 +11,108 @@ var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout'); var ManualImportLayout = require('../../ManualImport/ManualImportLayout'); var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout'); var MoviesDetailsLayout = require('../../Movies/Details/MoviesDetailsLayout'); +var EditFileView = require("../../Movies/Files/Edit/EditFileView"); module.exports = Marionette.AppRouter.extend({ - initialize : function() { - vent.on(vent.Commands.OpenModalCommand, this._openModal, this); - vent.on(vent.Commands.CloseModalCommand, this._closeModal, this); - vent.on(vent.Commands.OpenModal2Command, this._openModal2, this); - vent.on(vent.Commands.CloseModal2Command, this._closeModal2, this); - vent.on(vent.Commands.EditSeriesCommand, this._editSeries, this); - vent.on(vent.Commands.EditMovieCommand, this._editMovie, this); - vent.on(vent.Commands.DeleteSeriesCommand, this._deleteSeries, this); - vent.on(vent.Commands.ShowEpisodeDetails, this._showEpisode, this); - vent.on(vent.Commands.ShowMovieDetails, this._showMovie, this); - vent.on(vent.Commands.ShowHistoryDetails, this._showHistory, this); - vent.on(vent.Commands.ShowLogDetails, this._showLogDetails, this); - vent.on(vent.Commands.ShowRenamePreview, this._showRenamePreview, this); - vent.on(vent.Commands.ShowManualImport, this._showManualImport, this); - vent.on(vent.Commands.ShowFileBrowser, this._showFileBrowser, this); - vent.on(vent.Commands.CloseFileBrowser, this._closeFileBrowser, this); - }, - - _openModal : function(view) { - AppLayout.modalRegion.show(view); - }, - - _closeModal : function() { - AppLayout.modalRegion.closeModal(); - }, - - _openModal2 : function(view) { - AppLayout.modalRegion2.show(view); - }, - - _closeModal2 : function() { - AppLayout.modalRegion2.closeModal(); - }, - - _editSeries : function(options) { - var view = new EditSeriesView({ model : options.series }); - AppLayout.modalRegion.show(view); - }, - - _editMovie : function(options) { - var view = new EditMovieView({ model : options.movie }); - AppLayout.modalRegion.show(view); - }, - - _deleteSeries : function(options) { - var view = new DeleteSeriesView({ model : options.series }); - AppLayout.modalRegion.show(view); - }, - - _showEpisode : function(options) { - var view = new EpisodeDetailsLayout({ - model : options.episode, - hideSeriesLink : options.hideSeriesLink, - openingTab : options.openingTab - }); - AppLayout.modalRegion.show(view); - }, - - _showMovie : function(options) { - var view = new MoviesDetailsLayout({ - model : options.movie, - hideSeriesLink : options.hideSeriesLink, - openingTab : options.openingTab - }); - AppLayout.modalRegion.show(view); - }, - - _showHistory : function(options) { - var view = new HistoryDetailsLayout({ model : options.model }); - AppLayout.modalRegion.show(view); - }, - - _showLogDetails : function(options) { - var view = new LogDetailsView({ model : options.model }); - AppLayout.modalRegion.show(view); - }, - - _showRenamePreview : function(options) { - var view = new RenamePreviewLayout(options); - AppLayout.modalRegion.show(view); - }, - - _showManualImport : function(options) { - var view = new ManualImportLayout(options); - AppLayout.modalRegion.show(view); - }, - - _showFileBrowser : function(options) { - var view = new FileBrowserLayout(options); - AppLayout.modalRegion2.show(view); - }, - - _closeFileBrowser : function() { - AppLayout.modalRegion2.closeModal(); - } + initialize : function() { + vent.on(vent.Commands.OpenModalCommand, this._openModal, this); + vent.on(vent.Commands.CloseModalCommand, this._closeModal, this); + vent.on(vent.Commands.OpenModal2Command, this._openModal2, this); + vent.on(vent.Commands.CloseModal2Command, this._closeModal2, this); + vent.on(vent.Commands.EditSeriesCommand, this._editSeries, this); + vent.on(vent.Commands.EditMovieCommand, this._editMovie, this); + vent.on(vent.Commands.EditFileCommand, this._editFile, this); + vent.on(vent.Commands.DeleteSeriesCommand, this._deleteSeries, this); + vent.on(vent.Commands.ShowEpisodeDetails, this._showEpisode, this); + vent.on(vent.Commands.ShowMovieDetails, this._showMovie, this); + vent.on(vent.Commands.ShowHistoryDetails, this._showHistory, this); + vent.on(vent.Commands.ShowLogDetails, this._showLogDetails, this); + vent.on(vent.Commands.ShowRenamePreview, this._showRenamePreview, this); + vent.on(vent.Commands.ShowManualImport, this._showManualImport, this); + vent.on(vent.Commands.ShowFileBrowser, this._showFileBrowser, this); + vent.on(vent.Commands.CloseFileBrowser, this._closeFileBrowser, this); + }, + + _openModal : function(view) { + AppLayout.modalRegion.show(view); + }, + + _closeModal : function() { + AppLayout.modalRegion.closeModal(); + }, + + _openModal2 : function(view) { + AppLayout.modalRegion2.show(view); + }, + + _closeModal2 : function() { + AppLayout.modalRegion2.closeModal(); + }, + + _editSeries : function(options) { + var view = new EditSeriesView({ model : options.series }); + AppLayout.modalRegion.show(view); + }, + + _editMovie : function(options) { + var view = new EditMovieView({ model : options.movie }); + AppLayout.modalRegion.show(view); + }, + + _editFile : function(options) { + var view = new EditFileView({ model : options.file }); + AppLayout.modalRegion.show(view); + }, + + _deleteSeries : function(options) { + var view = new DeleteSeriesView({ model : options.series }); + AppLayout.modalRegion.show(view); + }, + + _showEpisode : function(options) { + var view = new EpisodeDetailsLayout({ + model : options.episode, + hideSeriesLink : options.hideSeriesLink, + openingTab : options.openingTab + }); + AppLayout.modalRegion.show(view); + }, + + _showMovie : function(options) { + var view = new MoviesDetailsLayout({ + model : options.movie, + hideSeriesLink : options.hideSeriesLink, + openingTab : options.openingTab + }); + AppLayout.modalRegion.show(view); + }, + + _showHistory : function(options) { + var view = new HistoryDetailsLayout({ model : options.model }); + AppLayout.modalRegion.show(view); + }, + + _showLogDetails : function(options) { + var view = new LogDetailsView({ model : options.model }); + AppLayout.modalRegion.show(view); + }, + + _showRenamePreview : function(options) { + var view = new RenamePreviewLayout(options); + AppLayout.modalRegion.show(view); + }, + + _showManualImport : function(options) { + var view = new ManualImportLayout(options); + AppLayout.modalRegion.show(view); + }, + + _showFileBrowser : function(options) { + var view = new FileBrowserLayout(options); + AppLayout.modalRegion2.show(view); + }, + + _closeFileBrowser : function() { + AppLayout.modalRegion2.closeModal(); + } }); diff --git a/src/UI/Wanted/Missing/MissingLayout.js b/src/UI/Wanted/Missing/MissingLayout.js index b3aa9e378..1a7e6711f 100644 --- a/src/UI/Wanted/Missing/MissingLayout.js +++ b/src/UI/Wanted/Missing/MissingLayout.js @@ -165,11 +165,11 @@ module.exports = Marionette.Layout.extend({ })); CommandController.bindToCommand({ element : this.$('.x-search-selected'), - command : { name : 'episodeSearch' } + command : { name : 'moviesSearch' } }); CommandController.bindToCommand({ element : this.$('.x-search-missing'), - command : { name : 'missingEpisodeSearch' } + command : { name : 'missingMoviesSearch' } }); }, @@ -187,20 +187,20 @@ module.exports = Marionette.Layout.extend({ if (selected.length === 0) { Messenger.show({ type : 'error', - message : 'No episodes selected' + message : 'No movies selected' }); return; } var ids = _.pluck(selected, 'id'); - CommandController.Execute('episodeSearch', { - name : 'episodeSearch', - episodeIds : ids + CommandController.Execute('moviesSearch', { + name : 'moviesSearch', + movieIds : ids }); }, _searchMissing : function() { if (window.confirm('Are you sure you want to search for {0} missing movies? '.format(this.collection.state.totalRecords) + 'One API request to each indexer will be used for each movie. ' + 'This cannot be stopped once started.')) { - CommandController.Execute('missingEpisodeSearch', { name : 'missingEpisodeSearch' }); + CommandController.Execute('missingMoviesSearch', { name : 'missingMoviesSearch' }); } }, _toggleMonitoredOfSelected : function() { @@ -209,7 +209,7 @@ module.exports = Marionette.Layout.extend({ if (selected.length === 0) { Messenger.show({ type : 'error', - message : 'No episodes selected' + message : 'No movies selected' }); return; } diff --git a/src/UI/index.html b/src/UI/index.html index 10e77adef..aa96ef4c9 100644 --- a/src/UI/index.html +++ b/src/UI/index.html @@ -7,11 +7,6 @@ <meta name="mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-capable" content="yes"/> - <!-- Chrome, Opera, and Firefox OS --> - <meta name="theme-color" content="#272727"/> - <!-- Windows Phone --> - <meta name="msapplication-navbutton-color" content="#272727"/> - <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/> <link href="/Content/bootstrap.css" rel='stylesheet' type='text/css'/> @@ -33,12 +28,24 @@ <link href="/Content/overrides.css" rel='stylesheet' type='text/css'/> <link href="/Content/info.css" rel='stylesheet' type='text/css'/> - <link rel="apple-touch-icon" href="/Content/Images/touch/57.png"/> - <link rel="apple-touch-icon" sizes="72x72" href="/Content/Images/touch/72.png"/> - <link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png"/> - <link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png"/> - <link rel="mask-icon" href="/Content/Images/safari/logo.svg" color="#35c5f4"> - <link rel="icon" type="image/ico" href="/Content/Images/favicon.ico"/> + <!-- Windows Phone --> + <meta name="msapplication-navbutton-color" content="#272727"/> + + <!-- Generated by http://realfavicongenerator.net/ --> + <link rel="apple-touch-icon" sizes="180x180" href="/Content/Images/favicon/apple-touch-icon.png"> + <link rel="icon" type="image/png" href="/Content/Images/favicon/favicon-32x32.png" sizes="32x32"> + <link rel="icon" type="image/png" href="/Content/Images/favicon/favicon-194x194.png" sizes="194x194"> + <link rel="icon" type="image/png" href="/Content/Images/favicon/android-chrome-192x192.png" sizes="192x192"> + <link rel="icon" type="image/png" href="/Content/Images/favicon/favicon-16x16.png" sizes="16x16"> + <link rel="manifest" href="/Content/Images/favicon/manifest.json"> + <link rel="mask-icon" href="/Content/Images/favicon/safari-pinned-tab.svg" color="#ffc230"> + <link rel="shortcut icon" href="/Content/Images/favicon/favicon.ico"> + <meta name="apple-mobile-web-app-title" content="Radarr"> + <meta name="application-name" content="Radarr"> + <meta name="msapplication-TileColor" content="#272727"> + <meta name="msapplication-TileImage" content="/Content/Images/favicon/mstile-144x144.png"> + <meta name="msapplication-config" content="/Content/Images/favicon/browserconfig.xml"> + <meta name="theme-color" content="#272727"> <link rel="alternate" type="text/calendar" title="iCalendar feed for Radarr" href="/feed/calendar/NzbDrone.ics"/> </head> diff --git a/src/UI/login.html b/src/UI/login.html index e956bbd4c..a73b84b38 100644 --- a/src/UI/login.html +++ b/src/UI/login.html @@ -11,11 +11,24 @@ <link href="/Content/bootstrap.css" rel='stylesheet' type='text/css'/> <link href="/Content/theme.css" rel='stylesheet' type='text/css'/> - <link rel="apple-touch-icon" href="/Content/Images/touch/57.png"/> - <link rel="apple-touch-icon" sizes="72x72" href="/Content/Images/touch/72.png"/> - <link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png"/> - <link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png"/> - <link rel="icon" type="image/ico" href="/Content/Images/favicon.ico"/> + <!-- Windows Phone --> + <meta name="msapplication-navbutton-color" content="#272727"/> + + <!-- Generated by http://realfavicongenerator.net/ --> + <link rel="apple-touch-icon" sizes="180x180" href="/Content/Images/favicon/apple-touch-icon.png"> + <link rel="icon" type="image/png" href="/Content/Images/favicon/favicon-32x32.png" sizes="32x32"> + <link rel="icon" type="image/png" href="/Content/Images/favicon/favicon-194x194.png" sizes="194x194"> + <link rel="icon" type="image/png" href="/Content/Images/favicon/android-chrome-192x192.png" sizes="192x192"> + <link rel="icon" type="image/png" href="/Content/Images/favicon/favicon-16x16.png" sizes="16x16"> + <link rel="manifest" href="/Content/Images/favicon/manifest.json"> + <link rel="mask-icon" href="/Content/Images/favicon/safari-pinned-tab.svg" color="#ffc230"> + <link rel="shortcut icon" href="/Content/Images/favicon/favicon.ico"> + <meta name="apple-mobile-web-app-title" content="Radarr"> + <meta name="application-name" content="Radarr"> + <meta name="msapplication-TileColor" content="#272727"> + <meta name="msapplication-TileImage" content="/Content/Images/favicon/mstile-144x144.png"> + <meta name="msapplication-config" content="/Content/Images/favicon/browserconfig.xml"> + <meta name="theme-color" content="#272727"> </head> <body> <div class="container"> diff --git a/src/UI/vent.js b/src/UI/vent.js index 1962f9d22..a6a7be318 100644 --- a/src/UI/vent.js +++ b/src/UI/vent.js @@ -3,40 +3,41 @@ var Wreqr = require('./JsLibraries/backbone.wreqr'); var vent = new Wreqr.EventAggregator(); vent.Events = { - SeriesAdded : 'series:added', - SeriesDeleted : 'series:deleted', - CommandComplete : 'command:complete', - ServerUpdated : 'server:updated', - EpisodeFileDeleted : 'episodefile:deleted' + SeriesAdded : 'series:added', + SeriesDeleted : 'series:deleted', + CommandComplete : 'command:complete', + ServerUpdated : 'server:updated', + EpisodeFileDeleted : 'episodefile:deleted' }; vent.Commands = { - EditSeriesCommand : 'EditSeriesCommand', - EditMovieCommand : 'EditMovieCommand', - DeleteSeriesCommand : 'DeleteSeriesCommand', - OpenModalCommand : 'OpenModalCommand', - CloseModalCommand : 'CloseModalCommand', - OpenModal2Command : 'OpenModal2Command', - CloseModal2Command : 'CloseModal2Command', - ShowEpisodeDetails : 'ShowEpisodeDetails', - ShowMovieDetails : 'ShowMovieDetails', - ShowHistoryDetails : 'ShowHistoryDetails', - ShowLogDetails : 'ShowLogDetails', - SaveSettings : 'saveSettings', - ShowLogFile : 'showLogFile', - ShowRenamePreview : 'showRenamePreview', - ShowManualImport : 'showManualImport', - ShowFileBrowser : 'showFileBrowser', - CloseFileBrowser : 'closeFileBrowser', - OpenControlPanelCommand : 'OpenControlPanelCommand', - CloseControlPanelCommand : 'CloseControlPanelCommand', - ShowExistingCommand : 'ShowExistingCommand' + EditSeriesCommand : 'EditSeriesCommand', + EditMovieCommand : 'EditMovieCommand', + EditFileCommand : "EditFileCommand", + DeleteSeriesCommand : 'DeleteSeriesCommand', + OpenModalCommand : 'OpenModalCommand', + CloseModalCommand : 'CloseModalCommand', + OpenModal2Command : 'OpenModal2Command', + CloseModal2Command : 'CloseModal2Command', + ShowEpisodeDetails : 'ShowEpisodeDetails', + ShowMovieDetails : 'ShowMovieDetails', + ShowHistoryDetails : 'ShowHistoryDetails', + ShowLogDetails : 'ShowLogDetails', + SaveSettings : 'saveSettings', + ShowLogFile : 'showLogFile', + ShowRenamePreview : 'showRenamePreview', + ShowManualImport : 'showManualImport', + ShowFileBrowser : 'showFileBrowser', + CloseFileBrowser : 'closeFileBrowser', + OpenControlPanelCommand : 'OpenControlPanelCommand', + CloseControlPanelCommand : 'CloseControlPanelCommand', + ShowExistingCommand : 'ShowExistingCommand' }; vent.Hotkeys = { - NavbarSearch : 'navbar:search', - SaveSettings : 'settings:save', - ShowHotkeys : 'hotkeys:show' + NavbarSearch : 'navbar:search', + SaveSettings : 'settings:save', + ShowHotkeys : 'hotkeys:show' }; module.exports = vent;