diff --git a/src/NzbDrone.Api/Series/SeriesModule.cs b/src/NzbDrone.Api/Series/SeriesModule.cs index ea01b1a06..d87a0f4ec 100644 --- a/src/NzbDrone.Api/Series/SeriesModule.cs +++ b/src/NzbDrone.Api/Series/SeriesModule.cs @@ -2,23 +2,39 @@ using System.Collections.Generic; using System.Linq; using FluentValidation; +using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.MediaCover; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.SeriesStats; using NzbDrone.Core.Tv; using NzbDrone.Api.Validation; using NzbDrone.Api.Mapping; +using NzbDrone.Core.Tv.Events; namespace NzbDrone.Api.Series { - public class SeriesModule : NzbDroneRestModule + public class SeriesModule : NzbDroneRestModuleWithSignalR, + IHandle, + IHandle, + IHandle, + IHandle, + IHandle + { + private readonly ICommandExecutor _commandExecutor; private readonly ISeriesService _seriesService; private readonly ISeriesStatisticsService _seriesStatisticsService; private readonly IMapCoversToLocal _coverMapper; - public SeriesModule(ISeriesService seriesService, ISeriesStatisticsService seriesStatisticsService, IMapCoversToLocal coverMapper) - : base("/Series") + public SeriesModule(ICommandExecutor commandExecutor, + ISeriesService seriesService, + ISeriesStatisticsService seriesStatisticsService, + IMapCoversToLocal coverMapper) + : base(commandExecutor) { + _commandExecutor = commandExecutor; _seriesService = seriesService; _seriesStatisticsService = seriesStatisticsService; _coverMapper = coverMapper; @@ -74,6 +90,8 @@ namespace NzbDrone.Api.Series private void UpdateSeries(SeriesResource seriesResource) { GetNewId(_seriesService.UpdateSeries, seriesResource); + + BroadcastResourceChange(ModelAction.Updated, seriesResource); } private void DeleteSeries(int id) @@ -119,5 +137,32 @@ namespace NzbDrone.Api.Series resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount; resource.NextAiring = seriesStatistics.NextAiring; } + + public void Handle(EpisodeImportedEvent message) + { + BroadcastResourceChange(ModelAction.Updated, message.ImportedEpisode.SeriesId); + } + + public void Handle(EpisodeFileDeletedEvent message) + { + if (message.ForUpgrade) return; + + BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.SeriesId); + } + + public void Handle(SeriesUpdatedEvent message) + { + BroadcastResourceChange(ModelAction.Updated, message.Series.Id); + } + + public void Handle(SeriesEditedEvent message) + { + BroadcastResourceChange(ModelAction.Updated, message.Series.Id); + } + + public void Handle(SeriesDeletedEvent message) + { + BroadcastResourceChange(ModelAction.Deleted, message.Series.InjectTo()); + } } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 83d567d8a..1944dc15a 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -489,6 +489,7 @@ + diff --git a/src/NzbDrone.Core/Tv/Events/SeriesEditedEvent.cs b/src/NzbDrone.Core/Tv/Events/SeriesEditedEvent.cs new file mode 100644 index 000000000..33371a4e7 --- /dev/null +++ b/src/NzbDrone.Core/Tv/Events/SeriesEditedEvent.cs @@ -0,0 +1,14 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Tv.Events +{ + public class SeriesEditedEvent : IEvent + { + public Series Series { get; private set; } + + public SeriesEditedEvent(Series series) + { + Series = series; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Tv/SeriesService.cs b/src/NzbDrone.Core/Tv/SeriesService.cs index b4fe33ea1..ee7af6841 100644 --- a/src/NzbDrone.Core/Tv/SeriesService.cs +++ b/src/NzbDrone.Core/Tv/SeriesService.cs @@ -136,7 +136,10 @@ namespace NzbDrone.Core.Tv } } - return _seriesRepository.Update(series); + var updatedSeries = _seriesRepository.Update(series); + _eventAggregator.PublishEvent(new SeriesEditedEvent(updatedSeries)); + + return updatedSeries; } public List UpdateSeries(List series) @@ -148,6 +151,8 @@ namespace NzbDrone.Core.Tv var folderName = new DirectoryInfo(s.Path).Name; s.Path = Path.Combine(s.RootFolderPath, folderName); } + + _eventAggregator.PublishEvent(new SeriesEditedEvent(s)); } _seriesRepository.UpdateMany(series); diff --git a/src/UI/Cells/SeriesStatusCell.js b/src/UI/Cells/SeriesStatusCell.js index b180a172c..027e5aea3 100644 --- a/src/UI/Cells/SeriesStatusCell.js +++ b/src/UI/Cells/SeriesStatusCell.js @@ -1,9 +1,9 @@ 'use strict'; define( [ - 'backgrid' - ], function (Backgrid) { - return Backgrid.Cell.extend({ + 'Cells/NzbDroneCell' + ], function (NzbDroneCell) { + return NzbDroneCell.extend({ className: 'series-status-cell', render: function () { @@ -13,20 +13,24 @@ define( if (status === 'ended') { this.$el.html(''); - this.model.set('statusWeight', 3); + this._setStatusWeight(3); } else if (!monitored) { this.$el.html(''); - this.model.set('statusWeight', 2); + this._setStatusWeight(2); } else { this.$el.html(''); - this.model.set('statusWeight', 1); + this._setStatusWeight(1); } return this; + }, + + _setStatusWeight: function (weight) { + this.model.set('statusWeight', weight, {silent: true}); } }); }); diff --git a/src/UI/Mixins/AsFilteredCollection.js b/src/UI/Mixins/AsFilteredCollection.js index 447f60edb..2a0e17991 100644 --- a/src/UI/Mixins/AsFilteredCollection.js +++ b/src/UI/Mixins/AsFilteredCollection.js @@ -32,7 +32,7 @@ define( this.prototype._makeFullCollection = function (models, options) { var self = this; - self.shadowCollection = originalMakeFullCollection.apply(this, [models, options]); + self.shadowCollection = originalMakeFullCollection.call(this, models, options); var filterModel = function(model) { if (!self.state.filterKey || !self.state.filterValue) @@ -46,12 +46,10 @@ define( }; var filteredModels = self.shadowCollection.filtered(); - - var fullCollection = originalMakeFullCollection.apply(this, [filteredModels, options]); - + var fullCollection = originalMakeFullCollection.call(this, filteredModels, options); fullCollection.resetFiltered = function(options) { - Backbone.Collection.prototype.reset.apply(this, [self.shadowCollection.filtered(), options]); + Backbone.Collection.prototype.reset.call(this, self.shadowCollection.filtered(), options); }; fullCollection.reset = function (models, options) { diff --git a/src/UI/Mixins/backbone.signalr.mixin.js b/src/UI/Mixins/backbone.signalr.mixin.js index 04b1dfd7f..a29ed4882 100644 --- a/src/UI/Mixins/backbone.signalr.mixin.js +++ b/src/UI/Mixins/backbone.signalr.mixin.js @@ -22,6 +22,12 @@ define( return; } + if (options.action === 'deleted') { + collection.remove(new collection.model(options.resource, {parse: true})); + + return; + } + var model = new collection.model(options.resource, {parse: true}); //updateOnly will prevent the collection from adding a new item diff --git a/src/UI/Series/Index/SeriesIndexLayout.js b/src/UI/Series/Index/SeriesIndexLayout.js index ac3262063..5d732d484 100644 --- a/src/UI/Series/Index/SeriesIndexLayout.js +++ b/src/UI/Series/Index/SeriesIndexLayout.js @@ -1,6 +1,7 @@ 'use strict'; define( [ + 'underscore', 'marionette', 'backgrid', 'Series/Index/Posters/SeriesPostersCollectionView', @@ -16,9 +17,9 @@ define( 'Cells/SeriesStatusCell', 'Series/Index/FooterView', 'Series/Index/FooterModel', - 'Shared/Toolbar/ToolbarLayout', - 'underscore' - ], function (Marionette, + 'Shared/Toolbar/ToolbarLayout' + ], function (_, + Marionette, Backgrid, PosterCollectionView, ListCollectionView, @@ -33,8 +34,7 @@ define( SeriesStatusCell, FooterView, FooterModel, - ToolbarLayout, - _) { + ToolbarLayout) { return Marionette.Layout.extend({ template: 'Series/Index/SeriesIndexLayoutTemplate', @@ -131,8 +131,25 @@ define( initialize: function () { this.seriesCollection = SeriesCollection.clone(); - this.listenTo(SeriesCollection, 'sync', this._renderView); - this.listenTo(SeriesCollection, 'remove', this._renderView); + this.listenTo(SeriesCollection, 'sync', function (model, collection, options) { + this.seriesCollection.shadowCollection.add(model, options); + this.seriesCollection.fullCollection.resetFiltered(); + this._renderView(); + }); + + this.listenTo(SeriesCollection, 'add', function (model, collection, options) { + this.seriesCollection.shadowCollection.add(model, options); + this.seriesCollection.fullCollection.resetFiltered(); + this._renderView(); + }); + + this.listenTo(SeriesCollection, 'remove', function (model, collection, options) { + this.seriesCollection.shadowCollection.remove(model, options); + this.seriesCollection.fullCollection.resetFiltered(); + this._renderView(); + }); + + this.sortingOptions = { type : 'sorting', @@ -255,7 +272,7 @@ define( }, _showList: function () { - this.currentView = new ListCollectionView({ + this.currentView = new ListCollectionView({ collection: this.seriesCollection }); @@ -269,7 +286,7 @@ define( this._renderView(); }, - + _renderView: function () { if (SeriesCollection.length === 0) { @@ -278,6 +295,8 @@ define( this.toolbar2.close(); } else { + this._resetFilter(); + this.seriesRegion.show(this.currentView); this._showToolbar(); @@ -295,6 +314,18 @@ define( this.seriesCollection.setFilterMode(mode); }, + _resetFilter: function () { + var key = this.seriesCollection.state.filterKey; + var value = this.seriesCollection.state.filterValue; + + this.seriesCollection.setFilter([ null, null ]); + this.seriesCollection.setFilter([ key, value ]); + }, + + _shadowTest: function () { + window.alert('added to shadow'); + }, + _showToolbar: function () { if (this.toolbar.currentView) { diff --git a/src/UI/Series/SeriesCollection.js b/src/UI/Series/SeriesCollection.js index 4f00e6c42..bbd70e940 100644 --- a/src/UI/Series/SeriesCollection.js +++ b/src/UI/Series/SeriesCollection.js @@ -8,7 +8,8 @@ define( 'api!series', 'Mixins/AsFilteredCollection', 'Mixins/AsPersistedStateCollection', - 'moment' + 'moment', + 'Mixins/backbone.signalr.mixin' ], function (_, Backbone, PageableCollection, SeriesModel, SeriesData, AsFilteredCollection, AsPersistedStateCollection, Moment) { var Collection = PageableCollection.extend({ url : window.NzbDrone.ApiRoot + '/series', @@ -72,5 +73,5 @@ define( var MixedIn = AsPersistedStateCollection.call(FilteredCollection); var collection = new MixedIn(SeriesData, { full: true }); - return collection; + return collection.bindSignalR(); }); diff --git a/src/UI/Shared/Toolbar/Radio/RadioButtonView.js b/src/UI/Shared/Toolbar/Radio/RadioButtonView.js index baa67a5da..af2f0c903 100644 --- a/src/UI/Shared/Toolbar/Radio/RadioButtonView.js +++ b/src/UI/Shared/Toolbar/Radio/RadioButtonView.js @@ -50,7 +50,6 @@ define( throw 'ownerContext must be set.'; } - var callback = this.model.get('callback'); if (callback) { callback.call(this.model.ownerContext, this);