diff --git a/src/NzbDrone.Api/Blacklist/BlacklistModule.cs b/src/NzbDrone.Api/Blacklist/BlacklistModule.cs new file mode 100644 index 000000000..913810767 --- /dev/null +++ b/src/NzbDrone.Api/Blacklist/BlacklistModule.cs @@ -0,0 +1,42 @@ +using System; +using NzbDrone.Core.Blacklisting; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Api.Blacklist +{ + public class BlacklistModule : NzbDroneRestModule + { + private readonly IBlacklistService _blacklistService; + + public BlacklistModule(IBlacklistService blacklistService) + { + _blacklistService = blacklistService; + GetResourcePaged = GetBlacklist; + DeleteResource = Delete; + } + + private PagingResource GetBlacklist(PagingResource pagingResource) + { + var pagingSpec = new PagingSpec + { + Page = pagingResource.Page, + PageSize = pagingResource.PageSize, + SortKey = pagingResource.SortKey, + SortDirection = pagingResource.SortDirection + }; + + //This is a hack to deal with backgrid setting the sortKey to the column name instead of sortValue + if (pagingSpec.SortKey.Equals("series", StringComparison.InvariantCultureIgnoreCase)) + { + pagingSpec.SortKey = "series.title"; + } + + return ApplyToPage(_blacklistService.Paged, pagingSpec); + } + + private void Delete(int id) + { + _blacklistService.Delete(id); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Blacklist/BlacklistResource.cs b/src/NzbDrone.Api/Blacklist/BlacklistResource.cs new file mode 100644 index 000000000..f2c27f2c4 --- /dev/null +++ b/src/NzbDrone.Api/Blacklist/BlacklistResource.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Api.REST; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Api.Blacklist +{ + public class BlacklistResource : RestResource + { + public int SeriesId { get; set; } + public List EpisodeIds { get; set; } + public string SourceTitle { get; set; } + public QualityModel Quality { get; set; } + public DateTime Date { get; set; } + } +} diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 00f5f96e3..0371bd9cb 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -82,6 +82,8 @@ + + diff --git a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs index 8e11f02ad..8b3ab0a2d 100644 --- a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs +++ b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs @@ -1,5 +1,7 @@ using System; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Download; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Blacklisting @@ -7,9 +9,11 @@ namespace NzbDrone.Core.Blacklisting public interface IBlacklistService { bool Blacklisted(string sourceTitle); + PagingSpec Paged(PagingSpec pagingSpec); + void Delete(int id); } - public class BlacklistService : IBlacklistService, IHandle + public class BlacklistService : IBlacklistService, IHandle, IExecute { private readonly IBlacklistRepository _blacklistRepository; private readonly IRedownloadFailedDownloads _redownloadFailedDownloadService; @@ -25,6 +29,16 @@ namespace NzbDrone.Core.Blacklisting return _blacklistRepository.Blacklisted(sourceTitle); } + public PagingSpec Paged(PagingSpec pagingSpec) + { + return _blacklistRepository.GetPaged(pagingSpec); + } + + public void Delete(int id) + { + _blacklistRepository.Delete(id); + } + public void Handle(DownloadFailedEvent message) { var blacklist = new Blacklist @@ -40,5 +54,10 @@ namespace NzbDrone.Core.Blacklisting _redownloadFailedDownloadService.Redownload(message.SeriesId, message.EpisodeIds); } + + public void Execute(ClearBlacklistCommand message) + { + _blacklistRepository.Purge(); + } } } diff --git a/src/NzbDrone.Core/Blacklisting/ClearBlacklistCommand.cs b/src/NzbDrone.Core/Blacklisting/ClearBlacklistCommand.cs new file mode 100644 index 000000000..03e9b2e5e --- /dev/null +++ b/src/NzbDrone.Core/Blacklisting/ClearBlacklistCommand.cs @@ -0,0 +1,15 @@ +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.Blacklisting +{ + public class ClearBlacklistCommand : Command + { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index c2be091cf..3c42a4d2a 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -122,6 +122,7 @@ + diff --git a/src/UI/History/Blacklist/BlacklistActionsCell.js b/src/UI/History/Blacklist/BlacklistActionsCell.js new file mode 100644 index 000000000..3f6e9a766 --- /dev/null +++ b/src/UI/History/Blacklist/BlacklistActionsCell.js @@ -0,0 +1,26 @@ +'use strict'; + +define( + [ + 'Cells/NzbDroneCell' + ], function (NzbDroneCell) { + return NzbDroneCell.extend({ + + className: 'blacklist-controls-cell', + + events: { + 'click': '_delete' + }, + + render: function () { + this.$el.empty(); + this.$el.html(''); + + return this; + }, + + _delete: function () { + this.model.destroy(); + } + }); + }); diff --git a/src/UI/History/Blacklist/BlacklistCollection.js b/src/UI/History/Blacklist/BlacklistCollection.js new file mode 100644 index 000000000..3dcdb7cab --- /dev/null +++ b/src/UI/History/Blacklist/BlacklistCollection.js @@ -0,0 +1,44 @@ +'use strict'; +define( + [ + 'History/Blacklist/BlacklistModel', + 'backbone.pageable', + 'Mixins/AsPersistedStateCollection' + ], function (BlacklistModel, PageableCollection, AsPersistedStateCollection) { + var collection = PageableCollection.extend({ + url : window.NzbDrone.ApiRoot + '/blacklist', + model: BlacklistModel, + + state: { + pageSize: 15, + sortKey : 'date', + order : 1 + }, + + queryParams: { + totalPages : null, + totalRecords: null, + pageSize : 'pageSize', + sortKey : 'sortKey', + order : 'sortDir', + directions : { + '-1': 'asc', + '1' : 'desc' + } + }, + + parseState: function (resp) { + return { totalRecords: resp.totalRecords }; + }, + + parseRecords: function (resp) { + if (resp) { + return resp.records; + } + + return resp; + } + }); + + return AsPersistedStateCollection.apply(collection); + }); diff --git a/src/UI/History/Blacklist/BlacklistLayout.js b/src/UI/History/Blacklist/BlacklistLayout.js new file mode 100644 index 000000000..8a6a74487 --- /dev/null +++ b/src/UI/History/Blacklist/BlacklistLayout.js @@ -0,0 +1,132 @@ +'use strict'; +define( + [ + 'vent', + 'marionette', + 'backgrid', + 'History/Blacklist/BlacklistCollection', + 'Cells/SeriesTitleCell', + 'Cells/QualityCell', + 'Cells/RelativeDateCell', + 'History/Blacklist/BlacklistActionsCell', + 'Shared/Grid/Pager', + 'Shared/LoadingView', + 'Shared/Toolbar/ToolbarLayout' + ], function (vent, + Marionette, + Backgrid, + BlacklistCollection, + SeriesTitleCell, + QualityCell, + RelativeDateCell, + BlacklistActionsCell, + GridPager, + LoadingView, + ToolbarLayout) { + return Marionette.Layout.extend({ + template: 'History/Blacklist/BlacklistLayoutTemplate', + + regions: { + blacklist : '#x-blacklist', + toolbar : '#x-toolbar', + pager : '#x-pager' + }, + + columns: + [ + { + name : 'series', + label: 'Series', + cell : SeriesTitleCell, + sortValue: 'series.title' + }, + { + name : 'sourceTitle', + label: 'Source Title', + cell : 'string', + sortValue: 'sourceTitle' + }, + { + name : 'quality', + label : 'Quality', + cell : QualityCell, + sortable: false + }, + { + name : 'date', + label: 'Date', + cell : RelativeDateCell + }, + { + name : 'this', + label : '', + cell : BlacklistActionsCell, + sortable: false + } + ], + + initialize: function () { + this.collection = new BlacklistCollection({ tableName: 'blacklist' }); + this.listenTo(this.collection, 'sync', this._showTable); + vent.on(vent.Events.CommandComplete, this._commandComplete, this); + }, + + onShow: function () { + this.blacklist.show(new LoadingView()); + this._showToolbar(); + this.collection.fetch(); + }, + + _showTable: function (collection) { + + this.blacklist.show(new Backgrid.Grid({ + columns : this.columns, + collection: collection, + className : 'table table-hover' + })); + + this.pager.show(new GridPager({ + columns : this.columns, + collection: collection + })); + }, + + _showToolbar: function () { + var leftSideButtons = { + type : 'default', + storeState: false, + items : + [ + { + title : 'Clear Blacklist', + icon : 'icon-trash', + command : 'clearBlacklist' + } + ] + }; + + this.toolbar.show(new ToolbarLayout({ + left : + [ + leftSideButtons + ], + context: this + })); + }, + + _refreshTable: function (buttonContext) { + this.collection.state.currentPage = 1; + var promise = this.collection.fetch({ reset: true }); + + if (buttonContext) { + buttonContext.ui.icon.spinForPromise(promise); + } + }, + + _commandComplete: function (options) { + if (options.command.get('name') === 'clearblacklist') { + this._refreshTable(); + } + } + }); + }); diff --git a/src/UI/History/Blacklist/BlacklistLayoutTemplate.html b/src/UI/History/Blacklist/BlacklistLayoutTemplate.html new file mode 100644 index 000000000..f90d55c39 --- /dev/null +++ b/src/UI/History/Blacklist/BlacklistLayoutTemplate.html @@ -0,0 +1,11 @@ +
+
+
+
+
+
+
+
+
+
+
diff --git a/src/UI/History/Blacklist/BlacklistModel.js b/src/UI/History/Blacklist/BlacklistModel.js new file mode 100644 index 000000000..f8bf927aa --- /dev/null +++ b/src/UI/History/Blacklist/BlacklistModel.js @@ -0,0 +1,21 @@ +'use strict'; +define( + [ + 'backbone', + 'Series/SeriesCollection' + ], function (Backbone, SeriesCollection) { + return Backbone.Model.extend({ + + //Hack to deal with Backbone 1.0's bug + initialize: function () { + this.url = function () { + return this.collection.url + '/' + this.get('id'); + }; + }, + + parse: function (model) { + model.series = SeriesCollection.get(model.seriesId); + return model; + } + }); + }); diff --git a/src/UI/History/HistoryLayout.js b/src/UI/History/HistoryLayout.js index 04f5bc12f..fc13cd2fe 100644 --- a/src/UI/History/HistoryLayout.js +++ b/src/UI/History/HistoryLayout.js @@ -5,24 +5,28 @@ define( 'backbone', 'backgrid', 'History/Table/HistoryTableLayout', + 'History/Blacklist/BlacklistLayout', 'History/Queue/QueueLayout' - ], function (Marionette, Backbone, Backgrid, HistoryTableLayout, QueueLayout) { + ], function (Marionette, Backbone, Backgrid, HistoryTableLayout, BlacklistLayout, QueueLayout) { return Marionette.Layout.extend({ template: 'History/HistoryLayoutTemplate', regions: { history : '#history', + blacklist : '#blacklist', queueRegion: '#queue' }, ui: { historyTab: '.x-history-tab', + blacklistTab: '.x-blacklist-tab', queueTab : '.x-queue-tab' }, events: { - 'click .x-history-tab': '_showHistory', - 'click .x-queue-tab' : '_showQueue' + 'click .x-history-tab' : '_showHistory', + 'click .x-blacklist-tab' : '_showBlacklist', + 'click .x-queue-tab' : '_showQueue' }, initialize: function (options) { @@ -55,6 +59,16 @@ define( this._navigate('/history'); }, + _showBlacklist: function (e) { + if (e) { + e.preventDefault(); + } + + this.blacklist.show(new BlacklistLayout()); + this.ui.blacklistTab.tab('show'); + this._navigate('/history/blacklist'); + }, + _showQueue: function (e) { if (e) { e.preventDefault(); diff --git a/src/UI/History/HistoryLayoutTemplate.html b/src/UI/History/HistoryLayoutTemplate.html index 50c981bf0..4faa2d28b 100644 --- a/src/UI/History/HistoryLayoutTemplate.html +++ b/src/UI/History/HistoryLayoutTemplate.html @@ -1,9 +1,11 @@ 
+
\ No newline at end of file diff --git a/src/UI/System/Logs/Files/LogFileLayout.js b/src/UI/System/Logs/Files/LogFileLayout.js index af8de87a9..0b1431aee 100644 --- a/src/UI/System/Logs/Files/LogFileLayout.js +++ b/src/UI/System/Logs/Files/LogFileLayout.js @@ -81,7 +81,7 @@ define( title : 'Refresh', icon : 'icon-refresh', ownerContext : this, - callback : this._refreshLogs + callback : this._refreshTable }, { @@ -140,7 +140,7 @@ define( this.contents.show(new ContentsView({ model: model })); }, - _refreshLogs: function (buttonContext) { + _refreshTable: function (buttonContext) { this.contents.close(); var promise = this.collection.fetch(); @@ -152,7 +152,7 @@ define( _commandComplete: function (options) { if (options.command.get('name') === 'deletelogfiles') { - this._refreshLogs(); + this._refreshTable(); } } }); diff --git a/src/UI/System/Logs/Table/LogsTableLayout.js b/src/UI/System/Logs/Table/LogsTableLayout.js index 219ebfc1b..b86bdbdf8 100644 --- a/src/UI/System/Logs/Table/LogsTableLayout.js +++ b/src/UI/System/Logs/Table/LogsTableLayout.js @@ -97,7 +97,7 @@ define( title : 'Refresh', icon : 'icon-refresh', ownerContext : this, - callback : this._refreshLogs + callback : this._refreshTable }, { @@ -117,7 +117,7 @@ define( })); }, - _refreshLogs: function (buttonContext) { + _refreshTable: function (buttonContext) { this.collection.state.currentPage = 1; var promise = this.collection.fetch({ reset: true }); @@ -128,7 +128,7 @@ define( _commandComplete: function (options) { if (options.command.get('name') === 'clearlog') { - this._refreshLogs(); + this._refreshTable(); } } });