diff --git a/src/NzbDrone.Api/Calendar/CalendarFeedModule.cs b/src/NzbDrone.Api/Calendar/CalendarFeedModule.cs new file mode 100644 index 000000000..9bdf4ec25 --- /dev/null +++ b/src/NzbDrone.Api/Calendar/CalendarFeedModule.cs @@ -0,0 +1,69 @@ +using Nancy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using DDay.iCal; +using NzbDrone.Core.Tv; +using Nancy.Responses; + +namespace NzbDrone.Api.Calendar +{ + public class CalendarFeedModule : NzbDroneFeedModule + { + private readonly IEpisodeService _episodeService; + + public CalendarFeedModule(IEpisodeService episodeService) + : base("calendar") + { + _episodeService = episodeService; + + Get["/NzbDrone.ics"] = options => GetCalendarFeed(); + } + + private Response GetCalendarFeed() + { + var start = DateTime.Today.Subtract(TimeSpan.FromDays(7)); + var end = DateTime.Today.AddDays(28); + + var queryStart = Request.Query.Start; + var queryEnd = Request.Query.End; + + if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value); + if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value); + + var episodes = _episodeService.EpisodesBetweenDates(start, end); + var icalCalendar = new iCalendar(); + + foreach (var series in episodes.GroupBy(v => v.Series)) + { + foreach (var episode in series) + { + var occurrence = icalCalendar.Create(); + occurrence.UID = "NzbDrone_episode_" + episode.Id.ToString(); + occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative; + occurrence.Start = new iCalDateTime(episode.AirDateUtc.Value); + occurrence.End = new iCalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime)); + occurrence.Description = episode.Overview; + occurrence.Categories = new List() { episode.Series.Network }; + + switch (episode.Series.SeriesType) + { + case SeriesTypes.Daily: + occurrence.Summary = string.Format("{0} - {1}", episode.Series.Title, episode.Title); + break; + + default: + occurrence.Summary = string.Format("{0} - {1}x{2:00} - {3}", episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber, episode.Title); + break; + } + } + } + + var serializer = new DDay.iCal.Serialization.iCalendar.SerializerFactory().Build(icalCalendar.GetType(), new DDay.iCal.Serialization.SerializationContext()) as DDay.iCal.Serialization.IStringSerializer; + var icalendar = serializer.SerializeToString(icalCalendar); + + return new TextResponse(icalendar, "text/calendar"); + } + } +} diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 177d606df..ba75c4bfa 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -40,6 +40,9 @@ 4 + + ..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll + False ..\packages\Microsoft.AspNet.SignalR.Core.1.1.3\lib\net40\Microsoft.AspNet.SignalR.Core.dll @@ -88,6 +91,7 @@ + @@ -139,6 +143,7 @@ + @@ -199,7 +204,9 @@ - + + Designer + diff --git a/src/NzbDrone.Api/NzbDroneFeedModule.cs b/src/NzbDrone.Api/NzbDroneFeedModule.cs new file mode 100644 index 000000000..d79307bef --- /dev/null +++ b/src/NzbDrone.Api/NzbDroneFeedModule.cs @@ -0,0 +1,12 @@ +using Nancy; + +namespace NzbDrone.Api +{ + public abstract class NzbDroneFeedModule : NancyModule + { + protected NzbDroneFeedModule(string resource) + : base("/feed/" + resource.Trim('/')) + { + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/packages.config b/src/NzbDrone.Api/packages.config index bc189533c..d7ee10e3a 100644 --- a/src/NzbDrone.Api/packages.config +++ b/src/NzbDrone.Api/packages.config @@ -1,5 +1,6 @@  + diff --git a/src/UI/Calendar/CalendarFeedView.js b/src/UI/Calendar/CalendarFeedView.js new file mode 100644 index 000000000..1ceb86c01 --- /dev/null +++ b/src/UI/Calendar/CalendarFeedView.js @@ -0,0 +1,16 @@ +'use strict'; +define( + [ + 'marionette', + ], function (Marionette) { + return Marionette.Layout.extend({ + template: 'Calendar/CalendarFeedViewTemplate', + + onRender: function() { + // hackish way to determine the correct url, as using urlBase seems to only work for reverse proxies or so + var ics = '//' + window.location.host + '/feed/calendar/NzbDrone.ics'; + this.$('#ical-url').val(window.location.protocol + ics); + this.$('#ical-subscribe-button').attr('href', 'webcal:' + ics); + } + }); + }); diff --git a/src/UI/Calendar/CalendarFeedViewTemplate.html b/src/UI/Calendar/CalendarFeedViewTemplate.html new file mode 100644 index 000000000..366ec21af --- /dev/null +++ b/src/UI/Calendar/CalendarFeedViewTemplate.html @@ -0,0 +1,26 @@ + + + \ No newline at end of file diff --git a/src/UI/Calendar/CalendarLayout.js b/src/UI/Calendar/CalendarLayout.js index 31d9e4a7f..e2772c3f5 100644 --- a/src/UI/Calendar/CalendarLayout.js +++ b/src/UI/Calendar/CalendarLayout.js @@ -1,10 +1,12 @@ 'use strict'; define( [ + 'AppLayout', 'marionette', 'Calendar/UpcomingCollectionView', - 'Calendar/CalendarView' - ], function (Marionette, UpcomingCollectionView, CalendarView) { + 'Calendar/CalendarView', + 'Calendar/CalendarFeedView' + ], function (AppLayout, Marionette, UpcomingCollectionView, CalendarView, CalendarFeedView) { return Marionette.Layout.extend({ template: 'Calendar/CalendarLayoutTemplate', @@ -12,6 +14,10 @@ define( upcoming: '#x-upcoming', calendar: '#x-calendar' }, + + events: { + 'click .x-ical': '_showiCal' + }, onShow: function () { this._showUpcoming(); @@ -24,6 +30,11 @@ define( _showCalendar: function () { this.calendar.show(new CalendarView()); + }, + + _showiCal: function () { + var view = new CalendarFeedView(); + AppLayout.modalRegion.show(view); } }); }); diff --git a/src/UI/Calendar/CalendarLayoutTemplate.html b/src/UI/Calendar/CalendarLayoutTemplate.html index 37ea4276e..a6bdc92d1 100644 --- a/src/UI/Calendar/CalendarLayoutTemplate.html +++ b/src/UI/Calendar/CalendarLayoutTemplate.html @@ -1,6 +1,13 @@ 
-

Upcoming

+
+

Upcoming

+
+
+

+ +

+
diff --git a/src/UI/Calendar/calendar.less b/src/UI/Calendar/calendar.less index 39a5e284a..8ca64ee2e 100644 --- a/src/UI/Calendar/calendar.less +++ b/src/UI/Calendar/calendar.less @@ -158,3 +158,13 @@ margin-right: 2px; } } + +.ical +{ + color: @btnInverseBackground; +} + +#ical-url +{ + width: 370px; +} \ No newline at end of file diff --git a/src/UI/index.html b/src/UI/index.html index c5ab40f60..14e06ebaf 100644 --- a/src/UI/index.html +++ b/src/UI/index.html @@ -23,6 +23,8 @@ + +