Min availability (#816)

* availability specification to prevent downloading titles before their
release

* pull inCinamas status out of js handlebars and set it in SkyHook

* minor code improvement

* add incinemas to footer

* typo

* another typo

* release date handling

* still print cinema date out for announced titles

* revert a minor change from before since its unnecessary

* early implementation of minimumAvailability --> when does radarr
consider a movie "available" should be specified by user
default to "Physical release?"

this isn't functional yet, but it has a skeleton + comments. I dont
know how to have the minimumavailability attribute default to something
or to have it actually populate the Movieinfo object
could use some help with that

* adding another comment for another location that might need to be
updated to handle minimumAvailability

* the implementation is now function;
however, i still need to specify default values for minimumAvailability

* missed these changes in the previous commit

* fix rounded corners on new field in editmovie dialog

* add minimum availability specification to the addMovie page

* minor adjustment from last commit

* handle the case where minimumavailability has never yet been set
nullstring.. if its never been set, default to Released (Physical/Web)
represented by integer value  3

* minAvailability specification on NetImport lists

* add support for min availability to the movie editor

* use enum MovieStatusType values directly

makes for cleaner code

* need to fix up the migration forgot in last commit

* cleaning up code, proper case

* erroneous code added in this feature needed to be removed

* update "Wanted" page to take into account minimumAvailability

* implement preDB minimumAvailability as default.. behaves same as
Physical/Web a few comments with TODO for when preDB is implemented

* minor adjustment

* remove some unused code (leave commented for now)

* improve code for minimumavailability and add option for
availabilitydelay (but doesnt do anything yet)

* improve isAvailable method

* clean up and fix helper info on indexer configuration page

* add buttons in Wanted/Missing view
This commit is contained in:
geogolem 2017-02-23 00:03:48 -05:00 committed by Devin Buhl
parent 731e607666
commit 140a220340
38 changed files with 446 additions and 81 deletions

View File

@ -8,6 +8,7 @@ namespace NzbDrone.Api.Config
public int MinimumAge { get; set; }
public int Retention { get; set; }
public int RssSyncInterval { get; set; }
public int AvailabilityDelay { get; set; }
}
public static class IndexerConfigResourceMapper
@ -19,6 +20,7 @@ namespace NzbDrone.Api.Config
MinimumAge = model.MinimumAge,
Retention = model.Retention,
RssSyncInterval = model.RssSyncInterval,
AvailabilityDelay = model.AvailabilityDelay,
};
}
}

View File

@ -22,6 +22,7 @@ namespace NzbDrone.Api.NetImport
resource.ProfileId = definition.ProfileId;
resource.RootFolderPath = definition.RootFolderPath;
resource.ShouldMonitor = definition.ShouldMonitor;
resource.MinimumAvailability = definition.MinimumAvailability;
}
protected override void MapToModel(NetImportDefinition definition, NetImportResource resource)
@ -33,6 +34,7 @@ namespace NzbDrone.Api.NetImport
definition.ProfileId = resource.ProfileId;
definition.RootFolderPath = resource.RootFolderPath;
definition.ShouldMonitor = resource.ShouldMonitor;
definition.MinimumAvailability = resource.MinimumAvailability;
}
protected override void Validate(NetImportDefinition definition, bool includeWarnings)
@ -41,4 +43,4 @@ namespace NzbDrone.Api.NetImport
base.Validate(definition, includeWarnings);
}
}
}
}

View File

@ -1,4 +1,5 @@
using NzbDrone.Core.NetImport;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.NetImport
{
@ -9,5 +10,6 @@ namespace NzbDrone.Api.NetImport
public bool ShouldMonitor { get; set; }
public string RootFolderPath { get; set; }
public int ProfileId { get; set; }
public MovieStatusType MinimumAvailability { get; set; }
}
}
}

View File

@ -43,6 +43,9 @@ namespace NzbDrone.Api.Movie
//Editing Only
public bool Monitored { get; set; }
public MovieStatusType MinimumAvailability { get; set; }
public bool IsAvailable { get; set; }
public int Runtime { get; set; }
public DateTime? LastInfoSync { get; set; }
public string CleanTitle { get; set; }
@ -129,6 +132,9 @@ namespace NzbDrone.Api.Movie
ProfileId = model.ProfileId,
Monitored = model.Monitored,
MinimumAvailability = model.MinimumAvailability,
IsAvailable = model.IsAvailable(),
SizeOnDisk = size,
@ -181,7 +187,8 @@ namespace NzbDrone.Api.Movie
ProfileId = resource.ProfileId,
Monitored = resource.Monitored,
MinimumAvailability = resource.MinimumAvailability,
Runtime = resource.Runtime,
LastInfoSync = resource.LastInfoSync,
CleanTitle = resource.CleanTitle,
@ -210,7 +217,8 @@ namespace NzbDrone.Api.Movie
movie.ProfileId = resource.ProfileId;
movie.Monitored = resource.Monitored;
movie.MinimumAvailability = resource.MinimumAvailability;
movie.RootFolderPath = resource.RootFolderPath;
movie.Tags = resource.Tags;
movie.AddOptions = resource.AddOptions;

View File

@ -34,10 +34,31 @@ namespace NzbDrone.Api.Wanted
{
pagingSpec.FilterExpression = v => v.Monitored == false;
}
else
else if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "true")
{
pagingSpec.FilterExpression = v => v.Monitored == true;
}
else if (pagingResource.FilterKey == "moviestatus" && pagingResource.FilterValue == "available")
{
//TODO: might need to handle PreDB here
pagingSpec.FilterExpression = v =>
(v.MinimumAvailability == MovieStatusType.Released && v.Status >= MovieStatusType.Released) ||
(v.MinimumAvailability == MovieStatusType.InCinemas && v.Status >= MovieStatusType.InCinemas) ||
(v.MinimumAvailability == MovieStatusType.Announced && v.Status >= MovieStatusType.Announced) ||
(v.MinimumAvailability == MovieStatusType.PreDB && v.Status >= MovieStatusType.Released);
}
else if (pagingResource.FilterKey == "moviestatus" && pagingResource.FilterValue == "announced")
{
pagingSpec.FilterExpression = v => v.Status == MovieStatusType.Announced;
}
else if (pagingResource.FilterKey == "moviestatus" && pagingResource.FilterValue == "incinemas")
{
pagingSpec.FilterExpression = v => v.Status == MovieStatusType.InCinemas;
}
else if (pagingResource.FilterKey == "moviestatus" && pagingResource.FilterValue == "released")
{
pagingSpec.FilterExpression = v => v.Status == MovieStatusType.Released;
}
var resource = ApplyToPage(_movieService.MoviesWithoutFiles, pagingSpec, v => MapToResource(v, true));

View File

@ -105,6 +105,12 @@ namespace NzbDrone.Core.Configuration
set { SetValue("RssSyncInterval", value); }
}
public int AvailabilityDelay
{
get { return GetValueInt("AvailabilityDelay",0); }
set { SetValue("AvailabilityDelay", value); }
}
public int NetImportSyncInterval
{
get { return GetValueInt("NetImportSyncInterval", 60); }

View File

@ -46,6 +46,8 @@ namespace NzbDrone.Core.Configuration
int RssSyncInterval { get; set; }
int MinimumAge { get; set; }
int AvailabilityDelay { get; set; }
int NetImportSyncInterval { get; set; }
//UI

View File

@ -0,0 +1,23 @@
using FluentMigrator;
//using FluentMigrator.Expressions;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(133)]
public class add_minimumavailability : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
if (!this.Schema.Schema("dbo").Table("NetImport").Column("MinimumAvailability").Exists())
{
Alter.Table("NetImport").AddColumn("MinimumAvailability").AsInt32().WithDefaultValue(MovieStatusType.PreDB);
}
if (!this.Schema.Schema("dbo").Table("Movies").Column("MinimumAvailability").Exists())
{
Alter.Table("Movies").AddColumn("MinimumAvailability").AsInt32().WithDefaultValue(MovieStatusType.PreDB);
}
}
}
}

View File

@ -0,0 +1,69 @@
using System;
using System.Linq;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
public class AvailabilitySpecification : IDecisionEngineSpecification
{
private readonly IConfigService _settingsService;
private readonly Logger _logger;
public AvailabilitySpecification(IConfigService settingsService, Logger logger)
{
_settingsService = settingsService;
_logger = logger;
}
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria != null)
{
if (searchCriteria.UserInvokedSearch)
{
_logger.Debug("Skipping availability check during search");
return Decision.Accept();
}
}
if (!subject.Movie.IsAvailable(_settingsService.AvailabilityDelay))
{
return Decision.Reject("Movie {0} will only be considered available {1} days after {2}", subject.Movie, _settingsService.AvailabilityDelay, subject.Movie.MinimumAvailability.ToString());
}
return Decision.Accept();
}
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria != null)
{
if (!searchCriteria.MonitoredEpisodesOnly)
{
_logger.Debug("Skipping availability check during search");
return Decision.Accept();
}
}
/*if (subject.Series.Status != MovieStatusType.Released)
{
_logger.Debug("{0} is present in the DB but not yet available. skipping.", subject.Series);
return Decision.Reject("Series is not yet available");
}
/*var monitoredCount = subject.Episodes.Count(episode => episode.Monitored);
if (monitoredCount == subject.Episodes.Count)
{
return Decision.Accept();
}
_logger.Debug("Only {0}/{1} episodes are monitored. skipping.", monitoredCount, subject.Episodes.Count);*/
return Decision.Reject("Episode is not yet available");
}
}
}

View File

@ -185,14 +185,71 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
movie.Genres.Add(genre.name);
}
if (resource.status == "Released")
//this is the way it should be handled
//but unfortunately it seems
//tmdb lacks alot of release date info
//omdbapi is actually quite good for this info
//except omdbapi has been having problems recently
//so i will just leave this in as a comment
//and use the 3 month logic that we were using before
/*var now = DateTime.Now;
if (now < movie.InCinemas)
movie.Status = MovieStatusType.Announced;
if (now >= movie.InCinemas)
movie.Status = MovieStatusType.InCinemas;
if (now >= movie.PhysicalRelease)
movie.Status = MovieStatusType.Released;
*/
var now = DateTime.Now;
//handle the case when we have both theatrical and physical release dates
if (movie.InCinemas.HasValue && movie.PhysicalRelease.HasValue)
{
if (now < movie.InCinemas)
movie.Status = MovieStatusType.Announced;
else if (now >= movie.InCinemas)
movie.Status = MovieStatusType.InCinemas;
if (now >= movie.PhysicalRelease)
movie.Status = MovieStatusType.Released;
}
//handle the case when we have theatrical release dates but we dont know the physical release date
else if (movie.InCinemas.HasValue && (now >= movie.InCinemas))
{
movie.Status = MovieStatusType.InCinemas;
}
//handle the case where we only have a physical release date
else if (movie.PhysicalRelease.HasValue && (now >= movie.PhysicalRelease))
{
movie.Status = MovieStatusType.Released;
}
//otherwise the title has only been announced
else
{
movie.Status = MovieStatusType.Announced;
}
//since TMDB lacks alot of information lets assume that stuff is released if its been in cinemas for longer than 3 months.
if (!movie.PhysicalRelease.HasValue && (movie.Status == MovieStatusType.InCinemas) && (((DateTime.Now).Subtract(movie.InCinemas.Value)).TotalSeconds > 60*60*24*30*3))
{
movie.Status = MovieStatusType.Released;
}
//this matches with the old behavior before the creation of the MovieStatusType.InCinemas
/*if (resource.status == "Released")
{
if (movie.InCinemas.HasValue && (((DateTime.Now).Subtract(movie.InCinemas.Value)).TotalSeconds <= 60 * 60 * 24 * 30 * 3))
{
movie.Status = MovieStatusType.InCinemas;
}
else
{
movie.Status = MovieStatusType.Released;
}
}
else
{
movie.Status = MovieStatusType.Announced;
}*/
if (resource.videos != null)
{
@ -650,6 +707,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
newMovie.ProfileId = movie.ProfileId;
newMovie.Monitored = movie.Monitored;
newMovie.MovieFile = movie.MovieFile;
newMovie.MinimumAvailability = movie.MinimumAvailability;
return newMovie;
}

View File

@ -112,6 +112,7 @@ namespace NzbDrone.Core.NetImport
m.RootFolderPath = ((NetImportDefinition) Definition).RootFolderPath;
m.ProfileId = ((NetImportDefinition) Definition).ProfileId;
m.Monitored = ((NetImportDefinition) Definition).ShouldMonitor;
m.MinimumAvailability = ((NetImportDefinition) Definition).MinimumAvailability;
return m;
}).ToList();
}

View File

@ -44,6 +44,7 @@ namespace NzbDrone.Core.NetImport
Enabled = config.Validate().IsValid && Enabled,
EnableAuto = true,
ProfileId = 1,
MinimumAvailability = MovieStatusType.PreDB,
Implementation = GetType().Name,
Settings = config
};

View File

@ -1,6 +1,7 @@
using Marr.Data;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport
{
@ -9,6 +10,7 @@ namespace NzbDrone.Core.NetImport
public bool Enabled { get; set; }
public bool EnableAuto { get; set; }
public bool ShouldMonitor { get; set; }
public MovieStatusType MinimumAvailability { get; set; }
public int ProfileId { get; set; }
public LazyLoaded<Profile> Profile { get; set; }
public string RootFolderPath { get; set; }

View File

@ -128,6 +128,7 @@
<Compile Include="Datastore\Migration\128_remove_kickass.cs" />
<Compile Include="Datastore\Migration\130_remove_wombles_kickass.cs" />
<Compile Include="Datastore\Migration\132_rename_torrent_downloadstation.cs" />
<Compile Include="Datastore\Migration\133_add_minimumavailability.cs" />
<Compile Include="NetImport\TMDb\TMDbLanguageCodes.cs" />
<Compile Include="NetImport\TMDb\TMDbSettings.cs" />
<Compile Include="NetImport\TMDb\TMDbListType.cs" />
@ -394,6 +395,7 @@
<Compile Include="DecisionEngine\Specifications\RssSync\DelaySpecification.cs" />
<Compile Include="DecisionEngine\Specifications\RssSync\HistorySpecification.cs" />
<Compile Include="DecisionEngine\Specifications\RssSync\MonitoredEpisodeSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\RssSync\AvailabilitySpecification.cs" />
<Compile Include="DecisionEngine\Specifications\RssSync\ProperSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\Search\DailyEpisodeMatchSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\Search\EpisodeRequestedSpecification.cs" />
@ -1341,4 +1343,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@ -26,6 +26,7 @@ namespace NzbDrone.Core.Tv
public MovieStatusType Status { get; set; }
public string Overview { get; set; }
public bool Monitored { get; set; }
public MovieStatusType MinimumAvailability { get; set; }
public int ProfileId { get; set; }
public DateTime? LastInfoSync { get; set; }
public int Runtime { get; set; }
@ -53,6 +54,35 @@ namespace NzbDrone.Core.Tv
public bool HasFile => MovieFileId > 0;
public bool IsAvailable(int delay = 0)
{
//the below line is what was used before delay was implemented, could still be used for cases when delay==0
//return (Status >= MinimumAvailability || (MinimumAvailability == MovieStatusType.PreDB && Status >= MovieStatusType.Released));
//This more complex sequence handles the delay
DateTime MinimumAvailabilityDate;
switch (MinimumAvailability)
{
case MovieStatusType.TBA:
case MovieStatusType.Announced:
MinimumAvailabilityDate = DateTime.MinValue;
break;
case MovieStatusType.InCinemas:
if (InCinemas.HasValue)
MinimumAvailabilityDate = InCinemas.Value;
else
MinimumAvailabilityDate = DateTime.MaxValue;
break;
//TODO: might need to handle PreDB but for now treat the same as Released
case MovieStatusType.Released:
case MovieStatusType.PreDB:
default:
MinimumAvailabilityDate = PhysicalRelease.HasValue ? PhysicalRelease.Value : (InCinemas.HasValue ? InCinemas.Value.AddDays(90) : DateTime.MaxValue);
break;
}
return DateTime.Now >= MinimumAvailabilityDate.AddDays(delay);
}
public override string ToString()
{
return string.Format("[{0}][{1}]", ImdbId, Title.NullSafe());
@ -63,4 +93,4 @@ namespace NzbDrone.Core.Tv
{
public bool SearchForMovie { get; set; }
}
}
}

View File

@ -196,7 +196,6 @@ namespace NzbDrone.Core.Tv
{
return Query.Where(pagingSpec.FilterExpression)
.AndWhere(m => m.MovieFileId == 0)
.AndWhere(m => m.Status == MovieStatusType.Released)
.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
.Skip(pagingSpec.PagingOffset())
.Take(pagingSpec.PageSize);
@ -242,4 +241,4 @@ namespace NzbDrone.Core.Tv
return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault();
}
}
}
}

View File

@ -2,8 +2,10 @@
{
public enum MovieStatusType
{
TBA = 0, //Nothing yet announced, only rumors, but still IMDb page
Announced = 1, //AirDate is announced
Released = 2 //Has at least one PreDB release
TBA = 0, //Nothing yet announced, only rumors, but still IMDb page (this might not be used)
Announced = 1, //Movie is announced but Cinema date is in the future or unknown
InCinemas = 2, //Been in Cinemas for less than 3 months (since TMDB lacks complete information)
Released = 3, //Physical or Web Release or been in cinemas for > 3 months (since TMDB lacks complete information)
PreDB = 4 //this is only used for MinimumAvailability. Movie items should never be in this state.
}
}

View File

@ -0,0 +1,10 @@
<dl class="minimumavailability-tooltip-contents">
<dt>Announced</dt>
<dd>Consider the movie available after it has been announced</dd>
<dt>In Cinemas</dt>
<dd>Consider the movie available once it is In Cinemas</dd>
<dt>Physical/Web</dt>
<dd>Consider the movie available after Physical/Web release</dd>
<dt>PreDB</dt>
<dd>Consider the movie available if preDB contains at least one entry</dd>
</dl>

View File

@ -22,6 +22,8 @@ var view = Marionette.ItemView.extend({
rootFolder : '.x-root-folder',
seasonFolder : '.x-season-folder',
monitor : '.x-monitor',
minimumAvailability : '.x-minimumavailability',
minimumAvailabilityTooltip : '.x-minimumavailability-tooltip',
monitorTooltip : '.x-monitor-tooltip',
addButton : '.x-add',
addSearchButton : '.x-add-search',
@ -70,6 +72,7 @@ var view = Marionette.ItemView.extend({
this.ui.seasonFolder.prop('checked', useSeasonFolder);
this.ui.monitor.val(defaultMonitorEpisodes);
this.ui.minimumAvailability.val("preDB");
//TODO: make this work via onRender, FM?
//works with onShow, but stops working after the first render
@ -88,6 +91,18 @@ var view = Marionette.ItemView.extend({
placement : 'right',
container : this.$el
});
this.templateFunction = Marionette.TemplateCache.get('AddMovies/MinimumAvailabilityTooltipTemplate');
var content1 = this.templateFunction();
this.ui.minimumAvailabilityTooltip.popover({
content : content1,
html :true,
trigger : 'hover',
title : 'When to Consider a Movie Available',
placement : 'right',
container : this.$el
});
},
_configureTemplateHelpers : function() {
@ -168,6 +183,7 @@ var view = Marionette.ItemView.extend({
var profile = this.ui.profile.val();
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
var monitor = this.ui.monitor.val();
var minAvail = this.ui.minimumAvailability.val();
var options = this._getAddMoviesOptions();
options.searchForMovie = searchForMovie;
@ -177,6 +193,7 @@ var view = Marionette.ItemView.extend({
profileId : profile,
rootFolderPath : rootFolderPath,
addOptions : options,
minimumAvailability : minAvail,
monitored : (monitor === 'all' ? true : false)
}, { silent : true });

View File

@ -47,6 +47,16 @@
</select>
</div>
<div class="form-group col-md-2">
<label>Min Availability <i class="icon-sonarr-form-info minimumavailability-tooltip x-minimumavailability-tooltip"></i></label>
<select class="form-control col-md-2 x-minimumavailability">
<option value="announced">Announced</option>
<option value="inCinemas">In Cinemas</option>
<option value="released">Physical/Web</option>
<option value="preDB">PreDB</option>
</select>
</div>
<div class="form-group col-md-2">
<label>Profile</label>
{{> ProfileSelectionPartial profiles}}

View File

@ -117,6 +117,9 @@
.monitor-tooltip {
margin-left : 5px;
}
.minimumavailability-tooltip {
margin-left : 5px;
}
}
.loading-folders {
@ -136,6 +139,13 @@
padding-bottom : 8px;
}
}
.minimumavailability-tooltip-contents {
padding-bottom : 0px;
dd {
padding-bottom :8px;
}
}
}
li.add-new {

View File

@ -17,12 +17,7 @@ module.exports = NzbDroneCell.extend({
this._setStatusWeight(3);
}
if (numOfMonths > 3) {
this.$el.html('<i class="icon-sonarr-movie-released grid-icon" title="Released"></i>');//TODO: Update for PreDB.me
this._setStatusWeight(2);
}
if (numOfMonths < 3) {
if (status === 'inCinemas') {
this.$el.html('<i class="icon-sonarr-movie-cinemas grid-icon" title="In Cinemas"></i>');
this._setStatusWeight(2);
}
@ -32,11 +27,6 @@ module.exports = NzbDroneCell.extend({
this._setStatusWeight(1);
}
// else if (!monitored) {
// this.$el.html('<i class="icon-sonarr-series-unmonitored grid-icon" title="Not Monitored"></i>');
// this._setStatusWeight(0);
// }
return this;
},

View File

@ -18,12 +18,7 @@ module.exports = NzbDroneCell.extend({
this._setStatusWeight(3);
}
if (numOfMonths > 3) {
this.$el.html('<div class="released-banner"><i class="icon-sonarr-movie-released grid-icon" title=""></i>&nbsp;Released</div>');//TODO: Update for PreDB.me
this._setStatusWeight(2);
}
if (numOfMonths < 3) {
if (status ==='inCinemas') {
this.$el.html('<div class="cinemas-banner"><i class="icon-sonarr-movie-cinemas grid-icon" title=""></i>&nbsp;In Cinemas</div>');
this._setStatusWeight(2);
}

View File

@ -60,6 +60,10 @@
.fa-icon-color(@brand-warning);
}
.icon-sonarr-available {
.fa-icon-content(@fa-var-clock-o);
}
.icon-sonarr-edit {
.fa-icon-content(@fa-var-wrench);
}

View File

@ -54,6 +54,9 @@ Handlebars.registerHelper('tmdbUrl', function() {
Handlebars.registerHelper('youTubeTrailerUrl', function() {
return 'https://www.youtube.com/watch?v=' + this.youTubeTrailerId;
});
Handlebars.registerHelper('allFlicksUrl', function() {
return this.allFlicksUrl;
});
Handlebars.registerHelper('homepage', function() {
return this.website;
@ -73,25 +76,21 @@ Handlebars.registerHelper('alternativeTitlesString', function() {
Handlebars.registerHelper('GetStatus', function() {
var monitored = this.monitored;
var status = this.status;
var inCinemas = this.inCinemas;
var date = new Date(inCinemas);
var timeSince = new Date().getTime() - date.getTime();
var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30;
//var inCinemas = this.inCinemas;
//var date = new Date(inCinemas);
//var timeSince = new Date().getTime() - date.getTime();
//var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30;
if (status === "announced") {
return new Handlebars.SafeString('<i class="icon-sonarr-movie-announced grid-icon" title=""></i>&nbsp;Announced');
}
if (numOfMonths < 3) {
if (status ==="inCinemas") {
return new Handlebars.SafeString('<i class="icon-sonarr-movie-cinemas grid-icon" title=""></i>&nbsp;In Cinemas');
}
if (numOfMonths > 3) {
return new Handlebars.SafeString('<i class="icon-sonarr-movie-released grid-icon" title=""></i>&nbsp;Released');//TODO: Update for PreDB.me
}
if (status === 'released') {
return new Handlebars.SafeString('<i class="icon-sonarr-movie-released grid-icon" title=""></i>&nbsp;Released');
}
@ -104,30 +103,22 @@ Handlebars.registerHelper('GetStatus', function() {
Handlebars.registerHelper('GetBannerStatus', function() {
var monitored = this.monitored;
var status = this.status;
var inCinemas = this.inCinemas;
var date = new Date(inCinemas);
var timeSince = new Date().getTime() - date.getTime();
var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30;
//var inCinemas = this.inCinemas;
//var date = new Date(inCinemas);
//var timeSince = new Date().getTime() - date.getTime();
//var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30;
if (status === "announced") {
return new Handlebars.SafeString('<div class="announced-banner"><i class="icon-sonarr-movie-announced grid-icon" title=""></i>&nbsp;Announced</div>');
}
if (numOfMonths < 3) {
if (status === "inCinemas") {
return new Handlebars.SafeString('<div class="cinemas-banner"><i class="icon-sonarr-movie-cinemas grid-icon" title=""></i>&nbsp;In Cinemas</div>');
}
if (status === 'released') {
return new Handlebars.SafeString('<div class="released-banner"><i class="icon-sonarr-movie-released grid-icon" title=""></i>&nbsp;Released</div>');
}
if (numOfMonths > 3) {
return new Handlebars.SafeString('<div class="released-banner"><i class="icon-sonarr-movie-released grid-icon" title=""></i>&nbsp;Released</div>');//TODO: Update for PreDB.me
}
else if (!monitored) {
return new Handlebars.SafeString('<div class="announced-banner"><i class="icon-sonarr-series-unmonitored grid-icon" title=""></i>&nbsp;Not Monitored</div>');
}
@ -145,7 +136,7 @@ Handlebars.registerHelper('DownloadedStatusColor', function() {
return "success";
}
if (this.status != "released") {
if (!this.isAvailable){
return "primary";
}
@ -160,8 +151,6 @@ Handlebars.registerHelper('DownloadedStatus', function() {
if (!this.monitored) {
return "Not Monitored";
}
return "Missing";
});

View File

@ -17,12 +17,11 @@
{{/if}}
<span class="label label-info">{{Bytes sizeOnDisk}}</span>
{{#if_eq status compare="released"}}
{{#if_eq status compare="announced"}}
<span class="label label-default">{{inCinemas}}</span>
{{else}}
<span class="label label-info">{{inCinemas}}</span>
{{else}}
<span class="label label-default">Announced</span>
{{/if_eq}}
{{/if_eq}}
<span class="label label-{{DownloadedStatusColor}}" title="{{DownloadedQuality}}">{{DownloadedStatus}}</span>
</div>
<div class="col-md-4">
@ -40,6 +39,10 @@
{{#if youTubeTrailerId}}
<a href="{{youTubeTrailerUrl}}" class="label label-info">Trailer</a>
{{/if}}
{{#if allFlicksUrl}}
<a href="{{allFlicksUrl}}" class="label label-info">AllFlicks</a>
{{/if}}
</span>
</div>
</div>

View File

@ -32,6 +32,20 @@
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Minimum Availability</label>
<div class="col-sm-1 col-sm-push-4 help-inline">
<i class="icon-sonarr-form-info" title="When the movie is considered Available"/>
</div>
<div class="col-sm-4 col-sm-pull-1">
<select class="form-control x-minimumavailability" name="minimumAvailability">
<option value="announced">Announced</option>
<option value="inCinemas">In Cinemas</option>
<option value="released">Physical/Web</option>
<option value="preDB">PreDB</option>
</select>
</div>
</div>
<!--<div class="form-group">
<label class="col-sm-4 control-label">Use Season Folder</label>

View File

@ -13,6 +13,7 @@ module.exports = Marionette.ItemView.extend({
ui : {
monitored : '.x-monitored',
profile : '.x-profiles',
minimumAvailability : '.x-minimumavailability',
seasonFolder : '.x-season-folder',
rootFolder : '.x-root-folder',
selectedCount : '.x-selected-count',
@ -53,6 +54,7 @@ module.exports = Marionette.ItemView.extend({
var selected = this.editorGrid.getSelectedModels();
var monitored = this.ui.monitored.val();
var minAvail = this.ui.minimumAvailability.val();
var profile = this.ui.profile.val();
var seasonFolder = this.ui.seasonFolder.val();
var rootFolder = this.ui.rootFolder.val();
@ -64,6 +66,10 @@ module.exports = Marionette.ItemView.extend({
model.set('monitored', false);
}
if (minAvail !=='noChange') {
model.set('minimumAvailability', minAvail);
}
if (profile !== 'noChange') {
model.set('profileId', parseInt(profile, 10));
}
@ -123,4 +129,4 @@ module.exports = Marionette.ItemView.extend({
vent.trigger(vent.Commands.OpenModalCommand, updateFilesMoviesView);
}
});
});

View File

@ -10,6 +10,18 @@
</select>
</div>
<div class="form-group col-md-2">
<label>Min Availability</label>
<select class="form-control x-action x-minimumavailability">
<option value="noChange">No change</option>
<option value="announced">Announced</option>
<option value="inCinemas">In Cinemas</option>
<option value="released">Physical/Web</option>
<option value="preDB">PreDB</option>
</select>
</div>
<div class="form-group col-md-2">
<label>Profile</label>

View File

@ -1,7 +1,7 @@
<div class="row">
<div class="series-legend legend col-xs-6 col-sm-4">
<ul class='legend-labels'>
<li><span class="progress-bar"></span>Missing, but not yet available.</li>
<li><span class="progress-bar"></span>Missing, but not yet considered availabile</li>
<li><span class="progress-bar-success"></span>Downloaded and imported.</li>
<li><span class="progress-bar-danger"></span>Missing and monitored.</li>
<li><span class="progress-bar-warning"></span>Missing, but not monitored.</li>
@ -17,6 +17,9 @@
<dt>Released</dt>
<dd>{{released}}</dd>
<dt>In Cinemas</dt>
<dd>{{incinemas}}</dd>
<dt>Announced</dt>
<dd>{{announced}}</dd>
</dl>

View File

@ -328,6 +328,7 @@ module.exports = Marionette.Layout.extend({
var episodeFiles = 0;
var announced = 0;
var released = 0;
var incinemas = 0;
var monitored = 0;
_.each(MoviesCollection.models, function(model) {
@ -336,6 +337,8 @@ module.exports = Marionette.Layout.extend({
if (model.get('status').toLowerCase() === 'released') {
released++;
} else if (model.get('status').toLowerCase() === 'incinemas') {
incinemas++;
} else {
announced++;
}
@ -348,6 +351,7 @@ module.exports = Marionette.Layout.extend({
footerModel.set({
series : series,
released : released,
incinemas : incinemas,
announced : announced,
monitored : monitored,
unmonitored : series - monitored,

View File

@ -14,16 +14,16 @@ module.exports = Backbone.Model.extend({
getStatus : function() {
var monitored = this.get("monitored");
var status = this.get("status");
var inCinemas = this.get("inCinemas");
var date = new Date(inCinemas);
var timeSince = new Date().getTime() - date.getTime();
var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30;
//var inCinemas = this.get("inCinemas");
//var date = new Date(inCinemas);
//var timeSince = new Date().getTime() - date.getTime();
//var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30;
if (status === "announced") {
return "announced"
}
if (numOfMonths < 3 && numOfMonths > 0) {
if (status === "inCinemas") {
return "inCinemas";
}
@ -31,9 +31,5 @@ module.exports = Backbone.Model.extend({
if (status === 'released') {
return "released";
}
if (numOfMonths > 3) {
return "released";//TODO: Update for PreDB.me
}
}
});

View File

@ -113,11 +113,14 @@ var Collection = PageableCollection.extend({
statusWeight : {
sortValue : function(model, attr) {
if (model.getStatus() == "released") {
return 1;
return 3;
}
if (model.getStatus() == "inCinemas") {
return 0;
return 2;
}
if (mode.getStatus() == "announced") {
return 1;
}
return -1;
}
},
@ -190,4 +193,4 @@ Collection = AsPersistedStateCollection.call(Collection);
var data = ApiData.get('movie');
module.exports = new Collection(data, { full : true }).bindSignalR();
module.exports = new Collection(data, { full : true }).bindSignalR();

View File

@ -37,4 +37,15 @@
<input type="number" name="rssSyncInterval" class="form-control" min="0" max="720"/>
</div>
</div>
<legend>Availability Options</legend>
<div class="form-group">
<label class="col-sm-3 control-label">Availability Delay</label>
<div class="col-sm-1 col-sm-push-2 help-inline">
<i class="icon-sonarr-form-info" title="A movie will be considered available during RssSync this many days after(or before) the Min Availability has been satisfied. (can be negative)"/>
<i class="icon-sonarr-form-info" title="This only effects RssSyncs, It does not effect how movies are displayed or what is shown in the Wanted/Missing View"/>
</div>
<div class="col-sm-2 col-sm-pull-1">
<input type="number" name="availabilityDelay" class="form-control" min="-365" max="365"/>
</div>
</div>
</fieldset>

View File

@ -19,6 +19,7 @@ var view = Marionette.ItemView.extend({
ui : {
profile : '.x-profile',
minimumAvailability : '.x-minimumavailability',
rootFolder : '.x-root-folder',
},
@ -48,10 +49,12 @@ var view = Marionette.ItemView.extend({
_onBeforeSave : function() {
var profile = this.ui.profile.val();
var minAvail = this.ui.minimumAvailability.val();
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
this.model.set({
profileId : profile,
rootFolderPath : rootFolderPath,
minimumAvailability : minAvail,
})
},

View File

@ -60,7 +60,18 @@
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Minimum Availability</label>
<div class="col-sm-5">
<select class="form-control x-minimumavailability" name="minimumAvailability">
<option value="announced">Announced</option>
<option value="inCinemas">In Cinemas</option>
<option value="released">Physical/Web</option>
<option value="preDB">PreDB</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Quality Profile</label>

View File

@ -36,7 +36,23 @@ var Collection = PagableCollection.extend({
'unmonitored' : [
'monitored',
'false'
]
],
'announced' : [
'moviestatus',
'announced'
],
'incinemas' : [
'moviestatus',
'incinemas'
],
'released' : [
'moviestatus',
'released'
],
'available' : [
'moviestatus',
'available',
]
},
parseState : function(resp) {

View File

@ -132,7 +132,7 @@ module.exports = Marionette.Layout.extend({
};
var filterOptions = {
type : 'radio',
storeState : false,
storeState : true,
menuKey : 'wanted.filterMode',
defaultAction : 'monitored',
items : [
@ -149,9 +149,37 @@ module.exports = Marionette.Layout.extend({
tooltip : 'Unmonitored Only',
icon : 'icon-sonarr-unmonitored',
callback : this._setFilter
}
]
};
},
{
key : 'announced',
title : '',
tooltip : 'Announced Only',
icon : 'icon-sonarr-movie-announced',
callback : this._setFilter
},
{
key : 'incinemas',
title : '',
tooltip : 'In Cinemas Only',
icon : 'icon-sonarr-movie-cinemas',
callback : this._setFilter
},
{
key : 'released',
title : '',
tooltip : 'Released Only',
icon : 'icon-sonarr-movie-released',
callback : this._setFilter
},
{
key : 'available',
title : '',
tooltip : 'Available Only',
icon : 'icon-sonarr-available',
callback : this._setFilter
}
]
};
this.toolbar.show(new ToolbarLayout({
left : [leftSideButtons],
right : [filterOptions],