From d2cae4012853bb6457554516f06e5bbf11121b8d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 18 Jan 2014 23:25:01 -0500 Subject: [PATCH] rework news downloading --- MediaBrowser.Api/NewsService.cs | 4 +- MediaBrowser.Controller/News/INewsService.cs | 5 +- MediaBrowser.Model/News/NewsItem.cs | 1 + .../LiveTv/LiveTvManager.cs | 26 +++- ...MediaBrowser.Server.Implementations.csproj | 1 + .../News/NewsEntryPoint.cs | 127 ++++++++++++++++++ .../News/NewsService.cs | 108 ++++----------- .../ApplicationHost.cs | 2 +- 8 files changed, 182 insertions(+), 92 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs diff --git a/MediaBrowser.Api/NewsService.cs b/MediaBrowser.Api/NewsService.cs index 1875db64a..efafc9d3f 100644 --- a/MediaBrowser.Api/NewsService.cs +++ b/MediaBrowser.Api/NewsService.cs @@ -6,7 +6,7 @@ using ServiceStack; namespace MediaBrowser.Api { [Route("/News/Product", "GET")] - [Api(Description = "Gets search hints based on a search term")] + [Api(Description = "Gets the latest product news.")] public class GetProductNews : IReturn> { /// @@ -40,7 +40,7 @@ namespace MediaBrowser.Api StartIndex = request.StartIndex, Limit = request.Limit - }).Result; + }); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Controller/News/INewsService.cs b/MediaBrowser.Controller/News/INewsService.cs index e3d238cc6..8237764df 100644 --- a/MediaBrowser.Controller/News/INewsService.cs +++ b/MediaBrowser.Controller/News/INewsService.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.News; using MediaBrowser.Model.Querying; -using System.Threading.Tasks; namespace MediaBrowser.Controller.News { @@ -13,7 +12,7 @@ namespace MediaBrowser.Controller.News /// Gets the product news. /// /// The query. - /// IEnumerable{NewsItem}. - Task> GetProductNews(NewsQuery query); + /// QueryResult{NewsItem}. + QueryResult GetProductNews(NewsQuery query); } } diff --git a/MediaBrowser.Model/News/NewsItem.cs b/MediaBrowser.Model/News/NewsItem.cs index 6dbe5a79e..181f43db7 100644 --- a/MediaBrowser.Model/News/NewsItem.cs +++ b/MediaBrowser.Model/News/NewsItem.cs @@ -16,6 +16,7 @@ namespace MediaBrowser.Model.News public string Title { get; set; } public string Link { get; set; } public string Description { get; set; } + public string DescriptionHtml { get; set; } public string Guid { get; set; } public DateTime Date { get; set; } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 8577e7510..f62efd9da 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -229,6 +229,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv return result; } + catch (Exception ex) + { + _logger.ErrorException("Error getting recording stream", ex); + + throw; + } finally { _liveStreamSemaphore.Release(); @@ -245,6 +251,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var channel = GetInternalChannel(id); + _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ChannelInfo.Id); + var result = await service.GetChannelStream(channel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(result.Id)) @@ -254,6 +262,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv return result; } + catch (Exception ex) + { + _logger.ErrorException("Error getting channel stream", ex); + + throw; + } finally { _liveStreamSemaphore.Release(); @@ -1261,9 +1275,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + var service = ActiveService; + + _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); + try { - await ActiveService.CloseLiveStream(id, cancellationToken).ConfigureAwait(false); + await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error closing live stream", ex); + + throw; } finally { diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 907301699..855183b97 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -173,6 +173,7 @@ + diff --git a/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs b/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs new file mode 100644 index 000000000..bb6ae5a90 --- /dev/null +++ b/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs @@ -0,0 +1,127 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.News; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; + +namespace MediaBrowser.Server.Implementations.News +{ + public class NewsEntryPoint : IServerEntryPoint + { + private Timer _timer; + private readonly IHttpClient _httpClient; + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + private readonly IJsonSerializer _json; + + private readonly TimeSpan _frequency = TimeSpan.FromHours(24); + + public NewsEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IJsonSerializer json) + { + _httpClient = httpClient; + _appPaths = appPaths; + _fileSystem = fileSystem; + _logger = logger; + _json = json; + } + + public void Run() + { + _timer = new Timer(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency); + } + + /// + /// Called when [timer fired]. + /// + /// The state. + private async void OnTimerFired(object state) + { + var path = Path.Combine(_appPaths.CachePath, "news.json"); + + try + { + await DownloadNews(path).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error downloading news", ex); + } + } + + private async Task DownloadNews(string path) + { + var requestOptions = new HttpRequestOptions + { + Url = "http://mediabrowser3.com/community/index.php?/blog/rss/1-media-browser-developers-blog", + Progress = new Progress() + }; + + using (var stream = await _httpClient.Get(requestOptions).ConfigureAwait(false)) + { + var doc = new XmlDocument(); + doc.Load(stream); + + var news = ParseRssItems(doc).ToList(); + + _json.SerializeToFile(news, path); + } + } + + private IEnumerable ParseRssItems(XmlDocument xmlDoc) + { + var nodes = xmlDoc.SelectNodes("rss/channel/item"); + + if (nodes != null) + { + foreach (XmlNode node in nodes) + { + var newsItem = new NewsItem(); + + newsItem.Title = ParseDocElements(node, "title"); + + newsItem.DescriptionHtml = ParseDocElements(node, "description"); + newsItem.Description = newsItem.DescriptionHtml.StripHtml(); + + newsItem.Link = ParseDocElements(node, "link"); + + var date = ParseDocElements(node, "pubDate"); + DateTime parsedDate; + + if (DateTime.TryParse(date, out parsedDate)) + { + newsItem.Date = parsedDate; + } + + yield return newsItem; + } + } + } + + private string ParseDocElements(XmlNode parent, string xPath) + { + var node = parent.SelectSingleNode(xPath); + + return node != null ? node.InnerText : string.Empty; + } + + public void Dispose() + { + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/News/NewsService.cs b/MediaBrowser.Server.Implementations/News/NewsService.cs index 9849d538a..34da857dd 100644 --- a/MediaBrowser.Server.Implementations/News/NewsService.cs +++ b/MediaBrowser.Server.Implementations/News/NewsService.cs @@ -1,38 +1,46 @@ using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.News; using MediaBrowser.Model.News; using MediaBrowser.Model.Querying; -using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; -using System.Xml; +using MediaBrowser.Model.Serialization; namespace MediaBrowser.Server.Implementations.News { public class NewsService : INewsService { private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly IHttpClient _httpClient; + private readonly IJsonSerializer _json; - public NewsService(IApplicationPaths appPaths, IFileSystem fileSystem, IHttpClient httpClient) + public NewsService(IApplicationPaths appPaths, IJsonSerializer json) { _appPaths = appPaths; - _fileSystem = fileSystem; - _httpClient = httpClient; + _json = json; } - public async Task> GetProductNews(NewsQuery query) + public QueryResult GetProductNews(NewsQuery query) { - var path = Path.Combine(_appPaths.CachePath, "news.xml"); + try + { + return GetProductNewsInternal(query); + } + catch (FileNotFoundException) + { + // No biggie + return new QueryResult + { + Items = new NewsItem[] { } + }; + } + } - await EnsureNewsFile(path).ConfigureAwait(false); + private QueryResult GetProductNewsInternal(NewsQuery query) + { + var path = Path.Combine(_appPaths.CachePath, "news.json"); - var items = GetNewsItems(path); + var items = GetNewsItems(path).OrderByDescending(i => i.Date); var itemsArray = items.ToArray(); var count = itemsArray.Length; @@ -56,77 +64,7 @@ namespace MediaBrowser.Server.Implementations.News private IEnumerable GetNewsItems(string path) { - var xmlDoc = new XmlDocument(); - - xmlDoc.Load(path); - - return ParseRssItems(xmlDoc); - } - - private IEnumerable ParseRssItems(XmlDocument xmlDoc) - { - var nodes = xmlDoc.SelectNodes("rss/channel/item"); - - if (nodes == null) - { - yield return null; - } - - foreach (XmlNode node in nodes) - { - var newsItem = new NewsItem(); - - newsItem.Title = ParseDocElements(node, "title"); - - newsItem.Description = ParseDocElements(node, "description"); - - newsItem.Link = ParseDocElements(node, "link"); - - var date = ParseDocElements(node, "pubDate"); - DateTime parsedDate; - - if (DateTime.TryParse(date, out parsedDate)) - { - newsItem.Date = parsedDate; - } - - yield return newsItem; - } - } - - private string ParseDocElements(XmlNode parent, string xPath) - { - var node = parent.SelectSingleNode(xPath); - - return node != null ? node.InnerText : "Unresolvable"; - } - - - /// - /// Ensures the news file. - /// - /// The path. - /// Task. - private async Task EnsureNewsFile(string path) - { - var info = _fileSystem.GetFileSystemInfo(path); - - if (!info.Exists || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(info)).TotalHours > 12) - { - var requestOptions = new HttpRequestOptions - { - Url = "http://mediabrowser3.com/community/index.php?/blog/rss/1-media-browser-developers-blog", - Progress = new Progress() - }; - - using (var stream = await _httpClient.Get(requestOptions).ConfigureAwait(false)) - { - using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) - { - await stream.CopyToAsync(fileStream).ConfigureAwait(false); - } - } - } + return _json.DeserializeFromFile>(path); } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 13e33c7a3..2858a781e 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -285,7 +285,7 @@ namespace MediaBrowser.ServerApplication DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor); RegisterSingleInstance(DtoService); - var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, FileSystemManager, HttpClient); + var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); RegisterSingleInstance(newsService); progress.Report(15);