diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj
index 9ca8abe50..a4f7bf194 100644
--- a/src/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/src/NzbDrone.Api/NzbDrone.Api.csproj
@@ -150,7 +150,8 @@
-
+
+
diff --git a/src/NzbDrone.Api/Wanted/CutoffModule.cs b/src/NzbDrone.Api/Wanted/CutoffModule.cs
new file mode 100644
index 000000000..b68dd5f10
--- /dev/null
+++ b/src/NzbDrone.Api/Wanted/CutoffModule.cs
@@ -0,0 +1,45 @@
+using System.Linq;
+using NzbDrone.Api.Episodes;
+using NzbDrone.Api.Extensions;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Tv;
+using NzbDrone.Core.Qualities;
+
+namespace NzbDrone.Api.Wanted
+{
+ public class CutoffModule : NzbDroneRestModule
+ {
+ private readonly IEpisodeService _episodeService;
+ private readonly SeriesRepository _seriesRepository;
+
+ public CutoffModule(IEpisodeService episodeService, SeriesRepository seriesRepository)
+ :base("wanted/cutoff")
+ {
+ _episodeService = episodeService;
+ _seriesRepository = seriesRepository;
+ GetResourcePaged = GetCutoffUnmetEpisodes;
+ }
+
+ private PagingResource GetCutoffUnmetEpisodes(PagingResource pagingResource)
+ {
+ var pagingSpec = new PagingSpec
+ {
+ Page = pagingResource.Page,
+ PageSize = pagingResource.PageSize,
+ SortKey = pagingResource.SortKey,
+ SortDirection = pagingResource.SortDirection
+ };
+
+ if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false")
+ pagingSpec.FilterExpression = v => v.Monitored == false || v.Series.Monitored == false;
+ else
+ pagingSpec.FilterExpression = v => v.Monitored == true && v.Series.Monitored == true;
+
+ PagingResource resource = ApplyToPage(_episodeService.GetCutoffUnmetEpisodes, pagingSpec);
+
+ resource.Records = resource.Records.LoadSubtype(e => e.SeriesId, _seriesRepository).ToList();
+
+ return resource;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Api/Missing/MissingModule.cs b/src/NzbDrone.Api/Wanted/MissingModule.cs
similarity index 68%
rename from src/NzbDrone.Api/Missing/MissingModule.cs
rename to src/NzbDrone.Api/Wanted/MissingModule.cs
index 968da7aab..4a5c34a90 100644
--- a/src/NzbDrone.Api/Missing/MissingModule.cs
+++ b/src/NzbDrone.Api/Wanted/MissingModule.cs
@@ -4,7 +4,7 @@ using NzbDrone.Api.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Tv;
-namespace NzbDrone.Api.Missing
+namespace NzbDrone.Api.Wanted
{
public class MissingModule : NzbDroneRestModule
{
@@ -12,7 +12,7 @@ namespace NzbDrone.Api.Missing
private readonly SeriesRepository _seriesRepository;
public MissingModule(IEpisodeService episodeService, SeriesRepository seriesRepository)
- :base("missing")
+ :base("wanted/missing")
{
_episodeService = episodeService;
_seriesRepository = seriesRepository;
@@ -28,8 +28,14 @@ namespace NzbDrone.Api.Missing
SortKey = pagingResource.SortKey,
SortDirection = pagingResource.SortDirection
};
+
+ if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false")
+ pagingSpec.FilterExpression = v => v.Monitored == false || v.Series.Monitored == false;
+ else
+ pagingSpec.FilterExpression = v => v.Monitored == true && v.Series.Monitored == true;
+
+ PagingResource resource = ApplyToPage(v => _episodeService.GetMissingEpisodes(v), pagingSpec);
- var resource = ApplyToPage(_episodeService.EpisodesWithoutFiles, pagingSpec);
resource.Records = resource.Records.LoadSubtype(e => e.SeriesId, _seriesRepository).ToList();
return resource;
diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs
index e59c67dec..50c4df605 100644
--- a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs
+++ b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs
@@ -79,7 +79,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
[Test]
public void should_get_monitored_episodes()
{
- var episodes = Subject.EpisodesWithoutFiles(_pagingSpec, false);
+ var episodes = Subject.GetMissingEpisodes(_pagingSpec, false);
episodes.Records.Should().HaveCount(1);
}
@@ -88,7 +88,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
[Ignore("Specials not implemented")]
public void should_get_episode_including_specials()
{
- var episodes = Subject.EpisodesWithoutFiles(_pagingSpec, true);
+ var episodes = Subject.GetMissingEpisodes(_pagingSpec, true);
episodes.Records.Should().HaveCount(2);
}
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
[Test]
public void should_not_include_unmonitored_episodes()
{
- var episodes = Subject.EpisodesWithoutFiles(_pagingSpec, false);
+ var episodes = Subject.GetMissingEpisodes(_pagingSpec, false);
episodes.Records.Should().NotContain(e => e.Monitored == false);
}
@@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
[Test]
public void should_not_contain_unmonitored_series()
{
- var episodes = Subject.EpisodesWithoutFiles(_pagingSpec, false);
+ var episodes = Subject.GetMissingEpisodes(_pagingSpec, false);
episodes.Records.Should().NotContain(e => e.SeriesId == _unmonitoredSeries.Id);
}
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
[Test]
public void should_have_count_of_one()
{
- var episodes = Subject.EpisodesWithoutFiles(_pagingSpec, false);
+ var episodes = Subject.GetMissingEpisodes(_pagingSpec, false);
episodes.TotalRecords.Should().Be(1);
}
diff --git a/src/NzbDrone.Core/Tv/EpisodeRepository.cs b/src/NzbDrone.Core/Tv/EpisodeRepository.cs
index dcbe99e1e..d33f173ca 100644
--- a/src/NzbDrone.Core/Tv/EpisodeRepository.cs
+++ b/src/NzbDrone.Core/Tv/EpisodeRepository.cs
@@ -4,7 +4,7 @@ using System.Linq;
using Marr.Data.QGen;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
-
+using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Core.Tv
{
@@ -17,7 +17,8 @@ namespace NzbDrone.Core.Tv
List GetEpisodes(int seriesId);
List GetEpisodes(int seriesId, int seasonNumber);
List GetEpisodeByFileId(int fileId);
- PagingSpec EpisodesWithoutFiles(PagingSpec pagingSpec, bool includeSpecials);
+ PagingSpec GetMissingEpisodes(PagingSpec pagingSpec, bool includeSpecials);
+ List GetCutoffUnmetEpisodes(PagingSpec pagingSpec, bool includeSpecials);
Episode FindEpisodeBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber);
List EpisodesBetweenDates(DateTime startDate, DateTime endDate);
void SetMonitoredFlat(Episode episode, bool monitored);
@@ -81,7 +82,7 @@ namespace NzbDrone.Core.Tv
return Query.Where(e => e.EpisodeFileId == fileId).ToList();
}
- public PagingSpec EpisodesWithoutFiles(PagingSpec pagingSpec, bool includeSpecials)
+ public PagingSpec GetMissingEpisodes(PagingSpec pagingSpec, bool includeSpecials)
{
var currentTime = DateTime.UtcNow;
var startingSeasonNumber = 1;
@@ -91,12 +92,47 @@ namespace NzbDrone.Core.Tv
startingSeasonNumber = 0;
}
- pagingSpec.Records = GetEpisodesWithoutFilesQuery(pagingSpec, currentTime, startingSeasonNumber).ToList();
- pagingSpec.TotalRecords = GetEpisodesWithoutFilesQuery(pagingSpec, currentTime, startingSeasonNumber).GetRowCount();
+ pagingSpec.TotalRecords = GetMissingEpisodesQuery(pagingSpec, currentTime, startingSeasonNumber).GetRowCount();
+ pagingSpec.Records = GetMissingEpisodesQuery(pagingSpec, currentTime, startingSeasonNumber).ToList();
return pagingSpec;
}
+ public List GetCutoffUnmetEpisodes(PagingSpec pagingSpec, bool includeSpecials)
+ {
+ var currentTime = DateTime.UtcNow;
+ var startingSeasonNumber = 1;
+
+ if (includeSpecials)
+ {
+ startingSeasonNumber = 0;
+ }
+
+ var query = Query.Join(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id)
+ .Join(JoinType.Left, e => e.EpisodeFile, (e, s) => e.EpisodeFileId == s.Id)
+ .Where(pagingSpec.FilterExpression)
+ .AndWhere(e => e.EpisodeFileId != 0)
+ .AndWhere(e => e.SeasonNumber >= startingSeasonNumber)
+ .AndWhere(e => e.AirDateUtc <= currentTime)
+ .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection());
+
+ return query.ToList();
+ }
+
+ private SortBuilder GetMissingEpisodesQuery(PagingSpec pagingSpec, DateTime currentTime, int startingSeasonNumber)
+ {
+ var query = Query.Join(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id)
+ .Where(pagingSpec.FilterExpression)
+ .AndWhere(e => e.EpisodeFileId == 0)
+ .AndWhere(e => e.SeasonNumber >= startingSeasonNumber)
+ .AndWhere(e => e.AirDateUtc <= currentTime)
+ .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
+ .Skip(pagingSpec.PagingOffset())
+ .Take(pagingSpec.PageSize);
+
+ return query;
+ }
+
public Episode FindEpisodeBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber)
{
return Query.Where(s => s.SeriesId == seriesId)
@@ -141,18 +177,5 @@ namespace NzbDrone.Core.Tv
{
SetFields(new Episode { Id = episodeId, EpisodeFileId = fileId }, episode => episode.EpisodeFileId);
}
-
- private SortBuilder GetEpisodesWithoutFilesQuery(PagingSpec pagingSpec, DateTime currentTime, int startingSeasonNumber)
- {
- return Query.Join(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id)
- .Where(e => e.EpisodeFileId == 0)
- .AndWhere(e => e.SeasonNumber >= startingSeasonNumber)
- .AndWhere(e => e.AirDateUtc <= currentTime)
- .AndWhere(e => e.Monitored)
- .AndWhere(e => e.Series.Monitored)
- .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
- .Skip(pagingSpec.PagingOffset())
- .Take(pagingSpec.PageSize);
- }
}
}
diff --git a/src/NzbDrone.Core/Tv/EpisodeService.cs b/src/NzbDrone.Core/Tv/EpisodeService.cs
index df27033a9..82dcb84f6 100644
--- a/src/NzbDrone.Core/Tv/EpisodeService.cs
+++ b/src/NzbDrone.Core/Tv/EpisodeService.cs
@@ -7,6 +7,7 @@ using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv.Events;
+using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Tv
{
@@ -20,7 +21,8 @@ namespace NzbDrone.Core.Tv
Episode FindEpisode(int seriesId, String date);
List GetEpisodeBySeries(int seriesId);
List GetEpisodesBySeason(int seriesId, int seasonNumber);
- PagingSpec EpisodesWithoutFiles(PagingSpec pagingSpec);
+ PagingSpec GetMissingEpisodes(PagingSpec pagingSpec);
+ PagingSpec GetCutoffUnmetEpisodes(PagingSpec pagingSpec);
List GetEpisodesByFileId(int episodeFileId);
void UpdateEpisode(Episode episode);
void SetEpisodeMonitored(int episodeId, bool monitored);
@@ -40,12 +42,14 @@ namespace NzbDrone.Core.Tv
{
private readonly IEpisodeRepository _episodeRepository;
+ private readonly IQualityProfileRepository _qualityProfileRepository;
private readonly IConfigService _configService;
private readonly Logger _logger;
- public EpisodeService(IEpisodeRepository episodeRepository, IConfigService configService, Logger logger)
+ public EpisodeService(IEpisodeRepository episodeRepository, IQualityProfileRepository qualityProfileRepository, IConfigService configService, Logger logger)
{
_episodeRepository = episodeRepository;
+ _qualityProfileRepository = qualityProfileRepository;
_configService = configService;
_logger = logger;
}
@@ -88,7 +92,7 @@ namespace NzbDrone.Core.Tv
{
return _episodeRepository.GetEpisodes(seriesId, seasonNumber);
}
-
+
public Episode FindEpisodeByName(int seriesId, int seasonNumber, string episodeTitle)
{
// TODO: can replace this search mechanism with something smarter/faster/better
@@ -105,11 +109,39 @@ namespace NzbDrone.Core.Tv
public PagingSpec EpisodesWithoutFiles(PagingSpec pagingSpec)
{
- var episodeResult = _episodeRepository.EpisodesWithoutFiles(pagingSpec, false);
+ var episodeResult = _episodeRepository.GetMissingEpisodes(pagingSpec, false);
return episodeResult;
}
+ public PagingSpec GetCutoffUnmetEpisodes(PagingSpec pagingSpec)
+ {
+ var allSpec = new PagingSpec
+ {
+ SortKey = pagingSpec.SortKey,
+ SortDirection = pagingSpec.SortDirection,
+ FilterExpression = pagingSpec.FilterExpression
+ };
+
+ var allItems = _episodeRepository.GetCutoffUnmetEpisodes(allSpec, false);
+
+ var qualityProfileComparers = _qualityProfileRepository.All().ToDictionary(v => v.Id, v => new { Profile = v, Comparer = new QualityModelComparer(v) });
+
+ var filtered = allItems.Where(episode =>
+ {
+ var profile = qualityProfileComparers[episode.Series.QualityProfileId];
+ return profile.Comparer.Compare(episode.EpisodeFile.Value.Quality.Quality, profile.Profile.Cutoff) < 0;
+ }).ToList();
+
+ pagingSpec.Records = filtered
+ .Skip(pagingSpec.PagingOffset())
+ .Take(pagingSpec.PageSize)
+ .ToList();
+ pagingSpec.TotalRecords = filtered.Count;
+
+ return pagingSpec;
+ }
+
public List GetEpisodesByFileId(int episodeFileId)
{
return _episodeRepository.GetEpisodeByFileId(episodeFileId);
diff --git a/src/UI/.idea/runConfigurations/Debug___Chrome.xml b/src/UI/.idea/runConfigurations/Debug___Chrome.xml
index 82eb4863d..47bd06dc9 100644
--- a/src/UI/.idea/runConfigurations/Debug___Chrome.xml
+++ b/src/UI/.idea/runConfigurations/Debug___Chrome.xml
@@ -6,7 +6,7 @@
-
+
diff --git a/src/UI/.idea/runConfigurations/Debug___Firefox.xml b/src/UI/.idea/runConfigurations/Debug___Firefox.xml
index 2e020afbc..d9e99acc3 100644
--- a/src/UI/.idea/runConfigurations/Debug___Firefox.xml
+++ b/src/UI/.idea/runConfigurations/Debug___Firefox.xml
@@ -6,7 +6,7 @@
-
+
diff --git a/src/UI/Cells/EpisodeStatusCell.js b/src/UI/Cells/EpisodeStatusCell.js
index d8ab15259..f54f2c474 100644
--- a/src/UI/Cells/EpisodeStatusCell.js
+++ b/src/UI/Cells/EpisodeStatusCell.js
@@ -52,7 +52,24 @@ define(
return;
}
+ else if (hasFile && this.model.get('episodeFile')) {
+ var episodeFile = this.model.get('episodeFile');
+
+ var quality = episodeFile.quality;
+ var size = FormatHelpers.bytes(episodeFile.size);
+ var title = 'Episode downloaded';
+
+ if (quality.proper) {
+ title += ' [PROPER] - {0}'.format(size);
+ this.$el.html('{1}'.format(title, quality.quality.name));
+ }
+ else {
+ title += ' - {0}'.format(size);
+ this.$el.html('{1}'.format(title, quality.quality.name));
+ }
+ return;
+ }
else {
var model = this.model;
var downloading = QueueCollection.findEpisode(model.get('id'));
diff --git a/src/UI/Controller.js b/src/UI/Controller.js
index db0b13e2b..19d7c3760 100644
--- a/src/UI/Controller.js
+++ b/src/UI/Controller.js
@@ -7,7 +7,7 @@ define(
'History/HistoryLayout',
'Settings/SettingsLayout',
'AddSeries/AddSeriesLayout',
- 'Missing/MissingLayout',
+ 'Wanted/WantedLayout',
'Calendar/CalendarLayout',
'Release/ReleaseLayout',
'System/SystemLayout',
@@ -20,7 +20,7 @@ define(
HistoryLayout,
SettingsLayout,
AddSeriesLayout,
- MissingLayout,
+ WantedLayout,
CalendarLayout,
ReleaseLayout,
SystemLayout,
@@ -44,10 +44,10 @@ define(
this.showMainRegion(new SettingsLayout({ action: action }));
},
- missing: function () {
- this.setTitle('Missing');
+ wanted: function (action) {
+ this.setTitle('Wanted');
- this.showMainRegion(new MissingLayout());
+ this.showMainRegion(new WantedLayout({ action: action }));
},
history: function (action) {
diff --git a/src/UI/Mixins/AsFilteredCollection.js b/src/UI/Mixins/AsFilteredCollection.js
index 2a0e17991..469059cfc 100644
--- a/src/UI/Mixins/AsFilteredCollection.js
+++ b/src/UI/Mixins/AsFilteredCollection.js
@@ -24,7 +24,7 @@ define(
};
this.prototype.setFilterMode = function(mode, options) {
- this.setFilter(this.filterModes[mode], options);
+ return this.setFilter(this.filterModes[mode], options);
};
var originalMakeFullCollection = this.prototype._makeFullCollection;
diff --git a/src/UI/Navbar/NavbarTemplate.html b/src/UI/Navbar/NavbarTemplate.html
index 19284def2..fdc1128b7 100644
--- a/src/UI/Navbar/NavbarTemplate.html
+++ b/src/UI/Navbar/NavbarTemplate.html
@@ -29,10 +29,10 @@
-
+
- Missing
+ Wanted
diff --git a/src/UI/Router.js b/src/UI/Router.js
index f2927787a..6c268a7ef 100644
--- a/src/UI/Router.js
+++ b/src/UI/Router.js
@@ -14,7 +14,8 @@ define(
'calendar' : 'calendar',
'settings' : 'settings',
'settings/:action(/:query)' : 'settings',
- 'missing' : 'missing',
+ 'wanted' : 'wanted',
+ 'wanted/:action' : 'wanted',
'history' : 'history',
'history/:action' : 'history',
'rss' : 'rss',
diff --git a/src/UI/Shared/Toolbar/Radio/RadioButtonCollectionView.js b/src/UI/Shared/Toolbar/Radio/RadioButtonCollectionView.js
index 240cd1445..e3793b341 100644
--- a/src/UI/Shared/Toolbar/Radio/RadioButtonCollectionView.js
+++ b/src/UI/Shared/Toolbar/Radio/RadioButtonCollectionView.js
@@ -16,13 +16,17 @@ define(
initialize: function (options) {
this.menu = options.menu;
- if (this.menu.storeState) {
- this.setActive();
- }
+ this.setActive();
},
setActive: function () {
- var storedKey = Config.getValue(this.menu.menuKey, this.menu.defaultAction);
+ var storedKey = this.menu.defaultAction;
+
+ if (this.menu.storeState)
+ storedKey = Config.getValue(this.menu.menuKey, storedKey);
+
+ if (!storedKey)
+ return;
this.collection.each(function (model) {
if (model.get('key').toLocaleLowerCase() === storedKey.toLowerCase()) {
diff --git a/src/UI/Missing/ControlsColumnTemplate.html b/src/UI/Wanted/ControlsColumnTemplate.html
similarity index 100%
rename from src/UI/Missing/ControlsColumnTemplate.html
rename to src/UI/Wanted/ControlsColumnTemplate.html
diff --git a/src/UI/Missing/MissingCollection.js b/src/UI/Wanted/Cutoff/CutoffUnmetCollection.js
similarity index 60%
rename from src/UI/Missing/MissingCollection.js
rename to src/UI/Wanted/Cutoff/CutoffUnmetCollection.js
index d58b6d133..a42c12dba 100644
--- a/src/UI/Missing/MissingCollection.js
+++ b/src/UI/Wanted/Cutoff/CutoffUnmetCollection.js
@@ -1,19 +1,21 @@
'use strict';
define(
[
+ 'underscore',
'Series/EpisodeModel',
'backbone.pageable',
+ 'Mixins/AsFilteredCollection',
'Mixins/AsPersistedStateCollection'
- ], function (EpisodeModel, PagableCollection, AsPersistedStateCollection) {
+ ], function (_, EpisodeModel, PagableCollection, AsFilteredCollection, AsPersistedStateCollection) {
var collection = PagableCollection.extend({
- url : window.NzbDrone.ApiRoot + '/missing',
+ url : window.NzbDrone.ApiRoot + '/wanted/cutoff',
model: EpisodeModel,
- tableName: 'missing',
+ tableName: 'wanted.cutoff',
state: {
- pageSize: 15,
- sortKey : 'airDateUtc',
- order : 1
+ pageSize : 15,
+ sortKey : 'airDateUtc',
+ order : 1
},
queryParams: {
@@ -27,6 +29,12 @@ define(
'1' : 'desc'
}
},
+
+ // Filter Modes
+ filterModes: {
+ 'monitored' : ['monitored', 'true'],
+ 'unmonitored' : ['monitored', 'false'],
+ },
parseState: function (resp) {
return {totalRecords: resp.totalRecords};
@@ -41,5 +49,6 @@ define(
}
});
+ collection = AsFilteredCollection.call(collection);
return AsPersistedStateCollection.call(collection);
});
diff --git a/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js b/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js
new file mode 100644
index 000000000..aeca14fbb
--- /dev/null
+++ b/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js
@@ -0,0 +1,206 @@
+'use strict';
+define(
+ [
+ 'underscore',
+ 'marionette',
+ 'backgrid',
+ 'Wanted/Cutoff/CutoffUnmetCollection',
+ 'Cells/SeriesTitleCell',
+ 'Cells/EpisodeNumberCell',
+ 'Cells/EpisodeTitleCell',
+ 'Cells/RelativeDateCell',
+ 'Cells/EpisodeStatusCell',
+ 'Shared/Grid/Pager',
+ 'Shared/Toolbar/ToolbarLayout',
+ 'Shared/LoadingView',
+ 'Shared/Messenger',
+ 'Commands/CommandController',
+ 'backgrid.selectall'
+ ], function (_,
+ Marionette,
+ Backgrid,
+ CutoffUnmetCollection,
+ SeriesTitleCell,
+ EpisodeNumberCell,
+ EpisodeTitleCell,
+ RelativeDateCell,
+ EpisodeStatusCell,
+ GridPager,
+ ToolbarLayout,
+ LoadingView,
+ Messenger,
+ CommandController) {
+ return Marionette.Layout.extend({
+ template: 'Wanted/Cutoff/CutoffUnmetLayoutTemplate',
+
+ regions: {
+ missing: '#x-missing',
+ toolbar: '#x-toolbar',
+ pager : '#x-pager'
+ },
+
+ ui: {
+ searchSelectedButton: '.btn i.icon-search'
+ },
+
+ columns:
+ [
+ {
+ name : '',
+ cell : 'select-row',
+ headerCell: 'select-all',
+ sortable : false
+ },
+ {
+ name : 'series',
+ label : 'Series Title',
+ sortable : false,
+ cell : SeriesTitleCell
+ },
+ {
+ name : 'this',
+ label : 'Episode',
+ sortable : false,
+ cell : EpisodeNumberCell
+ },
+ {
+ name : 'this',
+ label : 'Episode Title',
+ sortable : false,
+ cell : EpisodeTitleCell,
+ },
+ {
+ name : 'airDateUtc',
+ label : 'Air Date',
+ cell : RelativeDateCell
+ },
+ {
+ name : 'status',
+ label : 'Status',
+ cell : EpisodeStatusCell,
+ sortable: false
+ }
+ ],
+
+ initialize: function () {
+ this.collection = new CutoffUnmetCollection();
+
+ this.listenTo(this.collection, 'sync', this._showTable);
+ },
+
+ onShow: function () {
+ this.missing.show(new LoadingView());
+ this._showToolbar();
+ this.collection.fetch();
+ },
+
+ _showTable: function () {
+ this.missingGrid = new Backgrid.Grid({
+ columns : this.columns,
+ collection: this.collection,
+ className : 'table table-hover'
+ });
+
+ this.missing.show(this.missingGrid);
+
+ this.pager.show(new GridPager({
+ columns : this.columns,
+ collection: this.collection
+ }));
+ },
+
+ _showToolbar: function () {
+ var leftSideButtons = {
+ type : 'default',
+ storeState: false,
+ items :
+ [
+ {
+ title: 'Search Selected',
+ icon : 'icon-search',
+ callback: this._searchSelected,
+ ownerContext: this
+ },
+ {
+ title: 'Season Pass',
+ icon : 'icon-bookmark',
+ route: 'seasonpass'
+ }
+ ]
+ };
+
+ var filterOptions = {
+ type : 'radio',
+ storeState : false,
+ menuKey : 'wanted.filterMode',
+ defaultAction : 'monitored',
+ items :
+ [
+ {
+ key : 'monitored',
+ title : '',
+ tooltip : 'Monitored Only',
+ icon : 'icon-nd-monitored',
+ callback : this._setFilter
+ },
+ {
+ key : 'unmonitored',
+ title : '',
+ tooltip : 'Unmonitored Only',
+ icon : 'icon-nd-unmonitored',
+ callback : this._setFilter
+ },
+ ]
+ };
+
+ this.toolbar.show(new ToolbarLayout({
+ left :
+ [
+ leftSideButtons
+ ],
+ right :
+ [
+ filterOptions
+ ],
+ context: this
+ }));
+
+ CommandController.bindToCommand({
+ element: this.$('.x-toolbar-left-1 .btn i.icon-search'),
+ command: {
+ name: 'episodeSearch'
+ }
+ });
+ },
+
+ _setFilter: function(buttonContext) {
+ var mode = buttonContext.model.get('key');
+
+ this.collection.state.currentPage = 1;
+ var promise = this.collection.setFilterMode(mode);
+
+ if (buttonContext)
+ buttonContext.ui.icon.spinForPromise(promise);
+ },
+
+ _searchSelected: function () {
+ var selected = this.missingGrid.getSelectedModels();
+
+ if (selected.length === 0) {
+ Messenger.show({
+ type: 'error',
+ message: 'No episodes selected'
+ });
+
+ return;
+ }
+
+ var ids = _.pluck(selected, 'id');
+
+ CommandController.Execute('episodeSearch', {
+ name : 'episodeSearch',
+ episodeIds: ids
+ });
+ }
+ });
+ });
diff --git a/src/UI/Missing/MissingLayoutTemplate.html b/src/UI/Wanted/Cutoff/CutoffUnmetLayoutTemplate.html
similarity index 100%
rename from src/UI/Missing/MissingLayoutTemplate.html
rename to src/UI/Wanted/Cutoff/CutoffUnmetLayoutTemplate.html
diff --git a/src/UI/Wanted/Missing/MissingCollection.js b/src/UI/Wanted/Missing/MissingCollection.js
new file mode 100644
index 000000000..61564359f
--- /dev/null
+++ b/src/UI/Wanted/Missing/MissingCollection.js
@@ -0,0 +1,54 @@
+'use strict';
+define(
+ [
+ 'underscore',
+ 'Series/EpisodeModel',
+ 'backbone.pageable',
+ 'Mixins/AsFilteredCollection',
+ 'Mixins/AsPersistedStateCollection'
+ ], function (_, EpisodeModel, PagableCollection, AsFilteredCollection, AsPersistedStateCollection) {
+ var collection = PagableCollection.extend({
+ url : window.NzbDrone.ApiRoot + '/wanted/missing',
+ model: EpisodeModel,
+ tableName: 'wanted.missing',
+
+ state: {
+ pageSize : 15,
+ sortKey : 'airDateUtc',
+ order : 1
+ },
+
+ queryParams: {
+ totalPages : null,
+ totalRecords: null,
+ pageSize : 'pageSize',
+ sortKey : 'sortKey',
+ order : 'sortDir',
+ directions : {
+ '-1': 'asc',
+ '1' : 'desc'
+ }
+ },
+
+ // Filter Modes
+ filterModes: {
+ 'monitored' : ['monitored', 'true'],
+ 'unmonitored' : ['monitored', 'false'],
+ },
+
+ parseState: function (resp) {
+ return {totalRecords: resp.totalRecords};
+ },
+
+ parseRecords: function (resp) {
+ if (resp) {
+ return resp.records;
+ }
+
+ return resp;
+ }
+ });
+
+ collection = AsFilteredCollection.call(collection);
+ return AsPersistedStateCollection.call(collection);
+ });
diff --git a/src/UI/Missing/MissingLayout.js b/src/UI/Wanted/Missing/MissingLayout.js
similarity index 67%
rename from src/UI/Missing/MissingLayout.js
rename to src/UI/Wanted/Missing/MissingLayout.js
index 7240277f5..bd8f20843 100644
--- a/src/UI/Missing/MissingLayout.js
+++ b/src/UI/Wanted/Missing/MissingLayout.js
@@ -4,11 +4,12 @@ define(
'underscore',
'marionette',
'backgrid',
- 'Missing/MissingCollection',
+ 'Wanted/Missing/MissingCollection',
'Cells/SeriesTitleCell',
'Cells/EpisodeNumberCell',
'Cells/EpisodeTitleCell',
'Cells/RelativeDateCell',
+ 'Cells/EpisodeStatusCell',
'Shared/Grid/Pager',
'Shared/Toolbar/ToolbarLayout',
'Shared/LoadingView',
@@ -23,13 +24,14 @@ define(
EpisodeNumberCell,
EpisodeTitleCell,
RelativeDateCell,
+ EpisodeStatusCell,
GridPager,
ToolbarLayout,
LoadingView,
Messenger,
CommandController) {
return Marionette.Layout.extend({
- template: 'Missing/MissingLayoutTemplate',
+ template: 'Wanted/Missing/MissingLayoutTemplate',
regions: {
missing: '#x-missing',
@@ -52,25 +54,31 @@ define(
{
name : 'series',
label : 'Series Title',
- sortable: false,
+ sortable : false,
cell : SeriesTitleCell
},
{
name : 'this',
label : 'Episode',
- sortable: false,
+ sortable : false,
cell : EpisodeNumberCell
},
{
name : 'this',
label : 'Episode Title',
- sortable: false,
- cell : EpisodeTitleCell
+ sortable : false,
+ cell : EpisodeTitleCell,
},
{
- name : 'airDateUtc',
- label: 'Air Date',
- cell : RelativeDateCell
+ name : 'airDateUtc',
+ label : 'Air Date',
+ cell : RelativeDateCell
+ },
+ {
+ name : 'status',
+ label : 'Status',
+ cell : EpisodeStatusCell,
+ sortable: false
}
],
@@ -82,8 +90,8 @@ define(
onShow: function () {
this.missing.show(new LoadingView());
- this.collection.fetch();
this._showToolbar();
+ this.collection.fetch();
},
_showTable: function () {
@@ -120,12 +128,40 @@ define(
}
]
};
+
+ var filterOptions = {
+ type : 'radio',
+ storeState : false,
+ menuKey : 'wanted.filterMode',
+ defaultAction : 'monitored',
+ items :
+ [
+ {
+ key : 'monitored',
+ title : '',
+ tooltip : 'Monitored Only',
+ icon : 'icon-nd-monitored',
+ callback : this._setFilter
+ },
+ {
+ key : 'unmonitored',
+ title : '',
+ tooltip : 'Unmonitored Only',
+ icon : 'icon-nd-unmonitored',
+ callback : this._setFilter
+ },
+ ]
+ };
this.toolbar.show(new ToolbarLayout({
left :
[
leftSideButtons
],
+ right :
+ [
+ filterOptions
+ ],
context: this
}));
@@ -136,6 +172,16 @@ define(
}
});
},
+
+ _setFilter: function(buttonContext) {
+ var mode = buttonContext.model.get('key');
+
+ this.collection.state.currentPage = 1;
+ var promise = this.collection.setFilterMode(mode);
+
+ if (buttonContext)
+ buttonContext.ui.icon.spinForPromise(promise);
+ },
_searchSelected: function () {
var selected = this.missingGrid.getSelectedModels();
diff --git a/src/UI/Wanted/Missing/MissingLayoutTemplate.html b/src/UI/Wanted/Missing/MissingLayoutTemplate.html
new file mode 100644
index 000000000..958d5aa5e
--- /dev/null
+++ b/src/UI/Wanted/Missing/MissingLayoutTemplate.html
@@ -0,0 +1,11 @@
+
+
+
diff --git a/src/UI/Wanted/WantedLayout.js b/src/UI/Wanted/WantedLayout.js
new file mode 100644
index 000000000..bca0b3435
--- /dev/null
+++ b/src/UI/Wanted/WantedLayout.js
@@ -0,0 +1,69 @@
+'use strict';
+define(
+ [
+ 'marionette',
+ 'backbone',
+ 'backgrid',
+ 'Wanted/Missing/MissingLayout',
+ 'Wanted/Cutoff/CutoffUnmetLayout'
+ ], function (Marionette, Backbone, Backgrid, MissingLayout, CutoffUnmetLayout) {
+ return Marionette.Layout.extend({
+ template: 'Wanted/WantedLayoutTemplate',
+
+ regions: {
+ content : '#content'
+ //missing : '#missing',
+ //cutoff : '#cutoff'
+ },
+
+ ui: {
+ missingTab : '.x-missing-tab',
+ cutoffTab : '.x-cutoff-tab'
+ },
+
+ events: {
+ 'click .x-missing-tab' : '_showMissing',
+ 'click .x-cutoff-tab' : '_showCutoffUnmet'
+ },
+
+ initialize: function (options) {
+ if (options.action) {
+ this.action = options.action.toLowerCase();
+ }
+ },
+
+ onShow: function () {
+ switch (this.action) {
+ case 'cutoff':
+ this._showCutoffUnmet();
+ break;
+ default:
+ this._showMissing();
+ }
+ },
+
+ _navigate: function (route) {
+ Backbone.history.navigate(route);
+ },
+
+ _showMissing: function (e) {
+ if (e) {
+ e.preventDefault();
+ }
+
+ this.content.show(new MissingLayout());
+ this.ui.missingTab.tab('show');
+ this._navigate('/wanted/missing');
+ },
+
+ _showCutoffUnmet: function (e) {
+ if (e) {
+ e.preventDefault();
+ }
+
+ this.content.show(new CutoffUnmetLayout());
+ this.ui.cutoffTab.tab('show');
+ this._navigate('/wanted/cutoff');
+ }
+ });
+ });
diff --git a/src/UI/Wanted/WantedLayoutTemplate.html b/src/UI/Wanted/WantedLayoutTemplate.html
new file mode 100644
index 000000000..6665fb3d1
--- /dev/null
+++ b/src/UI/Wanted/WantedLayoutTemplate.html
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file