Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve error when visualizing recent shows #174

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Ch9/Ch9.Droid/Ch9.Droid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<AndroidUseAapt2>true</AndroidUseAapt2>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<AndroidUseIntermediateDesignerFile>True</AndroidUseIntermediateDesignerFile>
<ResourcesDirectory>..\Ch9.Shared\Strings</ResourcesDirectory>
Expand Down
250 changes: 218 additions & 32 deletions src/Ch9/Ch9.Shared/Domain/ShowService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,30 @@
using Newtonsoft.Json;
using Uno.Extensions;
using Uno.Logging;
using System.Globalization;

namespace Ch9
{
public class ShowService : IShowService
{
private const string YahooNamespace = "http://search.yahoo.com/mrss/";
private const string ITunesNamespace = "http://www.itunes.com/dtds/podcast-1.0.dtd";
private static readonly SourceFeed _channel9Feed = new SourceFeed("https://s.ch9.ms/feeds/rss", "Channel 9");
// private static readonly SourceFeed _channel9Feed = new SourceFeed("https://s.ch9.ms/feeds/rss", "Channel 9");
private static readonly SourceFeed _channel9Feed = new SourceFeed("https://learntvpublicschedule.azureedge.net/public/schedule.json", "Microsoft Learn Shows (ex-Channel 9)");
private const string LearnTvLogo = "https://static.docs.com/third-party/learn-player/v1.0.0/images/Learn_Thumbnail_v2_1920x1080.jpg";


private readonly IDictionary<string, Show> _cache = new Dictionary<string, Show>();

private readonly HttpClient _httpClient;
private readonly HttpClient _httpClient;

public ShowService(HttpClient httpClient)
public ShowService(HttpClient httpClient)
{
_httpClient = httpClient;
}
_httpClient = httpClient;
}

/// <inheritdoc/>
private IEnumerable<SourceFeed> GetFallbackShowFeeds()
private IEnumerable<SourceFeed> GetFallbackShowFeeds()
{
return new List<SourceFeed>
{
Expand All @@ -59,33 +63,33 @@ private IEnumerable<SourceFeed> GetFallbackShowFeeds()

public async Task<IEnumerable<SourceFeed>> GetShowFeeds()
{
// If any exception occurs, fallback to the list of hardcoded shows
try
{
var response = await _httpClient.GetStringAsync("api/rssfeeds");

return JsonConvert.DeserializeObject<SourceFeed[]>(response);
}
catch (Exception e)
{
if (!IsInternetAvailable()) throw;

this.Log().Warn("Couldn't load the shows. Fallbacking on the default shows.", e);
return GetFallbackShowFeeds();
}

bool IsInternetAvailable()
{
var profile = NetworkInformation.GetInternetConnectionProfile();
var level = profile?.GetNetworkConnectivityLevel();
return level == NetworkConnectivityLevel.InternetAccess;
}
}
// If any exception occurs, fallback to the list of hardcoded shows
try
{
var response = await _httpClient.GetStringAsync("api/rssfeeds");

return JsonConvert.DeserializeObject<SourceFeed[]>(response);
}
catch (Exception e)
{
if (!IsInternetAvailable()) throw;

this.Log().Warn("Couldn't load the shows. Fallbacking on the default shows.", e);
return GetFallbackShowFeeds();
}

bool IsInternetAvailable()
{
var profile = NetworkInformation.GetInternetConnectionProfile();
var level = profile?.GetNetworkConnectivityLevel();
return level == NetworkConnectivityLevel.InternetAccess;
}
}

/// <inheritdoc/>
public async Task<Show> GetShow(SourceFeed sourceFeed = null)
{
var url = sourceFeed != null ? sourceFeed.FeedUrl : _channel9Feed.FeedUrl;
var url = sourceFeed != null ? sourceFeed.FeedUrl : _channel9Feed.FeedUrl;

if (_cache.TryGetValue(url, out var cachedShow))
{
Expand Down Expand Up @@ -129,18 +133,200 @@ private IEnumerable<Episode> GetEpisodes(SourceFeed sourceFeed, SyndicationFeed

private async Task<SyndicationFeed> GetRssFeed(string url)
{
using (var reader = await HttpUtility.GetXmlReader(url))
using (var reader = await HttpUtility.GetJsonReader(url))
{
var feedItems = await ReadFeed(reader);
SyndicationFeed feed = new SyndicationFeed(feedItems);
feed.Description = SyndicationContent.CreatePlaintextContent("Microsoft Learn TV");
return feed;
}
}

private static async Task<List<SyndicationItem>> ReadFeed(JsonTextReader reader)
{
List<SyndicationItem> feedItems = new List<SyndicationItem>();
SyndicationItem currentItem = null;
String currentProperty = null;
String title = null;
String externalTitle = null;
DateTimeOffset? startTime = null;
DateTimeOffset? endTime = null;
Boolean? isLive = null;
bool? inArray = null;

// The url returns an Object with a property named "content" which is an Array
while (await reader.ReadAsync())
{
if (inArray == null)
{
if (reader.TokenType == JsonToken.StartArray)
{
inArray = true;
}

continue;
} else if (inArray == false) {
continue;
} else if (inArray == true) {
if (reader.TokenType == JsonToken.EndArray) {
inArray = false;
continue;
}
}

switch (reader.TokenType)
{
case JsonToken.StartObject:
currentItem = new SyndicationItem();
break;
case JsonToken.EndObject:
CompleteFeedItem(currentItem, title, externalTitle, startTime, endTime, isLive);

startTime = null;
endTime = null;
isLive = null;

feedItems.Add(currentItem);
break;
case JsonToken.PropertyName:
currentProperty = reader.Value.ToString();
break;
case JsonToken.Integer:
if(currentProperty == "pubble_id")
{
currentItem.Id = reader.Value.ToString();
}
break;
case JsonToken.String:
switch (currentProperty)
{
case "title": {
title = reader.Value.ToString();
break;
}
case "externaltitle": {
externalTitle = reader.Value.ToString();
break;
}
case "description": {
currentItem.Summary = SyndicationContent.CreatePlaintextContent(reader.Value.ToString());
break;
}
case "externalurl": {
currentItem.BaseUri = new Uri(reader.Value.ToString());
break;
}
default:
break;
}
break;
case JsonToken.Boolean:
if (currentProperty == "islive")
{
isLive = (Boolean)reader.Value;
}
break;
case JsonToken.Date:
switch (currentProperty)
{
case "start_time":
{
startTime = DateTimeOffset.Parse(reader.Value.ToString());
currentItem.PublishDate = startTime.Value;
break;
}
case "end_time":
{
endTime = DateTimeOffset.Parse(reader.Value.ToString());
break;
}
default:
break;
}
break;
// Commented out because not necessary, but can be useful for future improvements
//case JsonToken.StartArray:
// inArray = true;
// break;
//case JsonToken.EndArray:
// inArray = false;
// break;
//case JsonToken.Undefined:
// break;
//case JsonToken.None:
// break;
//case JsonToken.Null:
// break;
//case JsonToken.StartConstructor:
// break;
//case JsonToken.Float:
// break;
//case JsonToken.Comment:
// break;
//case JsonToken.Raw:
// break;
//case JsonToken.EndConstructor:
// break;
//case JsonToken.Bytes:
// break;
default:
break;
}
}

return feedItems;
}

private static void CompleteFeedItem(SyndicationItem currentItem, String title, String externalTitle, DateTimeOffset? startTime, DateTimeOffset? endTime, bool? isLive)
{
if (title == null)
{
title = externalTitle;
}

String details = "";
if (isLive != null)
{
details += $"Is Live Now: {isLive.Value}";
}
if (startTime != null)
{
details += (String.IsNullOrWhiteSpace(details) ? "" : " - ") + $"Starts At: {startTime.Value.ToString(CultureInfo.CurrentCulture)}";
}
if (endTime != null)
{
return SyndicationFeed.Load(reader);
details += (String.IsNullOrWhiteSpace(details) ? "" : " - ") + $"Ends At: {endTime.Value.ToString(CultureInfo.CurrentCulture)}";
}

if (!String.IsNullOrWhiteSpace(details))
{
currentItem.Content = SyndicationContent.CreateHtmlContent(details);
currentItem.ElementExtensions.Add(new SyndicationElementExtension("summary", ITunesNamespace, details));
}

TimeSpan? duration = null;
if (startTime.HasValue && endTime.HasValue)
{
duration = endTime.Value - startTime.Value;
}

currentItem.Title = SyndicationContent.CreatePlaintextContent($"{(startTime.HasValue ? (startTime.Value.ToString(CultureInfo.CurrentCulture) + ": ") : "")}{title}|");

currentItem.ElementExtensions.Add(new SyndicationElementExtension("duration", ITunesNamespace, duration.HasValue ? (long)duration.Value.TotalSeconds : 0L));
currentItem.Links.Add(new SyndicationLink(currentItem.BaseUri, "alternate", currentItem.Title.Text, "", duration.HasValue ? (long)duration.Value.TotalSeconds : 0L));
currentItem.Links.Add(new SyndicationLink(currentItem.BaseUri, "", currentItem.Title.Text, "video/mp4", duration.HasValue ? (long)duration.Value.TotalSeconds : 0L));

var thumbnail = new XElement(XName.Get("thumbnail"));
thumbnail.SetAttributeValue(XName.Get("url"), LearnTvLogo);
currentItem.ElementExtensions.Add("thumbnail", YahooNamespace, thumbnail);
}

private Episode CreateEpisode(SyndicationItem item, SourceFeed sourceFeed)
{
return new Episode
{
Title = GetTitle(item),
Show = GetShow(item, sourceFeed),
Show = GetShow(item, sourceFeed),
Summary = GetSummary(item),
Date = item.PublishDate,
Categories = GetCategories(item).ToArray(),
Expand Down
30 changes: 22 additions & 8 deletions src/Ch9/Ch9.Shared/Framework/HttpUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,51 @@
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml;
using Newtonsoft.Json;

namespace Ch9
{
internal static class HttpUtility
{
internal static HttpClient HttpClient { get; } = CreateHttpClient();
internal static HttpClient HttpClient { get; } = CreateHttpClient();

internal static HttpClient CreateHttpClient()
{
#if __WASM__
var httpClient = new HttpClient(new Uno.UI.Wasm.WasmHttpHandler());

httpClient.DefaultRequestHeaders.Add("origin", "");
httpClient.DefaultRequestHeaders.Add("origin", "");
#else
var httpClient = new HttpClient();
var httpClient = new HttpClient();
#endif
return httpClient;
}
return httpClient;
}

internal static async Task<XmlReader> GetXmlReader(string url)
internal static async Task<XmlReader> GetXmlReader(string url)
{
#if __WASM__
url = "https://ch9-app.azurewebsites.net/api/proxy?url=" + url;
url = "https://ch9-app.azurewebsites.net/api/proxy?url=" + url;
#endif
using (var response = await HttpClient.GetAsync(url))
using (var response = await HttpClient.GetAsync(url))
{
response.EnsureSuccessStatusCode();
var bytes = await response.Content.ReadAsByteArrayAsync();
var stream = new MemoryStream(bytes);
return XmlReader.Create(stream);
}
}

internal static async Task<JsonTextReader> GetJsonReader(string url)
{
using (var response = await HttpClient.GetAsync(url))
{
response.EnsureSuccessStatusCode();
var bytes = await response.Content.ReadAsByteArrayAsync();
var stream = new MemoryStream(bytes);
TextReader reader = new StreamReader(stream);
JsonTextReader jsonReader = new JsonTextReader(reader);
return jsonReader;
}
}
}
}
4 changes: 2 additions & 2 deletions src/Ch9/Ch9.UWP/Ch9.UWP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
you need to make sure that the version provided here matches https://github.com/onovotny/MSBuildSdkExtras/blob/master/Source/MSBuild.Sdk.Extras/DefaultItems/ImplicitPackages.targets#L11.
This is not an issue when libraries are referenced through nuget packages. See https://github.com/unoplatform/uno/issues/446 for more details.
-->
<Version>6.2.8</Version>
<Version>6.2.9</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
Expand All @@ -145,7 +145,7 @@
<PackageReference Include="WindowsStateTriggers" Version="1.1.0" />
<PackageReference Include="Microsoft.Toolkit.Uwp.UI" Version="6.0.0" />
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls" Version="6.0.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.2.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.5.3.2" />
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="3.2.2" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="3.2.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
Expand Down