From 001cdf4e0a564b97ad585c52043ef0623da328a9 Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Fri, 6 Jan 2017 09:44:19 -0800 Subject: [PATCH] Generate download links inside controller action --- src/Podsync/Controllers/FeedController.cs | 20 ++++++++- .../Services/Builder/CompositeRssBuilder.cs | 4 +- src/Podsync/Services/Builder/IRssBuilder.cs | 4 +- .../Services/Builder/RssBuilderBase.cs | 6 +-- .../Services/Builder/VimeoRssBuilder.cs | 12 +++--- .../Services/Builder/YouTubeRssBuilder.cs | 42 +++++++------------ src/Podsync/Services/Feed/Item.cs | 28 +++++++++---- src/Podsync/Services/Feed/MediaContent.cs | 13 ------ src/Podsync/Services/Videos/Vimeo/Video.cs | 4 ++ .../Services/Videos/Vimeo/VimeoClient.cs | 8 +++- .../Services/Builder/VimeoRssBuilderTests.cs | 8 ++-- .../Builder/YouTubeRssBuilderTests.cs | 10 ++--- .../Services/Feed/FeedSerializationTests.cs | 9 ++-- 13 files changed, 87 insertions(+), 81 deletions(-) delete mode 100644 src/Podsync/Services/Feed/MediaContent.cs diff --git a/src/Podsync/Controllers/FeedController.cs b/src/Podsync/Controllers/FeedController.cs index d477e51..6d36fb0 100644 --- a/src/Podsync/Controllers/FeedController.cs +++ b/src/Podsync/Controllers/FeedController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using System.Xml.Serialization; @@ -22,6 +23,12 @@ namespace Podsync.Controllers [HandleException] public class FeedController : Controller { + private static readonly IDictionary Extensions = new Dictionary + { + ["video/mp4"] = "mp4", + ["audio/mp4"] = "m4a" + }; + private readonly XmlSerializer _serializer = new XmlSerializer(typeof(Rss)); private readonly IRssBuilder _rssBuilder; @@ -94,18 +101,27 @@ namespace Podsync.Controllers try { - rss = await _rssBuilder.Query(Request.GetBaseUrl(), feedId); + rss = await _rssBuilder.Query(feedId); } catch (KeyNotFoundException) { return NotFound(feedId); } + var selfHost = Request.GetBaseUrl(); + // Set atom link to this feed // See https://validator.w3.org/feed/docs/warning/MissingAtomSelfLink.html - var selfLink = new Uri($"{Request.Scheme}://{Request.Host}{Request.Path}"); + var selfLink = new Uri(selfHost, Request.Path); rss.Channels.ForEach(x => x.AtomLink = selfLink); + // No magic here, just make download links to DownloadController.Download + rss.Channels.SelectMany(x => x.Items).ForEach(item => + { + var ext = Extensions[item.ContentType]; + item.DownloadLink = new Uri(selfHost, $"download/{feedId}/{item.Id}.{ext}"); + }); + // Serialize feed to string string body; using (var writer = new Utf8StringWriter()) diff --git a/src/Podsync/Services/Builder/CompositeRssBuilder.cs b/src/Podsync/Services/Builder/CompositeRssBuilder.cs index fea6c53..19add99 100644 --- a/src/Podsync/Services/Builder/CompositeRssBuilder.cs +++ b/src/Podsync/Services/Builder/CompositeRssBuilder.cs @@ -30,12 +30,12 @@ namespace Podsync.Services.Builder get { throw new NotSupportedException(); } } - public override Task Query(Uri baseUrl, string feedId, FeedMetadata feed) + public override Task Query(FeedMetadata feed) { IRssBuilder builder; if (_builders.TryGetValue(feed.Provider, out builder)) { - return builder.Query(baseUrl, feedId, feed); + return builder.Query(feed); } throw new NotSupportedException("Not supported provider"); diff --git a/src/Podsync/Services/Builder/IRssBuilder.cs b/src/Podsync/Services/Builder/IRssBuilder.cs index e6f2586..7eb147f 100644 --- a/src/Podsync/Services/Builder/IRssBuilder.cs +++ b/src/Podsync/Services/Builder/IRssBuilder.cs @@ -10,8 +10,8 @@ namespace Podsync.Services.Builder { Provider Provider { get; } - Task Query(Uri baseUrl, string feedId); + Task Query(string feedId); - Task Query(Uri baseUrl, string feedId, FeedMetadata metadata); + Task Query(FeedMetadata metadata); } } \ No newline at end of file diff --git a/src/Podsync/Services/Builder/RssBuilderBase.cs b/src/Podsync/Services/Builder/RssBuilderBase.cs index ec484dc..f10e24e 100644 --- a/src/Podsync/Services/Builder/RssBuilderBase.cs +++ b/src/Podsync/Services/Builder/RssBuilderBase.cs @@ -17,13 +17,13 @@ namespace Podsync.Services.Builder public abstract Provider Provider { get; } - public async Task Query(Uri baseUrl, string feedId) + public async Task Query(string feedId) { var metadata = await _storageService.Load(feedId); - return await Query(baseUrl, feedId, metadata); + return await Query(metadata); } - public abstract Task Query(Uri baseUrl, string feedId, FeedMetadata metadata); + public abstract Task Query(FeedMetadata metadata); } } \ No newline at end of file diff --git a/src/Podsync/Services/Builder/VimeoRssBuilder.cs b/src/Podsync/Services/Builder/VimeoRssBuilder.cs index e7f45c8..b7e6117 100644 --- a/src/Podsync/Services/Builder/VimeoRssBuilder.cs +++ b/src/Podsync/Services/Builder/VimeoRssBuilder.cs @@ -21,7 +21,7 @@ namespace Podsync.Services.Builder public override Provider Provider { get; } = Provider.Vimeo; - public override async Task Query(Uri baseUrl, string feedId, FeedMetadata metadata) + public override async Task Query(FeedMetadata metadata) { var linkType = metadata.LinkType; @@ -94,17 +94,15 @@ namespace Podsync.Services.Builder { return new Item { + Id = video.Id, Title = video.Title, Description = video.Description, PubDate = video.CreatedAt, Link = video.Link, Duration = video.Duration, - Content = new MediaContent - { - Length = video.Size, - MediaType = "video/mp4", - Url = null - } + FileSize = video.Size, + ContentType = "video/mp4", + Author = video.Author }; } diff --git a/src/Podsync/Services/Builder/YouTubeRssBuilder.cs b/src/Podsync/Services/Builder/YouTubeRssBuilder.cs index 27e85cc..6fccfa7 100644 --- a/src/Podsync/Services/Builder/YouTubeRssBuilder.cs +++ b/src/Podsync/Services/Builder/YouTubeRssBuilder.cs @@ -13,18 +13,16 @@ namespace Podsync.Services.Builder { public class YouTubeRssBuilder : RssBuilderBase { - private readonly ILinkService _linkService; private readonly IYouTubeClient _youTube; - public YouTubeRssBuilder(ILinkService linkService, IYouTubeClient youTube, IStorageService storageService) : base(storageService) + public YouTubeRssBuilder(IYouTubeClient youTube, IStorageService storageService) : base(storageService) { - _linkService = linkService; _youTube = youTube; } public override Provider Provider { get; } = Provider.YouTube; - public override async Task Query(Uri baseUrl, string feedId, FeedMetadata metadata) + public override async Task Query(FeedMetadata metadata) { if (metadata.Provider != Provider.YouTube) { @@ -57,7 +55,7 @@ namespace Podsync.Services.Builder // Get video descriptions var videos = await _youTube.GetVideos(new VideoQuery { Id = string.Join(",", ids) }); - channel.Items = videos.Select(youtubeVideo => MakeItem(youtubeVideo, baseUrl, feedId, metadata)); + channel.Items = videos.Select(youtubeVideo => MakeItem(youtubeVideo, metadata)); var rss = new Rss { @@ -102,46 +100,36 @@ namespace Podsync.Services.Builder }; } - private Item MakeItem(Video video, Uri baseUrl, string feedId, FeedMetadata feed) + private Item MakeItem(Video video, FeedMetadata feed) { - string contentType; - string extension; - GetMediaInfo(feed.Quality, out contentType, out extension); - - var downloadUri = _linkService.Download(baseUrl, feedId, video.VideoId, extension); + string contentType = GetContentType(feed.Quality); return new Item { + Id = video.VideoId, Title = video.Title, Description = video.Description, PubDate = video.PublishedAt, Link = video.Link, Duration = video.Duration, - Content = new MediaContent - { - Length = video.Size, - MediaType = contentType, - Url = downloadUri - } + FileSize = video.Size, + ContentType = contentType }; } - private static void GetMediaInfo(ResolveType resolveType, out string contentType, out string extension) + private static string GetContentType(ResolveType resolveType) { if (resolveType == ResolveType.VideoHigh || resolveType == ResolveType.VideoLow) { - contentType = "video/mp4"; - extension = ".mp4"; + return "video/mp4"; } - else if (resolveType == ResolveType.AudioHigh || resolveType == ResolveType.AudioLow) + + if (resolveType == ResolveType.AudioHigh || resolveType == ResolveType.AudioLow) { - contentType = "audio/mp4"; - extension = ".m4a"; - } - else - { - throw new ArgumentException("Unsupported resolve type"); + return "audio/mp4"; } + + throw new ArgumentException("Unsupported resolve type"); } } } \ No newline at end of file diff --git a/src/Podsync/Services/Feed/Item.cs b/src/Podsync/Services/Feed/Item.cs index dec6dbc..1cbc801 100644 --- a/src/Podsync/Services/Feed/Item.cs +++ b/src/Podsync/Services/Feed/Item.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; @@ -22,7 +23,13 @@ namespace Podsync.Services.Feed public TimeSpan Duration { get; set; } - public MediaContent Content { get; set; } + public string Id { get; set; } + + public long FileSize { get; set; } + + public Uri DownloadLink { get; set; } + + public string ContentType { get; set; } public XmlSchema GetSchema() { @@ -52,17 +59,22 @@ namespace Podsync.Services.Feed writer.WriteStartElement("guid"); writer.WriteAttributeString("isPermaLink", "true"); - writer.WriteString(Link.ToString()); + writer.WriteString(Link?.ToString() ?? Id); writer.WriteEndElement(); /* */ + if (DownloadLink == null) + { + throw new InvalidDataException("Can't generate RSS item with no download link"); + } + writer.WriteStartElement("enclosure"); - writer.WriteAttributeString("url", Content.Url.ToString()); - writer.WriteAttributeString("length", Content.Length.ToString()); - writer.WriteAttributeString("type", Content.MediaType); + writer.WriteAttributeString("url", DownloadLink.ToString()); + writer.WriteAttributeString("length", FileSize.ToString()); + writer.WriteAttributeString("type", ContentType); writer.WriteEndElement(); /* @@ -70,9 +82,9 @@ namespace Podsync.Services.Feed */ writer.WriteStartElement("content", Namespaces.Media); - writer.WriteAttributeString("url", Content.Url.ToString()); - writer.WriteAttributeString("fileSize", Content.Length.ToString()); - writer.WriteAttributeString("type", Content.MediaType); + writer.WriteAttributeString("url", DownloadLink.ToString()); + writer.WriteAttributeString("fileSize", FileSize.ToString()); + writer.WriteAttributeString("type", ContentType); writer.WriteEndElement(); /* diff --git a/src/Podsync/Services/Feed/MediaContent.cs b/src/Podsync/Services/Feed/MediaContent.cs deleted file mode 100644 index 2277eb5..0000000 --- a/src/Podsync/Services/Feed/MediaContent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Podsync.Services.Feed -{ - public struct MediaContent - { - public Uri Url { get; set; } - - public long Length { get; set; } - - public string MediaType { get; set; } - } -} \ No newline at end of file diff --git a/src/Podsync/Services/Videos/Vimeo/Video.cs b/src/Podsync/Services/Videos/Vimeo/Video.cs index e01ac47..aac895f 100644 --- a/src/Podsync/Services/Videos/Vimeo/Video.cs +++ b/src/Podsync/Services/Videos/Vimeo/Video.cs @@ -4,6 +4,8 @@ namespace Podsync.Services.Videos.Vimeo { public class Video { + public string Id { get; set; } + public string Title { get; set; } public string Description { get; set; } @@ -17,5 +19,7 @@ namespace Podsync.Services.Videos.Vimeo public long Size { get; set; } public TimeSpan Duration { get; set; } + + public string Author { get; set; } } } \ No newline at end of file diff --git a/src/Podsync/Services/Videos/Vimeo/VimeoClient.cs b/src/Podsync/Services/Videos/Vimeo/VimeoClient.cs index 6743422..d661e6f 100644 --- a/src/Podsync/Services/Videos/Vimeo/VimeoClient.cs +++ b/src/Podsync/Services/Videos/Vimeo/VimeoClient.cs @@ -101,15 +101,21 @@ namespace Podsync.Services.Videos.Vimeo v.duration.ToObject() * 0.38848958333); + // Extract id from uri like '/videos/50522981' + var uri = v.uri.ToString(); + var id = uri.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[1]; + var video = new Video { + Id = id, Title = v.name, Description = v.description, Link = new Uri(v.link?.ToString()), Thumbnail = new Uri(v.pictures?.sizes[0]?.link?.ToString()), CreatedAt = DateTime.Parse(v.created_time?.ToString()), Duration = TimeSpan.FromSeconds(v.duration?.ToObject()), - Size = size + Size = size, + Author = v.user.name }; output.Add(video); diff --git a/test/Podsync.Tests/Services/Builder/VimeoRssBuilderTests.cs b/test/Podsync.Tests/Services/Builder/VimeoRssBuilderTests.cs index 3ec884e..276fc42 100644 --- a/test/Podsync.Tests/Services/Builder/VimeoRssBuilderTests.cs +++ b/test/Podsync.Tests/Services/Builder/VimeoRssBuilderTests.cs @@ -38,7 +38,7 @@ namespace Podsync.Tests.Services.Builder _storageService.Setup(x => x.Load(feedId)).ReturnsAsync(feed); - var rss = await _builder.Query(new Uri("http://localhost:2020"), feedId); + var rss = await _builder.Query(feedId); Assert.NotEmpty(rss.Channels); @@ -52,12 +52,12 @@ namespace Podsync.Tests.Services.Builder foreach (var item in channel.Items) { + Assert.NotNull(item.Id); Assert.NotNull(item.Title); Assert.NotNull(item.Link); Assert.True(item.Duration.TotalSeconds > 0); - Assert.NotNull(item.Content); - Assert.True(item.Content.Length > 0); - Assert.NotNull(item.Content.MediaType); + Assert.True(item.FileSize > 0); + Assert.NotNull(item.ContentType); Assert.NotNull(item.PubDate); } } diff --git a/test/Podsync.Tests/Services/Builder/YouTubeRssBuilderTests.cs b/test/Podsync.Tests/Services/Builder/YouTubeRssBuilderTests.cs index 4cebaa7..42dbb98 100644 --- a/test/Podsync.Tests/Services/Builder/YouTubeRssBuilderTests.cs +++ b/test/Podsync.Tests/Services/Builder/YouTubeRssBuilderTests.cs @@ -21,7 +21,7 @@ namespace Podsync.Tests.Services.Builder var linkService = new LinkService(); var client = new YouTubeClient(linkService, Options); - _builder = new YouTubeRssBuilder(linkService, client, _storageService.Object); + _builder = new YouTubeRssBuilder(client, _storageService.Object); } [Theory] @@ -41,7 +41,7 @@ namespace Podsync.Tests.Services.Builder _storageService.Setup(x => x.Load(feedId)).ReturnsAsync(feed); - var rss = await _builder.Query(new Uri("http://localhost:2020"), feedId); + var rss = await _builder.Query(feedId); Assert.NotEmpty(rss.Channels); @@ -58,10 +58,8 @@ namespace Podsync.Tests.Services.Builder Assert.NotNull(item.Title); Assert.NotNull(item.Link); Assert.True(item.Duration.TotalSeconds > 0); - Assert.NotNull(item.Content); - Assert.True(item.Content.Length > 0); - Assert.NotNull(item.Content.MediaType); - Assert.NotNull(item.Content.Url); + Assert.True(item.FileSize > 0); + Assert.NotNull(item.ContentType); Assert.NotNull(item.PubDate); } } diff --git a/test/Podsync.Tests/Services/Feed/FeedSerializationTests.cs b/test/Podsync.Tests/Services/Feed/FeedSerializationTests.cs index 1154494..47a0842 100644 --- a/test/Podsync.Tests/Services/Feed/FeedSerializationTests.cs +++ b/test/Podsync.Tests/Services/Feed/FeedSerializationTests.cs @@ -20,12 +20,9 @@ namespace Podsync.Tests.Services.Feed Title = "Steve Gillespie - Getting Arrested (Stand up Comedy)", Link = new Uri("https://youtube.com/watch?v=Jj22gfTnpAI"), PubDate = DateTime.Parse("Mon, 07 Nov 2016 20:02:26 GMT"), - Content = new MediaContent - { - Url = new Uri("http://podsync.net/download/youtube/Jj22gfTnpAI.mp4"), - Length = 52850000, - MediaType = "video/mp4" - }, + DownloadLink = new Uri("http://podsync.net/download/youtube/Jj22gfTnpAI.mp4"), + FileSize = 52850000, + ContentType = "video/mp4", Duration = new TimeSpan(0, 0, 2, 31) };