diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index 2213a5af1b..2aa6800612 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -39,8 +39,8 @@ namespace MediaBrowser.Api.Images
[Route("/Items/{Id}/Images/{Type}", "GET")]
[Route("/Items/{Id}/Images/{Type}/{Index}", "GET")]
- [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "GET")]
- [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "HEAD")]
+ [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "GET")]
+ [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "HEAD")]
[Api(Description = "Gets an item image")]
public class GetItemImage : ImageRequest
{
@@ -583,7 +583,7 @@ namespace MediaBrowser.Api.Images
Width = request.Width,
OutputFormat = request.Format,
AddPlayedIndicator = request.AddPlayedIndicator,
- PercentPlayed = request.PercentPlayed,
+ PercentPlayed = request.PercentPlayed ?? 0,
UnplayedCount = request.UnplayedCount,
BackgroundColor = request.BackgroundColor
};
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index 5cb9ebb1b0..b0131b5c26 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -169,7 +169,7 @@
-
+
-
+
\ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
new file mode 100644
index 0000000000..e71a5d5142
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
@@ -0,0 +1,202 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.TV;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Server.Implementations.TV
+{
+ public class TVSeriesManager : ITVSeriesManager
+ {
+ private readonly IUserManager _userManager;
+ private readonly IUserDataManager _userDataManager;
+ private readonly ILibraryManager _libraryManager;
+
+ public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager)
+ {
+ _userManager = userManager;
+ _userDataManager = userDataManager;
+ _libraryManager = libraryManager;
+ }
+
+ public QueryResult GetNextUp(NextUpQuery request)
+ {
+ var user = _userManager.GetUserById(new Guid(request.UserId));
+
+ if (user == null)
+ {
+ throw new ArgumentException("User not found");
+ }
+
+ var parentIds = string.IsNullOrEmpty(request.ParentId)
+ ? new string[] { }
+ : new[] { request.ParentId };
+
+ var items = GetAllLibraryItems(user, parentIds)
+ .OfType();
+
+ // Avoid implicitly captured closure
+ var episodes = GetNextUpEpisodes(request, user, items);
+
+ return GetResult(episodes, null, request);
+ }
+
+ public QueryResult GetNextUp(NextUpQuery request, IEnumerable parentsFolders)
+ {
+ var user = _userManager.GetUserById(new Guid(request.UserId));
+
+ if (user == null)
+ {
+ throw new ArgumentException("User not found");
+ }
+
+ var items = parentsFolders.SelectMany(i => i.GetRecursiveChildren(user))
+ .OfType();
+
+ // Avoid implicitly captured closure
+ var episodes = GetNextUpEpisodes(request, user, items);
+
+ return GetResult(episodes, null, request);
+ }
+
+ private IEnumerable GetAllLibraryItems(User user, string[] parentIds)
+ {
+ if (parentIds.Length > 0)
+ {
+ return parentIds.SelectMany(i =>
+ {
+ var folder = (Folder)_libraryManager.GetItemById(new Guid(i));
+
+ return folder.GetRecursiveChildren(user);
+
+ });
+ }
+
+ if (user == null)
+ {
+ throw new ArgumentException("User not found");
+ }
+
+ return user.RootFolder.GetRecursiveChildren(user);
+ }
+
+ public IEnumerable GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable series)
+ {
+ // Avoid implicitly captured closure
+ var currentUser = user;
+
+ return FilterSeries(request, series)
+ .AsParallel()
+ .Select(i => GetNextUp(i, currentUser))
+ .Where(i => i.Item1 != null)
+ .OrderByDescending(i =>
+ {
+ var episode = i.Item1;
+
+ var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey());
+
+ if (seriesUserData.IsFavorite)
+ {
+ return 2;
+ }
+
+ if (seriesUserData.Likes.HasValue)
+ {
+ return seriesUserData.Likes.Value ? 1 : -1;
+ }
+
+ return 0;
+ })
+ .ThenByDescending(i => i.Item2)
+ .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
+ .Select(i => i.Item1);
+ }
+
+ ///
+ /// Gets the next up.
+ ///
+ /// The series.
+ /// The user.
+ /// Task{Episode}.
+ private Tuple GetNextUp(Series series, User user)
+ {
+ // Get them in display order, then reverse
+ var allEpisodes = series.GetSeasons(user, true, true)
+ .SelectMany(i => i.GetEpisodes(user, true, true))
+ .Reverse()
+ .ToList();
+
+ Episode lastWatched = null;
+ var lastWatchedDate = DateTime.MinValue;
+ Episode nextUp = null;
+
+ // Go back starting with the most recent episodes
+ foreach (var episode in allEpisodes)
+ {
+ var userData = _userDataManager.GetUserData(user.Id, episode.GetUserDataKey());
+
+ if (userData.Played)
+ {
+ if (lastWatched != null || nextUp == null)
+ {
+ break;
+ }
+
+ lastWatched = episode;
+ lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue;
+ }
+ else
+ {
+ if (episode.LocationType != LocationType.Virtual)
+ {
+ nextUp = episode;
+ }
+ }
+ }
+
+ if (lastWatched != null)
+ {
+ return new Tuple(nextUp, lastWatchedDate);
+ }
+
+ return new Tuple(null, lastWatchedDate);
+ }
+
+ private IEnumerable FilterSeries(NextUpQuery request, IEnumerable items)
+ {
+ if (!string.IsNullOrWhiteSpace(request.SeriesId))
+ {
+ var id = new Guid(request.SeriesId);
+
+ items = items.Where(i => i.Id == id);
+ }
+
+ return items;
+ }
+
+ private QueryResult GetResult(IEnumerable items, int? totalRecordLimit, NextUpQuery query)
+ {
+ var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();
+ var totalCount = itemsArray.Length;
+
+ if (query.Limit.HasValue)
+ {
+ itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray();
+ }
+ else if (query.StartIndex.HasValue)
+ {
+ itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray();
+ }
+
+ return new QueryResult
+ {
+ TotalRecordCount = totalCount,
+ Items = itemsArray
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index 51dccddff1..e97c212042 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/MediaBrowser.ServerApplication/ApplicationHost.cs
@@ -38,6 +38,7 @@ using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Controller.Sync;
using MediaBrowser.Controller.Themes;
+using MediaBrowser.Controller.TV;
using MediaBrowser.Dlna;
using MediaBrowser.Dlna.ConnectionManager;
using MediaBrowser.Dlna.ContentDirectory;
@@ -79,6 +80,7 @@ using MediaBrowser.Server.Implementations.ServerManager;
using MediaBrowser.Server.Implementations.Session;
using MediaBrowser.Server.Implementations.Sync;
using MediaBrowser.Server.Implementations.Themes;
+using MediaBrowser.Server.Implementations.TV;
using MediaBrowser.ServerApplication.FFMpeg;
using MediaBrowser.ServerApplication.IO;
using MediaBrowser.ServerApplication.Native;
@@ -213,10 +215,11 @@ namespace MediaBrowser.ServerApplication
private ISubtitleManager SubtitleManager { get; set; }
private IChapterManager ChapterManager { get; set; }
- private IUserViewManager UserViewManager { get; set; }
+ internal IUserViewManager UserViewManager { get; set; }
private IAuthenticationRepository AuthenticationRepository { get; set; }
private ISyncRepository SyncRepository { get; set; }
+ private ITVSeriesManager TVSeriesManager { get; set; }
///
/// Initializes a new instance of the class.
@@ -466,6 +469,9 @@ namespace MediaBrowser.ServerApplication
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, Logger, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager);
RegisterSingleInstance(ChannelManager);
+ TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager);
+ RegisterSingleInstance(TVSeriesManager);
+
var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
RegisterSingleInstance(appThemeManager);
@@ -487,7 +493,7 @@ namespace MediaBrowser.ServerApplication
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, FileSystemManager, UserManager, ChannelManager, LiveTvManager, ApplicationPaths, playlistManager);
RegisterSingleInstance(UserViewManager);
- var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, UserViewManager, ChannelManager);
+ var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient);
RegisterSingleInstance(contentDirectory);
NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
@@ -682,9 +688,10 @@ namespace MediaBrowser.ServerApplication
Folder.UserManager = UserManager;
BaseItem.FileSystem = FileSystemManager;
BaseItem.UserDataManager = UserDataManager;
- ChannelVideoItem.ChannelManager = ChannelManager;
+ BaseItem.ChannelManager = ChannelManager;
BaseItem.LiveTvManager = LiveTvManager;
- UserView.UserViewManager = UserViewManager;
+ Folder.UserViewManager = UserViewManager;
+ UserView.TVSeriesManager = TVSeriesManager;
}
///
diff --git a/MediaBrowser.ServerApplication/LibraryViewer.cs b/MediaBrowser.ServerApplication/LibraryViewer.cs
index 26cf243daf..e89cbd0c00 100644
--- a/MediaBrowser.ServerApplication/LibraryViewer.cs
+++ b/MediaBrowser.ServerApplication/LibraryViewer.cs
@@ -1,6 +1,9 @@
-using MediaBrowser.Controller.Entities;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using System;
@@ -16,17 +19,17 @@ namespace MediaBrowser.ServerApplication
{
private readonly IJsonSerializer _jsonSerializer;
private readonly ILibraryManager _libraryManager;
- private readonly IItemRepository _itemRepository;
+ private readonly IUserViewManager _userViewManager;
private User _currentUser;
- public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IItemRepository itemRepo)
+ public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IUserViewManager userViewManager)
{
InitializeComponent();
_jsonSerializer = jsonSerializer;
_libraryManager = libraryManager;
- _itemRepository = itemRepo;
+ _userViewManager = userViewManager;
foreach (var user in userManager.Users)
selectUser.Items.Add(user);
@@ -72,23 +75,50 @@ namespace MediaBrowser.ServerApplication
LoadTree();
}
+ private IEnumerable GetItems(Folder parent, User user)
+ {
+ if (parent == null)
+ {
+ var task = _userViewManager.GetUserViews(new UserViewQuery
+ {
+ UserId = user.Id.ToString("N")
+
+ }, CancellationToken.None);
+
+ task.RunSynchronously();
+
+ return task.Result;
+ }
+ else
+ {
+ var task = parent.GetUserItems(new UserItemsQuery
+ {
+ User = user,
+ SortBy = new[] { ItemSortBy.SortName }
+
+ });
+
+ task.RunSynchronously();
+
+ return task.Result.Items;
+ }
+ }
+
private void LoadTree()
{
treeView1.Nodes.Clear();
var isPhysical = _currentUser.Name == "Physical";
- IEnumerable children = isPhysical ? new[] { _libraryManager.RootFolder } : _libraryManager.RootFolder.GetChildren(_currentUser, true);
- children = OrderByName(children, _currentUser);
+ IEnumerable children = isPhysical ? new[] { _libraryManager.RootFolder } : GetItems(null, _currentUser);
- foreach (Folder folder in children)
+ foreach (var folder in children.OfType())
{
-
var currentFolder = folder;
var node = new TreeNode { Tag = currentFolder };
- var subChildren = isPhysical ? currentFolder.Children : currentFolder.GetChildren(_currentUser, true);
- subChildren = OrderByName(subChildren, _currentUser);
+ var subChildren = isPhysical ? currentFolder.Children.OrderBy(i => i.SortName) : GetItems(currentFolder, _currentUser);
+
AddChildren(node, subChildren, _currentUser, isPhysical);
node.Text = currentFolder.Name + " (" +
node.Nodes.Count + ")";
@@ -111,9 +141,9 @@ namespace MediaBrowser.ServerApplication
var subFolder = item as Folder;
if (subFolder != null)
{
- var subChildren = isPhysical ? subFolder.Children : subFolder.GetChildren(_currentUser, true);
+ var subChildren = isPhysical ? subFolder.Children.OrderBy(i => i.SortName) : GetItems(subFolder, _currentUser);
- AddChildren(node, OrderBy(subChildren, user, ItemSortBy.SortName), user, isPhysical);
+ AddChildren(node, subChildren, user, isPhysical);
node.Text = item.Name + " (" + node.Nodes.Count + ")";
}
else
@@ -124,28 +154,6 @@ namespace MediaBrowser.ServerApplication
}
}
- ///
- /// Orders the name of the by.
- ///
- /// The items.
- /// The user.
- /// IEnumerable{BaseItem}.
- private IEnumerable OrderByName(IEnumerable items, User user)
- {
- return OrderBy(items, user, ItemSortBy.SortName);
- }
-
- ///
- /// Orders the name of the by.
- ///
- /// The items.
- /// The user.
- /// IEnumerable{BaseItem}.
- private IEnumerable OrderBy(IEnumerable items, User user, string order)
- {
- return _libraryManager.Sort(items, user, new[] { order }, SortOrder.Ascending);
- }
-
///
/// The INDEN t_ STRING
///
diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs
index 81c04060a3..6e6377a643 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -249,7 +249,7 @@ namespace MediaBrowser.ServerApplication
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
- _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.ItemRepository, _appHost.LocalizationManager);
+ _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.LocalizationManager, _appHost.UserViewManager);
Application.Run();
}
diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
index 47a4be8e36..8740b707b8 100644
--- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
+++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
@@ -39,7 +39,7 @@ namespace MediaBrowser.ServerApplication
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
private readonly IJsonSerializer _jsonSerializer;
- private readonly IItemRepository _itemRepository;
+ private readonly IUserViewManager _userViewManager;
private readonly ILocalizationManager _localization;
private LogForm _logForm;
@@ -61,11 +61,11 @@ namespace MediaBrowser.ServerApplication
IServerConfigurationManager configurationManager,
IUserManager userManager, ILibraryManager libraryManager,
IJsonSerializer jsonSerializer,
- IItemRepository itemRepo, ILocalizationManager localization)
+ ILocalizationManager localization, IUserViewManager userViewManager)
{
_logger = logManager.GetLogger("MainWindow");
- _itemRepository = itemRepo;
_localization = localization;
+ _userViewManager = userViewManager;
_appHost = appHost;
_logManager = logManager;
_configurationManager = configurationManager;
@@ -318,7 +318,7 @@ namespace MediaBrowser.ServerApplication
void cmdLibraryExplorer_Click(object sender, EventArgs e)
{
- new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _itemRepository).Show();
+ new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _userViewManager).Show();
}
void cmdRestart_Click(object sender, EventArgs e)
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 32cb882180..864a2f70fb 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -2136,7 +2136,7 @@
-
+