aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2014-06-15 19:30:04 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2014-06-15 19:30:04 -0400
commit9e57e16aa9b8f0e639a106c83a2bed2756d8783c (patch)
tree413ab13730350ee682c4fcc62cecec60a81f5fbc
parent918034d803a6aa584a4a1a5a9a7027056d71a590 (diff)
fixes #839 - Support getting latest channel items
-rw-r--r--MediaBrowser.Api/ChannelService.cs62
-rw-r--r--MediaBrowser.Controller/Channels/IChannelManager.cs8
-rw-r--r--MediaBrowser.Model/Channels/ChannelFeatures.cs6
-rw-r--r--MediaBrowser.Model/Channels/ChannelQuery.cs11
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs152
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs130
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json3
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/server.json2
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs1
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj6
11 files changed, 330 insertions, 53 deletions
diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs
index 442a95f4a..3cd032015 100644
--- a/MediaBrowser.Api/ChannelService.cs
+++ b/MediaBrowser.Api/ChannelService.cs
@@ -107,6 +107,53 @@ namespace MediaBrowser.Api
}
}
+ [Route("/Channels/Items/Latest", "GET", Summary = "Gets channel items")]
+ public class GetLatestChannelItems : IReturn<QueryResult<BaseItemDto>>, IHasItemFields
+ {
+ /// <summary>
+ /// Gets or sets the user id.
+ /// </summary>
+ /// <value>The user id.</value>
+ [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string UserId { get; set; }
+
+ /// <summary>
+ /// Skips over a given number of items within the results. Use for paging.
+ /// </summary>
+ /// <value>The start index.</value>
+ [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; }
+
+ /// <summary>
+ /// The maximum number of items to return
+ /// </summary>
+ /// <value>The limit.</value>
+ [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; }
+
+ [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+ public string Filters { get; set; }
+
+ [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+ public string Fields { get; set; }
+
+ /// <summary>
+ /// Gets the filters.
+ /// </summary>
+ /// <returns>IEnumerable{ItemFilter}.</returns>
+ public IEnumerable<ItemFilter> GetFilters()
+ {
+ var val = Filters;
+
+ if (string.IsNullOrEmpty(val))
+ {
+ return new ItemFilter[] { };
+ }
+
+ return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
+ }
+ }
+
[Route("/Channels/Folder", "GET", Summary = "Gets the users channel folder, along with configured images")]
public class GetChannelFolder : IReturn<BaseItemDto>
{
@@ -173,5 +220,20 @@ namespace MediaBrowser.Api
return ToOptimizedResult(result);
}
+
+ public object Get(GetLatestChannelItems request)
+ {
+ var result = _channelManager.GetLatestChannelItems(new AllChannelMediaQuery
+ {
+ Limit = request.Limit,
+ StartIndex = request.StartIndex,
+ UserId = request.UserId,
+ Filters = request.GetFilters().ToArray(),
+ Fields = request.GetItemFields().ToList()
+
+ }, CancellationToken.None).Result;
+
+ return ToOptimizedResult(result);
+ }
}
}
diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs
index b6984d9ad..744eab96e 100644
--- a/MediaBrowser.Controller/Channels/IChannelManager.cs
+++ b/MediaBrowser.Controller/Channels/IChannelManager.cs
@@ -60,6 +60,14 @@ namespace MediaBrowser.Controller.Channels
Task<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken);
/// <summary>
+ /// Gets the latest media.
+ /// </summary>
+ /// <param name="query">The query.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{QueryResult{BaseItemDto}}.</returns>
+ Task<QueryResult<BaseItemDto>> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken);
+
+ /// <summary>
/// Gets the channel items.
/// </summary>
/// <param name="query">The query.</param>
diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs
index 8c41bfde7..0a919f222 100644
--- a/MediaBrowser.Model/Channels/ChannelFeatures.cs
+++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs
@@ -63,10 +63,10 @@ namespace MediaBrowser.Model.Channels
public bool CanFilter { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether this instance can download all media.
+ /// Gets or sets a value indicating whether [supports content downloading].
/// </summary>
- /// <value><c>true</c> if this instance can download all media; otherwise, <c>false</c>.</value>
- public bool CanDownloadAllMedia { get; set; }
+ /// <value><c>true</c> if [supports content downloading]; otherwise, <c>false</c>.</value>
+ public bool SupportsContentDownloading { get; set; }
public ChannelFeatures()
{
diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs
index a2f428869..9d4e26fa6 100644
--- a/MediaBrowser.Model/Channels/ChannelQuery.cs
+++ b/MediaBrowser.Model/Channels/ChannelQuery.cs
@@ -1,4 +1,7 @@
-namespace MediaBrowser.Model.Channels
+using MediaBrowser.Model.Querying;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.Channels
{
public class ChannelQuery
{
@@ -54,7 +57,13 @@
ChannelIds = new string[] { };
ContentTypes = new ChannelMediaContentType[] { };
+
+ Filters = new ItemFilter[] { };
+ Fields = new List<ItemFields>();
}
+
+ public ItemFilter[] Filters { get; set; }
+ public List<ItemFields> Fields { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 6f33de67f..1f37d3609 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -335,7 +335,7 @@ namespace MediaBrowser.Providers.Manager
return false;
}
- if (provider is IRemoteImageProvider)
+ if (provider is IRemoteImageProvider || provider is IDynamicImageProvider)
{
if (!ConfigurationManager.Configuration.EnableInternetProviders)
{
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
index b689f153c..fdc5cfd22 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Progress;
using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -9,6 +10,7 @@ using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.IO;
@@ -26,8 +28,9 @@ namespace MediaBrowser.Server.Implementations.Channels
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
+ private readonly IUserManager _userManager;
- public ChannelDownloadScheduledTask(IChannelManager manager, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem, ILibraryManager libraryManager)
+ public ChannelDownloadScheduledTask(IChannelManager manager, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem, ILibraryManager libraryManager, IUserManager userManager)
{
_manager = manager;
_config = config;
@@ -35,6 +38,7 @@ namespace MediaBrowser.Server.Implementations.Channels
_httpClient = httpClient;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
+ _userManager = userManager;
}
public string Name
@@ -55,70 +59,118 @@ namespace MediaBrowser.Server.Implementations.Channels
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
CleanChannelContent(cancellationToken);
- progress.Report(5);
- await DownloadChannelContent(cancellationToken, progress).ConfigureAwait(false);
+ var users = _userManager.Users.Select(i => i.Id.ToString("N")).ToList();
+
+ var numComplete = 0;
+
+ foreach (var user in users)
+ {
+ double percentPerUser = 1;
+ percentPerUser /= users.Count;
+ var startingPercent = numComplete * percentPerUser * 100;
+
+ var innerProgress = new ActionableProgress<double>();
+ innerProgress.RegisterAction(p => progress.Report(startingPercent + (.8 * p)));
+
+ await DownloadContent(user, cancellationToken, innerProgress).ConfigureAwait(false);
+
+ numComplete++;
+ double percent = numComplete;
+ percent /= users.Count;
+ progress.Report(percent * 100);
+ }
+
progress.Report(100);
}
- private void CleanChannelContent(CancellationToken cancellationToken)
+ private async Task DownloadContent(string user,
+ CancellationToken cancellationToken,
+ IProgress<double> progress)
{
- if (!_config.Configuration.ChannelOptions.MaxDownloadAge.HasValue)
+ var innerProgress = new ActionableProgress<double>();
+ innerProgress.RegisterAction(p => progress.Report(0 + (.8 * p)));
+ await DownloadAllChannelContent(user, cancellationToken, innerProgress).ConfigureAwait(false);
+ progress.Report(80);
+
+ innerProgress = new ActionableProgress<double>();
+ innerProgress.RegisterAction(p => progress.Report(80 + (.2 * p)));
+ await DownloadLatestChannelContent(user, cancellationToken, progress).ConfigureAwait(false);
+ progress.Report(100);
+ }
+
+ private async Task DownloadLatestChannelContent(string userId,
+ CancellationToken cancellationToken,
+ IProgress<double> progress)
+ {
+ var result = await _manager.GetLatestChannelItems(new AllChannelMediaQuery
{
- return;
- }
+ UserId = userId
- var minDateModified = DateTime.UtcNow.AddDays(0 - _config.Configuration.ChannelOptions.MaxDownloadAge.Value);
+ }, cancellationToken).ConfigureAwait(false);
+
+ progress.Report(5);
+
+ var innerProgress = new ActionableProgress<double>();
+ innerProgress.RegisterAction(p => progress.Report(5 + (.95 * p)));
var path = _manager.ChannelDownloadPath;
- try
- {
- DeleteCacheFilesFromDirectory(cancellationToken, path, minDateModified, new Progress<double>());
- }
- catch (DirectoryNotFoundException)
- {
- // No biggie here. Nothing to delete
- }
+ await DownloadChannelContent(result, path, cancellationToken, innerProgress).ConfigureAwait(false);
}
- private async Task DownloadChannelContent(CancellationToken cancellationToken, IProgress<double> progress)
+ private async Task DownloadAllChannelContent(string userId,
+ CancellationToken cancellationToken,
+ IProgress<double> progress)
{
- if (_config.Configuration.ChannelOptions.DownloadingChannels.Length == 0)
- {
- return;
- }
-
var result = await _manager.GetAllMedia(new AllChannelMediaQuery
{
- ChannelIds = _config.Configuration.ChannelOptions.DownloadingChannels
+ UserId = userId
}, cancellationToken).ConfigureAwait(false);
+ progress.Report(5);
+
+ var innerProgress = new ActionableProgress<double>();
+ innerProgress.RegisterAction(p => progress.Report(5 + (.95 * p)));
+
var path = _manager.ChannelDownloadPath;
+ await DownloadChannelContent(result, path, cancellationToken, innerProgress).ConfigureAwait(false);
+ }
+
+ private async Task DownloadChannelContent(QueryResult<BaseItemDto> result,
+ string path,
+ CancellationToken cancellationToken,
+ IProgress<double> progress)
+ {
var numComplete = 0;
foreach (var item in result.Items)
{
- try
- {
- await DownloadChannelItem(item, cancellationToken, path);
- }
- catch (OperationCanceledException)
+ if (_config.Configuration.ChannelOptions.DownloadingChannels.Contains(item.ChannelId))
{
- break;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name);
+ try
+ {
+ await DownloadChannelItem(item, cancellationToken, path);
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name);
+ }
}
numComplete++;
double percent = numComplete;
percent /= result.Items.Length;
- progress.Report(percent * 95 + 5);
+ progress.Report(percent * 100);
}
+
+ progress.Report(100);
}
private async Task DownloadChannelItem(BaseItemDto item,
@@ -212,6 +264,27 @@ namespace MediaBrowser.Server.Implementations.Channels
};
}
+ private void CleanChannelContent(CancellationToken cancellationToken)
+ {
+ if (!_config.Configuration.ChannelOptions.MaxDownloadAge.HasValue)
+ {
+ return;
+ }
+
+ var minDateModified = DateTime.UtcNow.AddDays(0 - _config.Configuration.ChannelOptions.MaxDownloadAge.Value);
+
+ var path = _manager.ChannelDownloadPath;
+
+ try
+ {
+ DeleteCacheFilesFromDirectory(cancellationToken, path, minDateModified, new Progress<double>());
+ }
+ catch (DirectoryNotFoundException)
+ {
+ // No biggie here. Nothing to delete
+ }
+ }
+
/// <summary>
/// Deletes the cache files from directory with a last write time less than a given date
/// </summary>
@@ -260,15 +333,22 @@ namespace MediaBrowser.Server.Implementations.Channels
}
}
+ /// <summary>
+ /// Gets a value indicating whether this instance is hidden.
+ /// </summary>
+ /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
public bool IsHidden
{
get
{
- return !_manager.GetAllChannelFeatures()
- .Any(i => i.CanDownloadAllMedia && _config.Configuration.ChannelOptions.DownloadingChannels.Contains(i.Id));
+ return !_manager.GetAllChannelFeatures().Any();
}
}
+ /// <summary>
+ /// Gets a value indicating whether this instance is enabled.
+ /// </summary>
+ /// <value><c>true</c> if this instance is enabled; otherwise, <c>false</c>.</value>
public bool IsEnabled
{
get
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index 553c683fd..ad775b576 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
@@ -53,6 +53,14 @@ namespace MediaBrowser.Server.Implementations.Channels
_localization = localization;
}
+ private TimeSpan CacheLength
+ {
+ get
+ {
+ return TimeSpan.FromDays(1);
+ }
+ }
+
public void AddParts(IEnumerable<IChannel> channels, IEnumerable<IChannelFactory> factories)
{
_channels = channels.ToArray();
@@ -443,6 +451,7 @@ namespace MediaBrowser.Server.Implementations.Channels
InternalChannelFeatures features)
{
var isIndexable = provider is IIndexableChannel;
+ var supportsLatest = provider is ISupportsLatestMedia;
return new ChannelFeatures
{
@@ -453,10 +462,10 @@ namespace MediaBrowser.Server.Implementations.Channels
MaxPageSize = features.MaxPageSize,
MediaTypes = features.MediaTypes,
SupportsSortOrderToggle = features.SupportsSortOrderToggle,
- SupportsLatestMedia = provider is ISupportsLatestMedia,
+ SupportsLatestMedia = supportsLatest,
Name = channel.Name,
Id = channel.Id.ToString("N"),
- CanDownloadAllMedia = isIndexable
+ SupportsContentDownloading = isIndexable || supportsLatest
};
}
@@ -470,6 +479,105 @@ namespace MediaBrowser.Server.Implementations.Channels
return ("Channel " + name).GetMBId(typeof(Channel));
}
+ public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken)
+ {
+ var user = string.IsNullOrWhiteSpace(query.UserId)
+ ? null
+ : _userManager.GetUserById(new Guid(query.UserId));
+
+ var channels = _channels;
+
+ if (query.ChannelIds.Length > 0)
+ {
+ // Avoid implicitly captured closure
+ var ids = query.ChannelIds;
+ channels = channels
+ .Where(i => ids.Contains(GetInternalChannelId(i.Name).ToString("N")))
+ .ToArray();
+ }
+
+ // Avoid implicitly captured closure
+ var userId = query.UserId;
+
+ var tasks = channels
+ .Select(async i =>
+ {
+ var indexable = i as ISupportsLatestMedia;
+
+ if (indexable != null)
+ {
+ try
+ {
+ var result = await indexable.GetLatestMedia(new ChannelLatestMediaSearch
+ {
+ UserId = userId
+
+ }, cancellationToken).ConfigureAwait(false);
+
+ var resultItems = result.ToList();
+
+ return new Tuple<IChannel, ChannelItemResult>(i, new ChannelItemResult
+ {
+ Items = resultItems,
+ TotalRecordCount = resultItems.Count
+ });
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting all media from {0}", ex, i.Name);
+ }
+ }
+ return new Tuple<IChannel, ChannelItemResult>(i, new ChannelItemResult { });
+ });
+
+ var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+
+ var totalCount = results.Length;
+
+ IEnumerable<Tuple<IChannel, ChannelItemInfo>> items = results
+ .SelectMany(i => i.Item2.Items.Select(m => new Tuple<IChannel, ChannelItemInfo>(i.Item1, m)))
+ .OrderBy(i => i.Item2.Name);
+
+ if (query.ContentTypes.Length > 0)
+ {
+ // Avoid implicitly captured closure
+ var contentTypes = query.ContentTypes;
+
+ items = items.Where(i => contentTypes.Contains(i.Item2.ContentType));
+ }
+
+ // Avoid implicitly captured closure
+ var token = cancellationToken;
+ var itemTasks = items.Select(i =>
+ {
+ var channelProvider = i.Item1;
+ var channel = GetChannel(GetInternalChannelId(channelProvider.Name).ToString("N"));
+ return GetChannelItemEntity(i.Item2, channelProvider, channel, token);
+ });
+
+ IEnumerable<BaseItem> internalItems = await Task.WhenAll(itemTasks).ConfigureAwait(false);
+
+ internalItems = ApplyFilters(internalItems, query.Filters, user);
+
+ if (query.StartIndex.HasValue)
+ {
+ internalItems = internalItems.Skip(query.StartIndex.Value);
+ }
+ if (query.Limit.HasValue)
+ {
+ internalItems = internalItems.Take(query.Limit.Value);
+ }
+
+ var returnItemArray = internalItems.Select(i => _dtoService.GetBaseItemDto(i, query.Fields, user))
+ .ToArray();
+
+ return new QueryResult<BaseItemDto>
+ {
+ TotalRecordCount = totalCount,
+ Items = returnItemArray
+ };
+ }
+
public async Task<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken)
{
var user = string.IsNullOrWhiteSpace(query.UserId)
@@ -480,11 +588,16 @@ namespace MediaBrowser.Server.Implementations.Channels
if (query.ChannelIds.Length > 0)
{
+ // Avoid implicitly captured closure
+ var ids = query.ChannelIds;
channels = channels
- .Where(i => query.ChannelIds.Contains(GetInternalChannelId(i.Name).ToString("N")))
+ .Where(i => ids.Contains(GetInternalChannelId(i.Name).ToString("N")))
.ToArray();
}
+ // Avoid implicitly captured closure
+ var userId = query.UserId;
+
var tasks = channels
.Select(async i =>
{
@@ -496,7 +609,7 @@ namespace MediaBrowser.Server.Implementations.Channels
{
var result = await indexable.GetAllMedia(new InternalAllChannelMediaQuery
{
- UserId = query.UserId
+ UserId = userId
}, cancellationToken).ConfigureAwait(false);
@@ -546,12 +659,7 @@ namespace MediaBrowser.Server.Implementations.Channels
var internalItems = await Task.WhenAll(itemTasks).ConfigureAwait(false);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
-
- var returnItemArray = internalItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+ var returnItemArray = internalItems.Select(i => _dtoService.GetBaseItemDto(i, query.Fields, user))
.ToArray();
return new QueryResult<BaseItemDto>
@@ -641,7 +749,7 @@ namespace MediaBrowser.Server.Implementations.Channels
{
var userId = user.Id.ToString("N");
- var cacheLength = TimeSpan.FromDays(1);
+ var cacheLength = CacheLength;
var cachePath = GetChannelDataCachePath(channel, userId, folderId, sortField, sortDescending);
try
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
index 74c1791ba..d4c31a61e 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
@@ -147,5 +147,6 @@
"ButtonRemove": "Remove",
"LabelChapterDownloaders": "Chapter downloaders:",
"LabelChapterDownloadersHelp": "Enable and rank your preferred chapter downloaders in order of priority. Lower priority downloaders will only be used to fill in missing information.",
- "HeaderFavoriteAlbums": "Favorite Albums"
+ "HeaderFavoriteAlbums": "Favorite Albums",
+ "HeaderLatestChannelMedia": "Latest Channel Items"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json
index 5626238c1..af6a312e0 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/server.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json
@@ -785,11 +785,13 @@
"LabelHomePageSection1": "Home page section one:",
"LabelHomePageSection2": "Home page section two:",
"LabelHomePageSection3": "Home page section three:",
+ "LabelHomePageSection4": "Home page section four:",
"OptionMyLibraryButtons": "My library (buttons)",
"OptionMyLibrary": "My library",
"OptionMyLibrarySmall": "My library (small)",
"OptionResumablemedia": "Resume",
"OptionLatestMedia": "Latest media",
+ "OptionLatestChannelMedia": "Latest channel items",
"OptionNone": "None",
"HeaderLiveTv": "Live TV",
"HeaderReports": "Reports",
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index 78344965d..aaa930055 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -538,6 +538,7 @@ namespace MediaBrowser.WebDashboard.Api
"autoorganizetv.js",
"autoorganizelog.js",
"channels.js",
+ "channelslatest.js",
"channelitems.js",
"channelsettings.js",
"dashboardgeneral.js",
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 709340dc0..1a2bd8ef1 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -106,6 +106,9 @@
<Content Include="dashboard-ui\channelsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\channelslatest.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\css\chromecast.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -622,6 +625,9 @@
<Content Include="dashboard-ui\scripts\channelsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\channelslatest.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\chromecast.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>