diff --git a/src/Podsync/Services/Feed/Channel.cs b/src/Podsync/Services/Feed/Channel.cs new file mode 100644 index 0000000..589105e --- /dev/null +++ b/src/Podsync/Services/Feed/Channel.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using Shared; + +namespace Podsync.Services.Feed +{ + public class Channel : IXmlSerializable + { + private const string PodsyncGeneratorName = "Podsync Generator"; + + public Channel() + { + Items = Enumerable.Empty(); + } + + public string Title { get; set; } + + public string Description { get; set; } + + public string Link { get; set; } + + public DateTime LastBuildDate { get; set; } + + public DateTime PubDate { get; set; } + + public string Subtitle { get; set; } + + public string Summary { get; set; } + + public string Category { get; set; } + + public string Image { get; set; } + + public string Thumbnail { get; set; } + + public IEnumerable Items { get; set; } + + public XmlSchema GetSchema() + { + return null; + } + + public void ReadXml(XmlReader reader) + { + throw new NotSupportedException("Reading is not supported"); + } + + public void WriteXml(XmlWriter writer) + { + writer.WriteElementString("title", Title); + writer.WriteElementString("description", Description); + writer.WriteElementString("link", Link); + + writer.WriteElementString("generator", PodsyncGeneratorName); + + writer.WriteElementString("lastBuildDate", LastBuildDate.ToString("R")); + writer.WriteElementString("pubDate", PubDate.ToString("R")); + + /* + Laugh Factory + The best stand up comedy clips online. That's it. + */ + + writer.WriteElementString("subtitle", Namespaces.Itunes, Title); + writer.WriteElementString("summary", Namespaces.Itunes, Summary); + + /* + + */ + + writer.WriteStartElement("category", Namespaces.Itunes); + writer.WriteAttributeString("text", Category); + writer.WriteEndElement(); + + /* + + */ + + writer.WriteStartElement("image", Namespaces.Itunes); + writer.WriteAttributeString("href", Image); + writer.WriteEndElement(); + + /* + + */ + + writer.WriteStartElement("thumbnail", Namespaces.Media); + writer.WriteAttributeString("url", Thumbnail); + writer.WriteEndElement(); + + // Items + var serializer = new XmlSerializer(typeof(Item)); + Items.ForEach(item => serializer.Serialize(writer, item)); + } + } +} \ No newline at end of file diff --git a/src/Podsync/Services/Feed/Item.cs b/src/Podsync/Services/Feed/Item.cs new file mode 100644 index 0000000..84bce54 --- /dev/null +++ b/src/Podsync/Services/Feed/Item.cs @@ -0,0 +1,85 @@ + +using System; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace Podsync.Services.Feed +{ + public class Item : IXmlSerializable + { + public string Title { get; set; } + + public string Description { get; set; } + + public string Author { get; set; } + + public string Link { get; set; } + + public DateTime PubDate { get; set; } + + public string Summary { get; set; } + + public TimeSpan Duration { get; set; } + + public MediaContent Content { get; set; } + + public XmlSchema GetSchema() + { + return null; + } + + public void ReadXml(XmlReader reader) + { + throw new NotSupportedException("Reading is not supported"); + } + + public void WriteXml(XmlWriter writer) + { + writer.WriteElementString("title", Title); + writer.WriteElementString("description", Description); + writer.WriteElementString("link", Link); + writer.WriteElementString("pubDate", PubDate.ToString("R")); + writer.WriteElementString("author", Author); + + /* + https://youtube.com/watch?v=yp202t46OIE + */ + + writer.WriteStartElement("guid"); + writer.WriteAttributeString("isPermaLink", "true"); + writer.WriteString(Link); + writer.WriteEndElement(); + + /* + + */ + + writer.WriteStartElement("enclosure"); + writer.WriteAttributeString("url", Content.Url); + writer.WriteAttributeString("length", Content.Length.ToString()); + writer.WriteAttributeString("type", Content.MediaType); + writer.WriteEndElement(); + + /* + + */ + + writer.WriteStartElement("content", Namespaces.Media); + writer.WriteAttributeString("url", Content.Url); + writer.WriteAttributeString("fileSize", Content.Length.ToString()); + writer.WriteAttributeString("type", Content.MediaType); + writer.WriteEndElement(); + + /* + Mike E. Winfield - Cheating (Stand up comedy) + ... + 00:02:18 + */ + + writer.WriteElementString("subtitle", Namespaces.Itunes, Title); + writer.WriteElementString("summary", Namespaces.Itunes, Summary); + writer.WriteElementString("duration", Namespaces.Itunes, Duration.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Podsync/Services/Feed/MediaContent.cs b/src/Podsync/Services/Feed/MediaContent.cs new file mode 100644 index 0000000..6f54fd3 --- /dev/null +++ b/src/Podsync/Services/Feed/MediaContent.cs @@ -0,0 +1,11 @@ +namespace Podsync.Services.Feed +{ + public struct MediaContent + { + public string 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/Feed/Namespaces.cs b/src/Podsync/Services/Feed/Namespaces.cs new file mode 100644 index 0000000..1ebec12 --- /dev/null +++ b/src/Podsync/Services/Feed/Namespaces.cs @@ -0,0 +1,15 @@ +namespace Podsync.Services.Feed +{ + public static class Namespaces + { + public const string Itunes = "http://www.itunes.com/dtds/podcast-1.0.dtd"; + + public const string Media = "http://search.yahoo.com/mrss/"; + + public const string Atom = "http://www.w3.org/2005/Atom"; + + public const string Content = "http://purl.org/rss/1.0/modules/content/"; + + public const string Dc = "http://purl.org/dc/elements/1.1/"; + } +} \ No newline at end of file diff --git a/src/Podsync/Services/Feed/Rss.cs b/src/Podsync/Services/Feed/Rss.cs new file mode 100644 index 0000000..d421c4f --- /dev/null +++ b/src/Podsync/Services/Feed/Rss.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using Shared; + +namespace Podsync.Services.Feed +{ + public class Rss : IXmlSerializable + { + public const string Version = "2.0"; + + public IEnumerable Channels { get; set; } + + public Rss() + { + Channels = Enumerable.Empty(); + } + + public XmlSchema GetSchema() + { + return null; + } + + public void ReadXml(XmlReader reader) + { + throw new NotSupportedException("Reading is not supported"); + } + + public void WriteXml(XmlWriter writer) + { + writer.WriteStartElement("rss"); + + writer.WriteAttributeString("xmlns", "dc", null, Namespaces.Dc); + writer.WriteAttributeString("xmlns", "content", null, Namespaces.Content); + writer.WriteAttributeString("xmlns", "atom", null, Namespaces.Atom); + writer.WriteAttributeString("xmlns", "itunes", null, Namespaces.Itunes); + writer.WriteAttributeString("xmlns", "media", null, Namespaces.Media); + + writer.WriteAttributeString("version", Version); + + var serializer = new XmlSerializer(typeof(Channel)); + Channels.ForEach(channel => serializer.Serialize(writer, channel)); + + writer.WriteEndElement(); + } + } +} \ No newline at end of file diff --git a/src/Podsync/project.json b/src/Podsync/project.json index 0136796..404bf7d 100644 --- a/src/Podsync/project.json +++ b/src/Podsync/project.json @@ -1,72 +1,70 @@ -{ - "dependencies": { - "Google.Apis.YouTube.v3": "1.16.0.582", - "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", - "Microsoft.AspNetCore.Razor.Tools": { - "version": "1.0.0-preview2-final", - "type": "build" - }, - "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", - "Microsoft.AspNetCore.StaticFiles": "1.0.0", - "Microsoft.AspNetCore.WebUtilities": "1.0.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", - "Microsoft.Extensions.Configuration.Json": "1.0.0", - "Microsoft.Extensions.Configuration.UserSecrets": "1.0.0", - "Microsoft.Extensions.Logging": "1.0.0", - "Microsoft.Extensions.Logging.Console": "1.0.0", - "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", - "Microsoft.NETCore.App": { - "version": "1.0.0", - "type": "platform" - }, - "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0" +{ + "dependencies": { + "Google.Apis.YouTube.v3": "1.16.0.582", + "Microsoft.AspNetCore.Diagnostics": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Razor.Tools": { + "version": "1.0.0-preview2-final", + "type": "build" }, - - "tools": { - "BundlerMinifier.Core": "2.0.238", - "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", - "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" + "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.StaticFiles": "1.0.0", + "Microsoft.AspNetCore.WebUtilities": "1.0.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", + "Microsoft.Extensions.Configuration.Json": "1.0.0", + "Microsoft.Extensions.Configuration.UserSecrets": "1.0.0", + "Microsoft.Extensions.Logging": "1.0.0", + "Microsoft.Extensions.Logging.Console": "1.0.0", + "Microsoft.Extensions.Logging.Debug": "1.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" }, - - "frameworks": { - "netcoreapp1.0": { - "imports": [ - "dotnet5.6", - "portable-net45+win8" - ] - } - }, - - "buildOptions": { - "emitEntryPoint": true, - "preserveCompilationContext": true, - - "compile": "..\\Shared\\*.cs" - }, - - "runtimeOptions": { - "configProperties": { - "System.GC.Server": true - } - }, - - "publishOptions": { - "include": [ - "wwwroot", - "Views", - "Areas/**/Views", - "appsettings.json", - "web.config" - ] - }, - - "scripts": { - "prepublish": [ "bower install", "dotnet bundle" ], - "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] - }, - - "userSecretsId": "aspnet-Podsync-20161004104901" -} + "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0", + "System.Xml.XmlSerializer": "4.0.11" + }, + "tools": { + "BundlerMinifier.Core": "2.0.238", + "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" + }, + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dotnet5.6", + "portable-net45+win8" + ] + } + }, + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true, + "compile": "..\\Shared\\*.cs" + }, + "runtimeOptions": { + "configProperties": { + "System.GC.Server": true + } + }, + "publishOptions": { + "include": [ + "wwwroot", + "Views", + "Areas/**/Views", + "appsettings.json", + "web.config" + ] + }, + "scripts": { + "prepublish": [ + "bower install", + "dotnet bundle" + ], + "postpublish": [ + "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" + ] + }, + "userSecretsId": "aspnet-Podsync-20161004104901" +} \ No newline at end of file diff --git a/test/Podsync.Tests/Services/Feed/FeedSerializationTests.cs b/test/Podsync.Tests/Services/Feed/FeedSerializationTests.cs new file mode 100644 index 0000000..7f03ba6 --- /dev/null +++ b/test/Podsync.Tests/Services/Feed/FeedSerializationTests.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Xml.Serialization; +using Podsync.Services.Feed; +using Xunit; + +namespace Podsync.Tests.Services.Feed +{ + public class FeedSerializationTests + { + private const string ImageUrl = "https://yt3.ggpht.com/-OOqFwHRjeMQ/AAAAAAAAAAI/AAAAAAAAAAA/0XQZ2NeGp_0/s88-c-k-no-mo-rj-c0xffffff/photo.jpg"; + + [Fact] + public void SerializeFeedTest() + { + var feed = new Rss(); + + var item = new Item + { + Title = "Steve Gillespie - Getting Arrested (Stand up Comedy)", + Link = "https://youtube.com/watch?v=Jj22gfTnpAI", + PubDate = DateTime.Parse("Mon, 07 Nov 2016 20:02:26 GMT"), + Content = new MediaContent + { + Url = "http://podsync.net/download/youtube/Jj22gfTnpAI.mp4", + Length = 52850000, + MediaType = "video/mp4" + }, + Duration = new TimeSpan(0, 0, 2, 31) + }; + + var channel = new Channel + { + Title = "Laugh Factory", + Description = "The best stand up comedy clips online. That's it.", + Link = "https://youtube.com/channel/UCxyCzPY2pjAjrxoSYclpuLg", + LastBuildDate = DateTime.Parse("Tue, 08 Nov 2016 05:55:25 GMT"), + PubDate = DateTime.Parse("Mon, 31 Jul 2006 22:18:05 GMT"), + Subtitle = "Laugh Factory", + Summary = "The best stand up comedy clips online. That's it.", + Category = "TV & Film", + Image = ImageUrl, + Thumbnail = ImageUrl, + + Items = new[] + { + item + } + }; + + feed.Channels = new[] + { + channel + }; + + var serializer = new XmlSerializer(typeof(Rss)); + + string body; + using (var writer = new StringWriter()) + { + serializer.Serialize(writer, feed); + body = writer.ToString(); + } + + Assert.NotEmpty(body); + + // Channel tests + + Assert.Contains("Laugh Factory", body); + Assert.Contains("The best stand up comedy clips online. That's it.", body); + Assert.Contains("https://youtube.com/channel/UCxyCzPY2pjAjrxoSYclpuLg", body); + Assert.Contains("Podsync Generator", body); + + Assert.Contains("Laugh Factory", body); + Assert.Contains("The best stand up comedy clips online. That's it.", body); + Assert.Contains("", body); + Assert.Contains($"", body); + Assert.Contains($"", body); + + // Items tests + Assert.Contains("Steve Gillespie - Getting Arrested (Stand up Comedy)", body); + Assert.Contains("Steve Gillespie - Getting Arrested (Stand up Comedy)", body); + + Assert.Contains("https://youtube.com/watch?v=Jj22gfTnpAI", body); + Assert.Contains("https://youtube.com/watch?v=Jj22gfTnpAI", body); + + Assert.Contains("00:02:31", body); + + Assert.Contains("", body); + Assert.Contains("", body); + } + } +} \ No newline at end of file diff --git a/test/Podsync.Tests/project.json b/test/Podsync.Tests/project.json index fc85f42..c76f124 100644 --- a/test/Podsync.Tests/project.json +++ b/test/Podsync.Tests/project.json @@ -12,6 +12,7 @@ }, "Moq": "4.6.38-alpha", "Podsync": "1.0.0-*", + "System.Xml.XmlSerializer": "4.0.11", "xunit": "2.2.0-beta2-build3300" },