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