diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index a04aca2a03..a46f82ae05 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -93,6 +93,7 @@ + diff --git a/MediaBrowser.Api/NewsService.cs b/MediaBrowser.Api/NewsService.cs new file mode 100644 index 0000000000..1875db64a5 --- /dev/null +++ b/MediaBrowser.Api/NewsService.cs @@ -0,0 +1,48 @@ +using MediaBrowser.Controller.News; +using MediaBrowser.Model.News; +using MediaBrowser.Model.Querying; +using ServiceStack; + +namespace MediaBrowser.Api +{ + [Route("/News/Product", "GET")] + [Api(Description = "Gets search hints based on a search term")] + public class GetProductNews : IReturn> + { + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + } + + public class NewsService : BaseApiService + { + private readonly INewsService _newsService; + + public NewsService(INewsService newsService) + { + _newsService = newsService; + } + + public object Get(GetProductNews request) + { + var result = _newsService.GetProductNews(new NewsQuery + { + StartIndex = request.StartIndex, + Limit = request.Limit + + }).Result; + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 353e92444f..53632a51c0 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -136,6 +136,7 @@ + diff --git a/MediaBrowser.Controller/News/INewsService.cs b/MediaBrowser.Controller/News/INewsService.cs new file mode 100644 index 0000000000..e3d238cc62 --- /dev/null +++ b/MediaBrowser.Controller/News/INewsService.cs @@ -0,0 +1,19 @@ +using MediaBrowser.Model.News; +using MediaBrowser.Model.Querying; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.News +{ + /// + /// Interface INewsFeed + /// + public interface INewsService + { + /// + /// Gets the product news. + /// + /// The query. + /// IEnumerable{NewsItem}. + Task> GetProductNews(NewsQuery query); + } +} diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 40c71ec3c9..708573e8f5 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -290,6 +290,9 @@ Net\WebSocketState.cs + + News\NewsItem.cs + Notifications\Notification.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index c03b872438..5fe9c4e5c2 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -277,6 +277,9 @@ Net\WebSocketState.cs + + News\NewsItem.cs + Notifications\Notification.cs diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index fff9991720..0eddcd7e78 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -80,6 +80,7 @@ + diff --git a/MediaBrowser.Model/News/NewsItem.cs b/MediaBrowser.Model/News/NewsItem.cs new file mode 100644 index 0000000000..6dbe5a79e5 --- /dev/null +++ b/MediaBrowser.Model/News/NewsItem.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Model.News +{ + public class NewsChannel + { + public string Title { get; set; } + public string Link { get; set; } + public string Description { get; set; } + public List Items { get; set; } + } + + public class NewsItem + { + public string Title { get; set; } + public string Link { get; set; } + public string Description { get; set; } + public string Guid { get; set; } + public DateTime Date { get; set; } + } + + public class NewsQuery + { + public int? StartIndex { get; set; } + + public int? Limit { get; set; } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index e530eca46c..907301699a 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/NewsService.cs b/MediaBrowser.Server.Implementations/News/NewsService.cs new file mode 100644 index 0000000000..b01470d31c --- /dev/null +++ b/MediaBrowser.Server.Implementations/News/NewsService.cs @@ -0,0 +1,132 @@ +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; + +namespace MediaBrowser.Server.Implementations.News +{ + public class NewsService : INewsService + { + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + private readonly IHttpClient _httpClient; + + public NewsService(IApplicationPaths appPaths, IFileSystem fileSystem, IHttpClient httpClient) + { + _appPaths = appPaths; + _fileSystem = fileSystem; + _httpClient = httpClient; + } + + public async Task> GetProductNews(NewsQuery query) + { + var path = Path.Combine(_appPaths.CachePath, "news.xml"); + + await EnsureNewsFile(path).ConfigureAwait(false); + + var items = GetNewsItems(path); + + var itemsArray = items.ToArray(); + var count = itemsArray.Length; + + if (query.StartIndex.HasValue) + { + itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray(); + } + + if (query.Limit.HasValue) + { + itemsArray = itemsArray.Take(query.Limit.Value).ToArray(); + } + + return new QueryResult + { + Items = itemsArray, + TotalRecordCount = count + }; + } + + 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 > 24) + { + 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); + } + } + } + } + } +} diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 84adffecb9..13e33c7a3d 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -19,6 +19,7 @@ using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.News; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Plugins; @@ -283,6 +284,10 @@ namespace MediaBrowser.ServerApplication DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor); RegisterSingleInstance(DtoService); + + var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, FileSystemManager, HttpClient); + RegisterSingleInstance(newsService); + progress.Report(15); var innerProgress = new ActionableProgress(); diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index d8a1c45ce6..2b282e5e42 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -197,6 +197,19 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi return webSocket && (webSocket.readyState === WebSocket.OPEN || webSocket.readyState === WebSocket.CONNECTING); }; + self.getProductNews = function (options) { + + options = options || {}; + + var url = self.getUrl("News/Product", options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + /** * Gets an item from the server * Omit itemId to get the root folder. diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 321ffd45c3..70c1bd6e29 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file