Add contracts for generating RSS feeds

This commit is contained in:
Maksym Pavlenko
2016-11-08 14:21:37 -08:00
parent 2842cd68d3
commit c39dd14cc3
8 changed files with 423 additions and 70 deletions
+100
View File
@@ -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<Item>();
}
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<Item> 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"));
/*
<itunes:subtitle>Laugh Factory</itunes:subtitle>
<itunes:summary>The best stand up comedy clips online. That's it.</itunes:summary>
*/
writer.WriteElementString("subtitle", Namespaces.Itunes, Title);
writer.WriteElementString("summary", Namespaces.Itunes, Summary);
/*
<itunes:category text="TV & Film"/>
*/
writer.WriteStartElement("category", Namespaces.Itunes);
writer.WriteAttributeString("text", Category);
writer.WriteEndElement();
/*
<itunes:image href="https://yt3.ggpht.com/photo.jpg"/>
*/
writer.WriteStartElement("image", Namespaces.Itunes);
writer.WriteAttributeString("href", Image);
writer.WriteEndElement();
/*
<media:thumbnail url="https://yt3.ggpht.com//photo.jpg"/>
*/
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));
}
}
}
+85
View File
@@ -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);
/*
<guid isPermaLink="true">https://youtube.com/watch?v=yp202t46OIE</guid>
*/
writer.WriteStartElement("guid");
writer.WriteAttributeString("isPermaLink", "true");
writer.WriteString(Link);
writer.WriteEndElement();
/*
<enclosure url="http://podsync.net/download/youtube/yp202t46OIE.mp4" length="48300000" type="video/mp4"/>
*/
writer.WriteStartElement("enclosure");
writer.WriteAttributeString("url", Content.Url);
writer.WriteAttributeString("length", Content.Length.ToString());
writer.WriteAttributeString("type", Content.MediaType);
writer.WriteEndElement();
/*
<media:content url="http://podsync.net/download/youtube/yp202t46OIE.mp4" fileSize="48300000" type="video/mp4"/>
*/
writer.WriteStartElement("content", Namespaces.Media);
writer.WriteAttributeString("url", Content.Url);
writer.WriteAttributeString("fileSize", Content.Length.ToString());
writer.WriteAttributeString("type", Content.MediaType);
writer.WriteEndElement();
/*
<itunes:subtitle>Mike E. Winfield - Cheating (Stand up comedy)</itunes:subtitle>
<itunes:summary>...</itunes:summary>
<itunes:duration>00:02:18</itunes:duration>
*/
writer.WriteElementString("subtitle", Namespaces.Itunes, Title);
writer.WriteElementString("summary", Namespaces.Itunes, Summary);
writer.WriteElementString("duration", Namespaces.Itunes, Duration.ToString());
}
}
}
+11
View File
@@ -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; }
}
}
+15
View File
@@ -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/";
}
}
+50
View File
@@ -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<Channel> Channels { get; set; }
public Rss()
{
Channels = Enumerable.Empty<Channel>();
}
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();
}
}
}
+68 -70
View File
@@ -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"
}
@@ -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("<title>Laugh Factory</title>", body);
Assert.Contains("<description>The best stand up comedy clips online. That's it.</description>", body);
Assert.Contains("<link>https://youtube.com/channel/UCxyCzPY2pjAjrxoSYclpuLg</link>", body);
Assert.Contains("<generator>Podsync Generator</generator>", body);
Assert.Contains("<itunes:subtitle>Laugh Factory</itunes:subtitle>", body);
Assert.Contains("<itunes:summary>The best stand up comedy clips online. That's it.</itunes:summary>", body);
Assert.Contains("<itunes:category text=\"TV &amp; Film\" />", body);
Assert.Contains($"<itunes:image href=\"{ImageUrl}\" />", body);
Assert.Contains($"<media:thumbnail url=\"{ImageUrl}\" />", body);
// Items tests
Assert.Contains("<title>Steve Gillespie - Getting Arrested (Stand up Comedy)</title>", body);
Assert.Contains("<itunes:subtitle>Steve Gillespie - Getting Arrested (Stand up Comedy)</itunes:subtitle>", body);
Assert.Contains("<link>https://youtube.com/watch?v=Jj22gfTnpAI</link>", body);
Assert.Contains("<guid isPermaLink=\"true\">https://youtube.com/watch?v=Jj22gfTnpAI</guid>", body);
Assert.Contains("<itunes:duration>00:02:31</itunes:duration>", body);
Assert.Contains("<enclosure url=\"http://podsync.net/download/youtube/Jj22gfTnpAI.mp4\" length=\"52850000\" type=\"video/mp4\" />", body);
Assert.Contains("<media:content url=\"http://podsync.net/download/youtube/Jj22gfTnpAI.mp4\" fileSize=\"52850000\" type=\"video/mp4\" />", body);
}
}
}
+1
View File
@@ -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"
},