diff --git a/NzbDrone.Api/Bootstrapper.cs b/NzbDrone.Api/Bootstrapper.cs index 003ebe7c1..8fa20f538 100644 --- a/NzbDrone.Api/Bootstrapper.cs +++ b/NzbDrone.Api/Bootstrapper.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using AutoMapper; using Autofac; using NLog; @@ -9,7 +10,9 @@ using NzbDrone.Api.QualityProfiles; using NzbDrone.Api.QualityType; using NzbDrone.Api.Resolvers; +using NzbDrone.Api.Series; using NzbDrone.Core; +using NzbDrone.Core.Helpers; using NzbDrone.Core.Repository.Quality; namespace NzbDrone.Api @@ -32,25 +35,30 @@ protected override void ApplicationStartup(ILifetimeScope container, IPipelines public static void InitializeAutomapper() { //QualityProfiles - Mapper.CreateMap() - .ForMember(dest => dest.QualityProfileId, opt => opt.MapFrom(src => src.Id)) - .ForMember(dest => dest.Allowed, - opt => opt.ResolveUsing().FromMember(src => src.Qualities)); - Mapper.CreateMap() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.QualityProfileId)) .ForMember(dest => dest.Qualities, opt => opt.ResolveUsing().FromMember(src => src.Allowed)); + Mapper.CreateMap() + .ForMember(dest => dest.QualityProfileId, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.Allowed, + opt => opt.ResolveUsing().FromMember(src => src.Qualities)); + Mapper.CreateMap() .ForMember(dest => dest.Allowed, opt => opt.Ignore()); //QualityTypes + Mapper.CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.QualityTypeId)); + Mapper.CreateMap() .ForMember(dest => dest.QualityTypeId, opt => opt.MapFrom(src => src.Id)); - Mapper.CreateMap() - .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.QualityTypeId)); + //Series + Mapper.CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.SeriesId)); + //.ForMember(dest => dest.BacklogSetting, opt => opt.MapFrom(src => Convert.ToInt32(src.BacklogSetting))); } protected override ILifetimeScope GetApplicationContainer() diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index 96d560fbe..b6249c9d5 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -96,6 +96,7 @@ + diff --git a/NzbDrone.Api/Series/SeriesModel.cs b/NzbDrone.Api/Series/SeriesModel.cs new file mode 100644 index 000000000..2fd100293 --- /dev/null +++ b/NzbDrone.Api/Series/SeriesModel.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Core.Model; + +namespace NzbDrone.Api.Series +{ + public class SeriesModel + { + public Int32 Id { get; set; } + + //Todo: Sorters should be done completely on the client + //Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing? + + //View Only + public String Title { get; set; } + public Int32 SeasonsCount { get; set; } + public Int32 EpisodeCount { get; set; } + public Int32 EpisodeFileCount { get; set; } + public String Status { get; set; } + public String AirsDayOfWeek { get; set; } + public String QualityProfileName { get; set; } + public String Overview { get; set; } + public Int32 Episodes { get; set; } + public Boolean HasBanner { get; set; } + public DateTime NextAiring { get; set; } + public String Details { get; set; } + public String Network { get; set; } + public String AirTime { get; set; } + public String Language { get; set; } + + public Int32 SeasonCount { get; set; } + public Int32 UtcOffset { get; set; } + + //View & Edit + public String Path { get; set; } + public Int32 QualityProfileId { get; set; } + + //Editing Only + public Boolean SeasonFolder { get; set; } + public Boolean Monitored { get; set; } + public BacklogSettingType BacklogSetting { get; set; } + public DateTime? CustomStartDate { get; set; } + } +} diff --git a/NzbDrone.Api/Series/SeriesModule.cs b/NzbDrone.Api/Series/SeriesModule.cs index ca0fe7274..01c8c81ef 100644 --- a/NzbDrone.Api/Series/SeriesModule.cs +++ b/NzbDrone.Api/Series/SeriesModule.cs @@ -1,10 +1,14 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; using FluentValidation; using Nancy; using NzbDrone.Api.Extentions; +using NzbDrone.Api.QualityProfiles; using NzbDrone.Common; using NzbDrone.Core.Jobs; using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Core; namespace NzbDrone.Api.Series { @@ -12,15 +16,26 @@ public class SeriesModule : NzbDroneApiModule { private readonly SeriesProvider _seriesProvider; private readonly JobProvider _jobProvider; + private readonly ConfigProvider _configProvider; - public SeriesModule(SeriesProvider seriesProvider, JobProvider jobProvider) + public SeriesModule(SeriesProvider seriesProvider, JobProvider jobProvider, + ConfigProvider configProvider) : base("/Series") { _seriesProvider = seriesProvider; _jobProvider = jobProvider; + _configProvider = configProvider; + Get["/"] = x => AllSeries(); Post["/"] = x => AddSeries(); } + private Response AllSeries() + { + var series = _seriesProvider.GetAllSeriesWithEpisodeCount().ToList(); + var seriesModels = Mapper.Map, List>(series); + + return seriesModels.AsResponse(); + } private Response AddSeries() { @@ -29,8 +44,7 @@ private Response AddSeries() //Todo: Alert the user if this series already exists //Todo: We need to create the folder if the user is adding a new series //(we can just create the folder and it won't blow up if it already exists) - //We also need to remove any special characters from the filename before attempting to create it - + //We also need to remove any special characters from the filename before attempting to create it _seriesProvider.AddSeries("", request.Path, request.SeriesId, request.QualityProfileId, null); _jobProvider.QueueJob(typeof(ImportNewSeriesJob)); @@ -39,7 +53,6 @@ private Response AddSeries() } } - public class SeriesValidator : AbstractValidator { private readonly DiskProvider _diskProvider; @@ -58,7 +71,5 @@ public SeriesValidator() RuleFor(s => s.QualityProfileId).GreaterThan(0); }); } - - } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index 9a0d94f5f..5ac92fe32 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -58,7 +58,7 @@ public virtual IList GetAllSeriesWithEpisodeCount() var series = _database .Fetch(@"SELECT Series.SeriesId, Series.Title, Series.CleanTitle, Series.Status, Series.Overview, Series.AirsDayOfWeek, Series.AirTimes, Series.Language, Series.Path, Series.Monitored, Series.QualityProfileId, Series.SeasonFolder, Series.BacklogSetting, Series.Network, - SUM(CASE WHEN Ignored = 0 AND Airdate <= @0 THEN 1 ELSE 0 END) AS EpisodeCount, + Series.UtcOffset, Series.CustomStartDate, SUM(CASE WHEN Ignored = 0 AND Airdate <= @0 THEN 1 ELSE 0 END) AS EpisodeCount, SUM(CASE WHEN Episodes.Ignored = 0 AND Episodes.EpisodeFileId > 0 AND Episodes.AirDate <= @0 THEN 1 ELSE 0 END) as EpisodeFileCount, MAX(Episodes.SeasonNumber) as SeasonCount, MIN(CASE WHEN AirDate < @0 OR Ignored = 1 THEN NULL ELSE AirDate END) as NextAiring, QualityProfiles.QualityProfileId, QualityProfiles.Name, QualityProfiles.Cutoff, QualityProfiles.SonicAllowed @@ -68,7 +68,8 @@ FROM Series WHERE Series.LastInfoSync IS NOT NULL GROUP BY Series.SeriesId, Series.Title, Series.CleanTitle, Series.Status, Series.Overview, Series.AirsDayOfWeek, Series.AirTimes, Series.Language, Series.Path, Series.Monitored, Series.QualityProfileId, Series.SeasonFolder, Series.BacklogSetting, Series.Network, - QualityProfiles.QualityProfileId, QualityProfiles.Name, QualityProfiles.Cutoff, QualityProfiles.SonicAllowed", DateTime.Today); + Series.UtcOffset, Series.CustomStartDate, + QualityProfiles.QualityProfileId, QualityProfiles.Name, QualityProfiles.Cutoff, QualityProfiles.SonicAllowed",DateTime.Today); return series; } @@ -99,7 +100,7 @@ public virtual Series UpdateSeriesInfo(int seriesId) series.SeriesId = tvDbSeries.Id; series.Title = tvDbSeries.SeriesName; - series.AirTimes = CleanAirsTime(tvDbSeries.AirsTime); + series.AirTime = CleanAirsTime(tvDbSeries.AirsTime); series.AirsDayOfWeek = tvDbSeries.AirsDayOfWeek; series.Overview = tvDbSeries.Overview; series.Status = tvDbSeries.Status; diff --git a/NzbDrone.Core/Repository/Series.cs b/NzbDrone.Core/Repository/Series.cs index 4525b3e7c..71f738969 100644 --- a/NzbDrone.Core/Repository/Series.cs +++ b/NzbDrone.Core/Repository/Series.cs @@ -26,7 +26,8 @@ public class Series [DisplayName("Air on")] public DayOfWeek? AirsDayOfWeek { get; set; } - public String AirTimes { get; set; } + [Column("AirTimes")] + public String AirTime { get; set; } public string Language { get; set; } diff --git a/NzbDrone.Web/Controllers/SeriesController.cs b/NzbDrone.Web/Controllers/SeriesController.cs index 333f706cf..3722217ec 100644 --- a/NzbDrone.Web/Controllers/SeriesController.cs +++ b/NzbDrone.Web/Controllers/SeriesController.cs @@ -237,7 +237,7 @@ private List GetSeriesModels(IList seriesInDb) EpisodeFileCount = s.EpisodeFileCount, NextAiring = s.NextAiring == null ? String.Empty : s.NextAiring.Value.ToBestDateString(), NextAiringSorter = s.NextAiring == null ? new DateTime(9999, 12, 31).ToString("o", CultureInfo.InvariantCulture) : s.NextAiring.Value.ToString("o", CultureInfo.InvariantCulture), - AirTime = s.AirTimes, + AirTime = s.AirTime, CustomStartDate = s.CustomStartDate.HasValue ? s.CustomStartDate.Value.ToString("yyyy-MM-dd") : String.Empty }).ToList(); diff --git a/NzbDrone.Web/Controllers/UpcomingController.cs b/NzbDrone.Web/Controllers/UpcomingController.cs index 3a06bf795..06f2cb261 100644 --- a/NzbDrone.Web/Controllers/UpcomingController.cs +++ b/NzbDrone.Web/Controllers/UpcomingController.cs @@ -52,9 +52,9 @@ private List GetUpcomingEpisodeModels(List episod EpisodeNumbering = String.Format("{0}x{1:00}", u.SeasonNumber, u.EpisodeNumber), Title = u.Title, Overview = u.Overview, - AirDateTime = GetDateTime(u.AirDate.Value, u.Series.AirTimes), + AirDateTime = GetDateTime(u.AirDate.Value, u.Series.AirTime), AirDate = u.AirDate.Value.ToBestDateString(), - AirTime = String.IsNullOrEmpty(u.Series.AirTimes) ? "?" : Convert.ToDateTime(u.Series.AirTimes).ToShortTimeString(), + AirTime = String.IsNullOrEmpty(u.Series.AirTime) ? "?" : Convert.ToDateTime(u.Series.AirTime).ToShortTimeString(), Status = u.Status.ToString() }).OrderBy(e => e.AirDateTime).ToList(); } diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index b84df8ad8..01c6821a0 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -409,6 +409,12 @@ + + + + + + diff --git a/NzbDrone.Web/_backboneApp/AddSeries/New/SearchResultView.js b/NzbDrone.Web/_backboneApp/AddSeries/New/SearchResultView.js index 3e8d074e2..bb148f215 100644 --- a/NzbDrone.Web/_backboneApp/AddSeries/New/SearchResultView.js +++ b/NzbDrone.Web/_backboneApp/AddSeries/New/SearchResultView.js @@ -56,8 +56,6 @@ NzbDrone.AddSeries.SearchItemView = Backbone.Marionette.ItemView.extend({ } }); } - - }); NzbDrone.AddSeries.SearchResultView = Backbone.Marionette.CollectionView.extend({ diff --git a/NzbDrone.Web/_backboneApp/CassetteConfiguration.cs b/NzbDrone.Web/_backboneApp/CassetteConfiguration.cs index 209219294..31e1342b8 100644 --- a/NzbDrone.Web/_backboneApp/CassetteConfiguration.cs +++ b/NzbDrone.Web/_backboneApp/CassetteConfiguration.cs @@ -30,6 +30,7 @@ public void Configure(BundleCollection bundles) bundles.Add("~/_backboneApp/JsLibraries/backbone.js"); bundles.Add(NZBDRONE, new[]{ + APP_PATH + "\\Series\\Index\\IndexLayout.js", APP_PATH + "\\AddSeries\\AddSeriesLayout.js", APP_PATH + "\\Shared\\NotificationView.js", diff --git a/NzbDrone.Web/_backboneApp/Series/Index/IndexLayout.js b/NzbDrone.Web/_backboneApp/Series/Index/IndexLayout.js new file mode 100644 index 000000000..77c91ed24 --- /dev/null +++ b/NzbDrone.Web/_backboneApp/Series/Index/IndexLayout.js @@ -0,0 +1,33 @@ +'use strict;' +/// +/// +/// + +NzbDrone.Series.IndexLayout = Backbone.Marionette.Layout.extend({ + template: 'Series/Index/IndexLayoutTemplate', + route: 'Series/index', + + ui: { + edit: '.edit-series', + delele: '.delete-series' + }, + + regions: { + main: '#series', + }, + + collection: new NzbDrone.Series.SeriesCollection(), + + initialize: function (options) { + + }, + + onRender: function () { + console.log('binding auto complete'); + + this.collection.fetch(); + //Show things + + this.main.show(new NzbDrone.Series.Index.SeriesCollectionView({ collection: this.collection })); + }, +}); \ No newline at end of file diff --git a/NzbDrone.Web/_backboneApp/Series/Index/IndexLayoutTemplate.html b/NzbDrone.Web/_backboneApp/Series/Index/IndexLayoutTemplate.html new file mode 100644 index 000000000..49701c63d --- /dev/null +++ b/NzbDrone.Web/_backboneApp/Series/Index/IndexLayoutTemplate.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/NzbDrone.Web/_backboneApp/Series/Index/SeriesCollectionTemplate.html b/NzbDrone.Web/_backboneApp/Series/Index/SeriesCollectionTemplate.html new file mode 100644 index 000000000..de6e0098e --- /dev/null +++ b/NzbDrone.Web/_backboneApp/Series/Index/SeriesCollectionTemplate.html @@ -0,0 +1,13 @@ + + + + Title + Seasons + Quality + Network + Next Airing + Episodes + + + + \ No newline at end of file diff --git a/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemTemplate.html b/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemTemplate.html new file mode 100644 index 000000000..2ecc4a668 --- /dev/null +++ b/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemTemplate.html @@ -0,0 +1,15 @@ + + + + + + + +{{#if episodeFileCount}} + {{episodeFileCount}} +{{else}} + 0 +{{/if}} + / {{episodeCount}} + +Edit/Delete diff --git a/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemView.js b/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemView.js new file mode 100644 index 000000000..8f0a39293 --- /dev/null +++ b/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemView.js @@ -0,0 +1,30 @@ +'use strict'; +/*global NzbDrone, Backbone*/ +/// +/// +/// + +NzbDrone.Series.Index.SeriesItemView = Backbone.Marionette.ItemView.extend({ + template: 'Series/Index/SeriesItemTemplate', + tagName: 'tr', + + events: { + 'click .x-remove': 'removeSeries', + }, + + onRender: function () { + NzbDrone.ModelBinder.bind(this.model, this.el); + }, + + removeSeries: function () { + this.model.destroy({ wait: true }); + this.model.collection.remove(this.model); + }, +}); + +NzbDrone.Series.Index.SeriesCollectionView = Backbone.Marionette.CompositeView.extend({ + itemView: NzbDrone.Series.Index.SeriesItemView, + template: 'Series/Index/SeriesCollectionTemplate', + tagName: 'table', + className: 'table table-hover', +}); diff --git a/NzbDrone.Web/_backboneApp/Series/SeriesCollection.js b/NzbDrone.Web/_backboneApp/Series/SeriesCollection.js new file mode 100644 index 000000000..c66bd2c21 --- /dev/null +++ b/NzbDrone.Web/_backboneApp/Series/SeriesCollection.js @@ -0,0 +1,7 @@ +/// +/// + +NzbDrone.Series.SeriesCollection = Backbone.Collection.extend({ + url: NzbDrone.Constants.ApiRoot + '/series', + model: NzbDrone.Series.SeriesModel, +}); \ No newline at end of file diff --git a/NzbDrone.Web/_backboneApp/Series/SeriesModel.js b/NzbDrone.Web/_backboneApp/Series/SeriesModel.js index 4d34e7caf..1d0a63ea5 100644 --- a/NzbDrone.Web/_backboneApp/Series/SeriesModel.js +++ b/NzbDrone.Web/_backboneApp/Series/SeriesModel.js @@ -1,10 +1,3 @@ NzbDrone.Series.SeriesModel = Backbone.Model.extend({ - url: NzbDrone.Constants.ApiRoot + '/series' - -}); - - -NzbDrone.Series.SeriesCollection = Backbone.Collection.extend({ - model: NzbDrone.Series.SeriesModel, - url: NzbDrone.Constants.ApiRoot + '/series', -}); + url: NzbDrone.Constants.ApiRoot + '/series' +}); \ No newline at end of file diff --git a/NzbDrone.Web/_backboneApp/app.js b/NzbDrone.Web/_backboneApp/app.js index 67ab85355..e339649ce 100644 --- a/NzbDrone.Web/_backboneApp/app.js +++ b/NzbDrone.Web/_backboneApp/app.js @@ -18,6 +18,7 @@ if (typeof console === undefined) { NzbDrone = new Backbone.Marionette.Application(); NzbDrone.Series = {}; +NzbDrone.Series.Index = {}; NzbDrone.AddSeries = {}; NzbDrone.AddSeries.New = {}; NzbDrone.AddSeries.Existing = {}; @@ -33,7 +34,6 @@ _.templateSettings = { NzbDrone.ModelBinder = new Backbone.ModelBinder(); - NzbDrone.Constants = { ApiRoot: '/api' }; @@ -42,38 +42,37 @@ NzbDrone.Events = { DisplayInMainRegion: 'DisplayInMainRegion' }; - NzbDrone.Controller = Backbone.Marionette.Controller.extend({ addSeries: function (action, query) { NzbDrone.mainRegion.show(new NzbDrone.AddSeries.AddSeriesLayout(this, action, query)); }, + series: function (action, query) { + NzbDrone.mainRegion.show(new NzbDrone.Series.IndexLayout(this, action, query)); + }, notFound: function () { alert('route not found'); } }); - NzbDrone.Router = Backbone.Marionette.AppRouter.extend({ controller: new NzbDrone.Controller(), // "someMethod" must exist at controller.someMethod appRoutes: { + 'series/index': 'series', 'series/add': 'addSeries', 'series/add/:action(/:query)': 'addSeries', ':whatever': 'notFound' - } - }); NzbDrone.addInitializer(function (options) { console.log('starting application'); - NzbDrone.addRegions({ mainRegion: '#main-region', notificationRegion: '#notification-region' @@ -81,6 +80,4 @@ NzbDrone.addInitializer(function (options) { NzbDrone.Router = new NzbDrone.Router(); Backbone.history.start(); - - }); \ No newline at end of file diff --git a/NzbDrone.ncrunchsolution b/NzbDrone.ncrunchsolution index c875ef518..13a4b6131 100644 --- a/NzbDrone.ncrunchsolution +++ b/NzbDrone.ncrunchsolution @@ -1,6 +1,6 @@ 1 - True + False true UseDynamicAnalysis Disabled