diff --git a/NzbDrone.Api/AutomapperBootstraper.cs b/NzbDrone.Api/AutomapperBootstraper.cs index 7fcec8128..63987d428 100644 --- a/NzbDrone.Api/AutomapperBootstraper.cs +++ b/NzbDrone.Api/AutomapperBootstraper.cs @@ -4,7 +4,9 @@ using NzbDrone.Api.QualityProfiles; using NzbDrone.Api.QualityType; using NzbDrone.Api.Resolvers; using NzbDrone.Api.Series; +using NzbDrone.Api.Upcoming; using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Tv; namespace NzbDrone.Api { @@ -40,6 +42,12 @@ namespace NzbDrone.Api .ForMember(dest => dest.CustomStartDate, opt => opt.ResolveUsing().FromMember(src => src.CustomStartDate)) .ForMember(dest => dest.BacklogSetting, opt => opt.MapFrom(src => (Int32)src.BacklogSetting)) .ForMember(dest => dest.NextAiring, opt => opt.ResolveUsing()); + + //Upcoming + Mapper.CreateMap() + .ForMember(dest => dest.SeriesTitle, opt => opt.MapFrom(src => src.Series.Title)) + .ForMember(dest => dest.EpisodeTitle, opt => opt.MapFrom(src => src.Title)) + .ForMember(dest => dest.AirTime, opt => opt.ResolveUsing()); } } } \ No newline at end of file diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index 91e91a67f..29ebe1dd9 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -115,6 +115,7 @@ + @@ -138,6 +139,8 @@ + + diff --git a/NzbDrone.Api/Resolvers/AirTimeResolver.cs b/NzbDrone.Api/Resolvers/AirTimeResolver.cs new file mode 100644 index 000000000..e98a2f06b --- /dev/null +++ b/NzbDrone.Api/Resolvers/AirTimeResolver.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using NzbDrone.Api.QualityProfiles; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Api.Resolvers +{ + public class AirTimeResolver : ValueResolver + { + protected override DateTime? ResolveCore(Episode source) + { + if(String.IsNullOrWhiteSpace(source.Series.AirTime) || !source.AirDate.HasValue) + return source.AirDate; + + return source.AirDate.Value.Add(Convert.ToDateTime(source.Series.AirTime).TimeOfDay) + .AddHours(source.Series.UtcOffset * -1); + } + } +} diff --git a/NzbDrone.Api/Upcoming/UpcomingModule.cs b/NzbDrone.Api/Upcoming/UpcomingModule.cs new file mode 100644 index 000000000..8247bbedc --- /dev/null +++ b/NzbDrone.Api/Upcoming/UpcomingModule.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using AutoMapper; +using FluentValidation; +using Nancy; +using NzbDrone.Api.Extentions; +using NzbDrone.Api.Series; +using NzbDrone.Common; +using NzbDrone.Core.Jobs; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Api.Upcoming +{ + public class UpcomingModule : NzbDroneApiModule + { + private readonly UpcomingEpisodesProvider _upcomingEpisodesProvider; + + public UpcomingModule(UpcomingEpisodesProvider upcomingEpisodesProvider) + : base("/Upcoming") + { + _upcomingEpisodesProvider = upcomingEpisodesProvider; + Get["/"] = x => Upcoming(); + } + + private Response Upcoming() + { + var upcoming = _upcomingEpisodesProvider.UpcomingEpisodes(); + return Mapper.Map, List>(upcoming).AsResponse(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Api/Upcoming/UpcomingResource.cs b/NzbDrone.Api/Upcoming/UpcomingResource.cs new file mode 100644 index 000000000..fd6d5ed80 --- /dev/null +++ b/NzbDrone.Api/Upcoming/UpcomingResource.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Api.QualityProfiles; +using NzbDrone.Core.Model; + +namespace NzbDrone.Api.Upcoming +{ + public class UpcomingResource + { + public Int32 SeriesId { get; set; } + public String SeriesTitle { get; set; } + + public Int32 EpisodeId { get; set; } + public String EpisodeTitle { get; set; } + public Int32 SeasonNumber { get; set; } + public Int32 EpisodeNumber { get; set; } + public DateTime? AirTime { get; set; } + public Int32 Status { get; set; } + public String Overview { get; set; } + } +} diff --git a/NzbDrone.Backbone/Controller.js b/NzbDrone.Backbone/Controller.js index 3449136f7..ab9b7df73 100644 --- a/NzbDrone.Backbone/Controller.js +++ b/NzbDrone.Backbone/Controller.js @@ -1,4 +1,4 @@ -define(['app', 'Shared/ModalRegion', 'AddSeries/AddSeriesLayout', 'Series/SeriesCollectionView', 'Shared/NotificationView', 'Shared/NotFoundView'], function (app, modalRegion) { +define(['app', 'Shared/ModalRegion', 'AddSeries/AddSeriesLayout', 'Series/SeriesCollectionView', 'Upcoming/UpcomingCollectionView', 'Shared/NotificationView', 'Shared/NotFoundView'], function (app, modalRegion) { var controller = Backbone.Marionette.Controller.extend({ @@ -12,6 +12,11 @@ NzbDrone.mainRegion.show(new NzbDrone.Series.SeriesCollectionView(this, action, query)); }, + upcoming: function (action, query) { + this.setTitle('Upcoming'); + NzbDrone.mainRegion.show(new NzbDrone.Upcoming.UpcomingCollectionView(this, action, query)); + }, + notFound: function () { this.setTitle('Not Found'); NzbDrone.mainRegion.show(new NzbDrone.Shared.NotFoundView(this)); diff --git a/NzbDrone.Backbone/NzbDrone.Backbone.csproj b/NzbDrone.Backbone/NzbDrone.Backbone.csproj index 433f50237..d7643a68e 100644 --- a/NzbDrone.Backbone/NzbDrone.Backbone.csproj +++ b/NzbDrone.Backbone/NzbDrone.Backbone.csproj @@ -121,6 +121,12 @@ + + + + + + diff --git a/NzbDrone.Backbone/Routing.js b/NzbDrone.Backbone/Routing.js index 5a7735475..6c5e92a50 100644 --- a/NzbDrone.Backbone/Routing.js +++ b/NzbDrone.Backbone/Routing.js @@ -9,6 +9,8 @@ 'series/index': 'series', 'series/add': 'addSeries', 'series/add/:action(/:query)': 'addSeries', + 'upcoming': 'upcoming', + 'upcoming/index': 'upcoming', ':whatever': 'notFound' } }); diff --git a/NzbDrone.Backbone/Series/SeriesModel.js b/NzbDrone.Backbone/Series/SeriesModel.js index 6dc10e9de..87537a67b 100644 --- a/NzbDrone.Backbone/Series/SeriesModel.js +++ b/NzbDrone.Backbone/Series/SeriesModel.js @@ -12,7 +12,6 @@ if (date.isYesterday()) return 'Yesterday'; if (date.isToday()) return 'Today'; if (date.isTomorrow()) return 'Tomorrow'; - if (date.isToday()) return 'Today'; if (date.isBefore(Date.create().addDays(7))) return date.format('{Weekday}'); return date.format('{MM}/{dd}/{yyyy}'); diff --git a/NzbDrone.Backbone/Upcoming/UpcomingCollection.js b/NzbDrone.Backbone/Upcoming/UpcomingCollection.js new file mode 100644 index 000000000..759aba04d --- /dev/null +++ b/NzbDrone.Backbone/Upcoming/UpcomingCollection.js @@ -0,0 +1,6 @@ +define(['app', 'Upcoming/UpcomingModel'], function () { + NzbDrone.Upcoming.UpcomingCollection = Backbone.Collection.extend({ + url: NzbDrone.Constants.ApiRoot + '/upcoming', + model: NzbDrone.Upcoming.UpcomingModel + }); +}); \ No newline at end of file diff --git a/NzbDrone.Backbone/Upcoming/UpcomingCollectionTemplate.html b/NzbDrone.Backbone/Upcoming/UpcomingCollectionTemplate.html new file mode 100644 index 000000000..da60443a6 --- /dev/null +++ b/NzbDrone.Backbone/Upcoming/UpcomingCollectionTemplate.html @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Series TitleEpisodeEpisode TitleAir TimeStatus
Yesterday
Today
Tomorrow
{{two_days}}
{{three_days}}
{{four_days}}
{{five_days}}
{{six_days}}
Later
\ No newline at end of file diff --git a/NzbDrone.Backbone/Upcoming/UpcomingCollectionView.js b/NzbDrone.Backbone/Upcoming/UpcomingCollectionView.js new file mode 100644 index 000000000..00dd91b07 --- /dev/null +++ b/NzbDrone.Backbone/Upcoming/UpcomingCollectionView.js @@ -0,0 +1,89 @@ +'use strict'; + +define(['app', 'Upcoming/UpcomingItemView'], function (app) { + NzbDrone.Upcoming.UpcomingCollectionView = Backbone.Marionette.CompositeView.extend({ + itemView: NzbDrone.Upcoming.UpcomingItemView, + template: 'Upcoming/UpcomingCollectionTemplate', + itemViewContainer: 'table', + + ui: { + yesterday: 'tbody#yesterday', + today: 'tbody#today', + tomorrow: 'tbody#tomorrow', + two_days: 'tbody#two_days', + three_days: 'tbody#three_days', + four_days: 'tbody#four_days', + five_days: 'tbody#five_days', + six_days: 'tbody#six_days', + later: 'tbody#later' + }, + + initialize: function () { + this.collection = new NzbDrone.Upcoming.UpcomingCollection(); + this.collection.fetch(); + }, + + serializeData: function() { + var viewData = {}; + viewData.two_days = Date.create().addDays(2).format('{Weekday}'); + viewData.three_days = Date.create().addDays(3).format('{Weekday}'); + viewData.four_days = Date.create().addDays(4).format('{Weekday}'); + viewData.five_days = Date.create().addDays(5).format('{Weekday}'); + viewData.six_days = Date.create().addDays(6).format('{Weekday}'); + return viewData; + }, + + appendHtml: function(collectionView, itemView, index){ + var date = Date.create(itemView.model.get('airTime')); + + if (date.isYesterday()){ + collectionView.$(this.ui.yesterday).append(itemView.el); + return; + } + + if (date.isToday()){ + collectionView.$(this.ui.today).append(itemView.el); + return; + } + + if (date.isTomorrow()){ + collectionView.$(this.ui.tomorrow).append(itemView.el); + return; + } + + if (date.is(Date.create().addDays(2).short())){ + collectionView.$(this.ui.two_days).append(itemView.el); + return; + } + + if (date.is(Date.create().addDays(3).short())){ + collectionView.$(this.ui.three_days).append(itemView.el); + return; + } + + if (date.is(Date.create().addDays(4).short())){ + collectionView.$(this.ui.four_days).append(itemView.el); + return; + } + + if (date.is(Date.create().addDays(5).short())){ + collectionView.$(this.ui.five_days).append(itemView.el); + return; + } + + if (date.is(Date.create().addDays(6).short())){ + collectionView.$(this.ui.six_days).append(itemView.el); + return; + } + + collectionView.$(this.ui.later).append(itemView.el); + + //if (date.isBefore(Date.create().addDays(7))) return date.format('{Weekday}'); + }, + + onCompositeCollectionRendered: function() + { + //Might not need this :D + } + }); +}); \ No newline at end of file diff --git a/NzbDrone.Backbone/Upcoming/UpcomingItemTemplate.html b/NzbDrone.Backbone/Upcoming/UpcomingItemTemplate.html new file mode 100644 index 000000000..b53ef04e9 --- /dev/null +++ b/NzbDrone.Backbone/Upcoming/UpcomingItemTemplate.html @@ -0,0 +1,5 @@ +{{seriesTitle}} +{{seasonNumber}}x{{episodeNumber}} +{{episodeTitle}} +{{airTime}} +{{status}} \ No newline at end of file diff --git a/NzbDrone.Backbone/Upcoming/UpcomingItemView.js b/NzbDrone.Backbone/Upcoming/UpcomingItemView.js new file mode 100644 index 000000000..b2f836945 --- /dev/null +++ b/NzbDrone.Backbone/Upcoming/UpcomingItemView.js @@ -0,0 +1,16 @@ +'use strict'; + +define([ + 'app', + 'Upcoming/UpcomingCollection' + +], function () { + NzbDrone.Upcoming.UpcomingItemView = Backbone.Marionette.ItemView.extend({ + template: 'Upcoming/UpcomingItemTemplate', + tagName: 'tr', + + onRender: function () { + NzbDrone.ModelBinder.bind(this.model, this.el); + } + }) +}) \ No newline at end of file diff --git a/NzbDrone.Backbone/Upcoming/UpcomingModel.js b/NzbDrone.Backbone/Upcoming/UpcomingModel.js new file mode 100644 index 000000000..4acf9434e --- /dev/null +++ b/NzbDrone.Backbone/Upcoming/UpcomingModel.js @@ -0,0 +1,10 @@ +define(['app'], function (app) { + NzbDrone.Upcoming.UpcomingModel = Backbone.Model.extend({ + mutators: { + + }, + defaults: { + status: 0 + } + }); +}); diff --git a/NzbDrone.Backbone/app.js b/NzbDrone.Backbone/app.js index e89d1cc7a..d01ffdde4 100644 --- a/NzbDrone.Backbone/app.js +++ b/NzbDrone.Backbone/app.js @@ -38,6 +38,7 @@ define('app', function () { window.NzbDrone.AddSeries.RootFolders = {}; window.NzbDrone.Quality = {}; window.NzbDrone.Shared = {}; + window.NzbDrone.Upcoming = {}; window.NzbDrone.Events = { OpenModalDialog :'openModal', diff --git a/NzbDrone.Core/Model/EpisodeStatusType.cs b/NzbDrone.Core/Model/EpisodeStatusType.cs index f77d662bd..12a260073 100644 --- a/NzbDrone.Core/Model/EpisodeStatusType.cs +++ b/NzbDrone.Core/Model/EpisodeStatusType.cs @@ -15,6 +15,7 @@ /// /// Episode has aired, but no episode /// files are avilable + /// Todo: We shouldn't set missing until the episode has past the actual airtime + runtime, including UtcOffset /// Missing, diff --git a/NzbDrone.Core/Providers/UpcomingEpisodesProvider.cs b/NzbDrone.Core/Providers/UpcomingEpisodesProvider.cs index 5693fab57..2586668b3 100644 --- a/NzbDrone.Core/Providers/UpcomingEpisodesProvider.cs +++ b/NzbDrone.Core/Providers/UpcomingEpisodesProvider.cs @@ -17,6 +17,7 @@ namespace NzbDrone.Core.Providers _database = database; } + //Todo: Might be best if this is part of episode repo (when its there) public virtual List UpcomingEpisodes() { return _database.Fetch(@"SELECT * FROM Episodes diff --git a/NzbDrone.Core/Tv/Episode.cs b/NzbDrone.Core/Tv/Episode.cs index dd5c46436..9a47bde98 100644 --- a/NzbDrone.Core/Tv/Episode.cs +++ b/NzbDrone.Core/Tv/Episode.cs @@ -15,6 +15,8 @@ namespace NzbDrone.Core.Tv public int SeasonNumber { get; set; } public int EpisodeNumber { get; set; } public string Title { get; set; } + + //Todo: Since we're displaying next airing relative to the user's timezone we may want to store this as UTC (with airtime + UTC offset) public DateTime? AirDate { get; set; } public string Overview { get; set; } public Boolean Ignored { get; set; } @@ -23,6 +25,7 @@ namespace NzbDrone.Core.Tv public int SceneSeasonNumber { get; set; } public int SceneEpisodeNumber { get; set; } + //Todo: This should be UTC public DateTime? GrabDate { get; set; } public EpisodeStatusType Status diff --git a/NzbDrone.Core/Tv/Series.cs b/NzbDrone.Core/Tv/Series.cs index 74783ec9a..d9d1f7c8c 100644 --- a/NzbDrone.Core/Tv/Series.cs +++ b/NzbDrone.Core/Tv/Series.cs @@ -2,6 +2,7 @@ using System; using NzbDrone.Core.Model; using NzbDrone.Core.Repository.Quality; +using PetaPoco; namespace NzbDrone.Core.Tv { @@ -19,6 +20,7 @@ namespace NzbDrone.Core.Tv public DayOfWeek? AirsDayOfWeek { get; set; } + [Column("AirTimes")] public String AirTime { get; set; } public string Language { get; set; }