From c1b68e0deee4cebaba9b98c1e9b4acb51413ee47 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 10 Aug 2013 11:38:01 -0700 Subject: [PATCH] When a new episode is grabbed the client will update its status --- NzbDrone.Api/Episodes/EpisodeConnection.cs | 24 ++++++++++++++++++++ NzbDrone.Api/NzbDrone.Api.csproj | 1 + UI/Cells/EpisodeStatusCell.js | 7 +++++- UI/Mixins/backbone.signalr.mixin.js | 26 +++++++++++++++------- UI/Series/Details/SeasonCollectionView.js | 17 +++++++++++++- UI/Series/Details/SeriesDetailsLayout.js | 18 ++++++++++----- UI/Series/EpisodeCollection.js | 26 +++++++++++++++++++--- 7 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 NzbDrone.Api/Episodes/EpisodeConnection.cs diff --git a/NzbDrone.Api/Episodes/EpisodeConnection.cs b/NzbDrone.Api/Episodes/EpisodeConnection.cs new file mode 100644 index 000000000..814c6dc84 --- /dev/null +++ b/NzbDrone.Api/Episodes/EpisodeConnection.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.AspNet.SignalR; +using Microsoft.AspNet.SignalR.Infrastructure; +using NzbDrone.Api.SignalR; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Download; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Api.Episodes +{ + public class EpisodeConnection : BasicResourceConnection, IHandleAsync + { + public override string Resource + { + get { return "/Episodes"; } + } + + public void HandleAsync(EpisodeGrabbedEvent message) + { + var context = ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType()); + context.Connection.Broadcast(message); + } + } +} diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index 7fae02668..6e67dd73d 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -89,6 +89,7 @@ + diff --git a/UI/Cells/EpisodeStatusCell.js b/UI/Cells/EpisodeStatusCell.js index b397abd25..d20e96052 100644 --- a/UI/Cells/EpisodeStatusCell.js +++ b/UI/Cells/EpisodeStatusCell.js @@ -40,7 +40,12 @@ define( return this; } else { - if (!this.model.get('airDateUtc')) { + if (this.model.get('downloading')) { + icon = 'icon-download-alt'; + tooltip = 'Episode is downloading'; + } + + else if (!this.model.get('airDateUtc')) { icon = 'icon-question-sign'; tooltip = 'TBA'; } diff --git a/UI/Mixins/backbone.signalr.mixin.js b/UI/Mixins/backbone.signalr.mixin.js index 781669313..6b1f9ce9e 100644 --- a/UI/Mixins/backbone.signalr.mixin.js +++ b/UI/Mixins/backbone.signalr.mixin.js @@ -6,11 +6,13 @@ define( _.extend(Backbone.Collection.prototype, {BindSignalR: function (options) { - if (!options || !options.url) { + if (!options) { + options = {}; + } + + if (!options.url) { console.assert(this.url, 'url must be provided or collection must have url'); - options = { - url: this.url.replace('api', 'signalr') - }; + options['url'] = this.url.replace('api', 'signalr'); } var self = this; @@ -31,16 +33,24 @@ define( }; - var connection = $.connection(options.url); connection.stateChanged(function (change) { console.debug('{0} [{1}]'.format(options.url, _getStatus(change.newState))); }); - connection.received(function (model) { - console.debug(model); - self.fetch(); + connection.received(function (message) { + console.debug(message); + + if (options.onReceived) { + var context = options.context || self; + + options.onReceived.call(context, message); + } + + else { + self.fetch(); + } }); connection.start({ transport: diff --git a/UI/Series/Details/SeasonCollectionView.js b/UI/Series/Details/SeasonCollectionView.js index 6fab22774..49e333c8a 100644 --- a/UI/Series/Details/SeasonCollectionView.js +++ b/UI/Series/Details/SeasonCollectionView.js @@ -23,7 +23,22 @@ define( episodeCollection: this.episodeCollection, series : this.series }; - } + }, + + onEpisodeGrabbed: function (message) { + if (message.episode.series.id != this.episodeCollection.seriesId) { + return; + } + var self = this; + + _.each(message.episode.episodes, function (episode){ + var ep = self.episodeCollection.find({ id: episode.id }); + ep.set('downloading', true); + console.debug(episode.title); + }); + + this.render(); + } }); }); diff --git a/UI/Series/Details/SeriesDetailsLayout.js b/UI/Series/Details/SeriesDetailsLayout.js index 3663fd776..7e456df90 100644 --- a/UI/Series/Details/SeriesDetailsLayout.js +++ b/UI/Series/Details/SeriesDetailsLayout.js @@ -9,7 +9,8 @@ define( 'Series/Details/SeasonMenu/CollectionView', 'Shared/LoadingView', 'Shared/Actioneer', - 'backstrech' + 'backstrech', + 'Mixins/backbone.signalr.mixin' ], function (App, Marionette, EpisodeCollection, SeasonCollection, SeasonCollectionView, SeasonMenuCollectionView, LoadingView, Actioneer) { return Marionette.Layout.extend({ @@ -156,14 +157,21 @@ define( this.seasons.show(new LoadingView()); this.seasonCollection = new SeasonCollection(); - this.episodeCollection = new EpisodeCollection(); + this.episodeCollection = new EpisodeCollection({ seriesId: this.model.id }); - $.when(this.episodeCollection.fetch({data: { seriesId: this.model.id }}), this.seasonCollection.fetch({data: { seriesId: this.model.id }})).done(function () { - self.seasons.show(new SeasonCollectionView({ + $.when(this.episodeCollection.fetch(), this.seasonCollection.fetch({data: { seriesId: this.model.id }})).done(function () { + var seasonCollectionView = new SeasonCollectionView({ collection : self.seasonCollection, episodeCollection: self.episodeCollection, series : self.model - })); + }); + + self.episodeCollection.BindSignalR({ + onReceived: seasonCollectionView.onEpisodeGrabbed, + context : seasonCollectionView + }); + + self.seasons.show(seasonCollectionView); self.seasonMenu.show(new SeasonMenuCollectionView({ collection: self.seasonCollection, diff --git a/UI/Series/EpisodeCollection.js b/UI/Series/EpisodeCollection.js index 3e6a7d628..52aa024e9 100644 --- a/UI/Series/EpisodeCollection.js +++ b/UI/Series/EpisodeCollection.js @@ -8,6 +8,17 @@ define( url : window.ApiRoot + '/episodes', model: EpisodeModel, + state: { + sortKey: 'episodeNumber', + order : -1 + }, + + originalFetch: Backbone.Collection.prototype.fetch, + + initialize: function (options) { + this.seriesId = options.seriesId; + }, + bySeason: function (season) { var filtered = this.filter(function (episode) { return episode.get('seasonNumber') === season; @@ -34,9 +45,18 @@ define( return 0; }, - state: { - sortKey: 'episodeNumber', - order : -1 + fetch: function (options) { + if (!this.seriesId) { + throw 'seriesId is required'; + } + + if (!options) { + options = {}; + } + + options['data'] = { seriesId: this.seriesId }; + + return this.originalFetch.call(this, options); } }); });