fixes #912 - Add special views for Dlna

pull/702/head
Luke Pulverenti 10 years ago
parent 383b9999da
commit 6f45ea0823

@ -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
};

@ -169,7 +169,7 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -1516,6 +1516,7 @@ namespace MediaBrowser.Api.Playback
state.RunTimeTicks = item.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
state.InputBitrate = mediaSource.Bitrate;
state.InputFileSize = mediaSource.Size;
mediaStreams = mediaSource.MediaStreams;
}
else
@ -1530,6 +1531,7 @@ namespace MediaBrowser.Api.Playback
state.MediaPath = mediaSource.Path;
state.InputProtocol = mediaSource.Protocol;
state.InputContainer = mediaSource.Container;
state.InputFileSize = mediaSource.Size;
state.InputBitrate = mediaSource.Bitrate;
if (item is Video)

@ -1,4 +1,5 @@
using MediaBrowser.Common.IO;
using System.Linq;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@ -153,7 +154,25 @@ namespace MediaBrowser.Api.Playback.Progressive
using (state)
{
var throttleLimit = state.InputBitrate.HasValue ? (state.InputBitrate.Value / 8) : 0;
var limits = new List<long>();
if (state.InputBitrate.HasValue)
{
// Bytes per second
limits.Add((state.InputBitrate.Value / 8));
}
if (state.InputFileSize.HasValue && state.RunTimeTicks.HasValue)
{
var totalSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds;
if (totalSeconds > 1)
{
var timeBasedLimit = state.InputFileSize.Value / totalSeconds;
limits.Add(Convert.ToInt64(timeBasedLimit));
}
}
// Take the greater of the above to methods, just to be safe
var throttleLimit = limits.Count > 0 ? limits.Max() : 0;
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
@ -166,8 +185,8 @@ namespace MediaBrowser.Api.Playback.Progressive
// Pad by 20% to play it safe
ThrottleLimit = Convert.ToInt64(1.2 * throttleLimit),
// Three minutes
MinThrottlePosition = throttleLimit * 180
// 3.5 minutes
MinThrottlePosition = throttleLimit * 210
});
}
}

@ -69,6 +69,7 @@ namespace MediaBrowser.Api.Playback
public long? RunTimeTicks;
public long? InputBitrate { get; set; }
public long? InputFileSize { get; set; }
public string OutputAudioSync = "1";
public string OutputVideoSync = "vfr";

@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using ServiceStack;
@ -26,7 +27,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <value>The user id.</value>
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public Guid UserId { get; set; }
public string UserId { get; set; }
/// <summary>
/// Skips over a given number of items within the results. Use for paging.
@ -195,6 +196,7 @@ namespace MediaBrowser.Api
private readonly IItemRepository _itemRepo;
private readonly IDtoService _dtoService;
private readonly ITVSeriesManager _tvSeriesManager;
/// <summary>
/// Initializes a new instance of the <see cref="TvShowsService" /> class.
@ -202,13 +204,14 @@ namespace MediaBrowser.Api
/// <param name="userManager">The user manager.</param>
/// <param name="userDataManager">The user data repository.</param>
/// <param name="libraryManager">The library manager.</param>
public TvShowsService(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService)
public TvShowsService(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService, ITVSeriesManager tvSeriesManager)
{
_userManager = userManager;
_userDataManager = userDataManager;
_libraryManager = libraryManager;
_itemRepo = itemRepo;
_dtoService = dtoService;
_tvSeriesManager = tvSeriesManager;
}
/// <summary>
@ -270,129 +273,26 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public object Get(GetNextUpEpisodes request)
{
var user = _userManager.GetUserById(request.UserId);
var itemsList = GetNextUpEpisodes(request)
.ToList();
var result = _tvSeriesManager.GetNextUp(new NextUpQuery
{
Limit = request.Limit,
ParentId = request.ParentId,
SeriesId = request.SeriesId,
StartIndex = request.StartIndex,
UserId = request.UserId
});
var pagedItems = ApplyPaging(itemsList, request.StartIndex, request.Limit);
var user = _userManager.GetUserById(new Guid(request.UserId));
var fields = request.GetItemFields().ToList();
var returnItems = pagedItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray();
var returnItems = result.Items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray();
var result = new ItemsResult
return ToOptimizedSerializedResultUsingCache(new ItemsResult
{
TotalRecordCount = itemsList.Count,
TotalRecordCount = result.TotalRecordCount,
Items = returnItems
};
return ToOptimizedSerializedResultUsingCache(result);
}
public IEnumerable<Episode> GetNextUpEpisodes(GetNextUpEpisodes request)
{
var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId)
.OfType<Series>();
// Avoid implicitly captured closure
return GetNextUpEpisodes(request, items);
}
public IEnumerable<Episode> GetNextUpEpisodes(GetNextUpEpisodes request, IEnumerable<Series> series)
{
var user = _userManager.GetUserById(request.UserId);
// 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);
}
/// <summary>
/// Gets the next up.
/// </summary>
/// <param name="series">The series.</param>
/// <param name="user">The user.</param>
/// <returns>Task{Episode}.</returns>
private Tuple<Episode, DateTime> 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<Episode, DateTime>(nextUp, lastWatchedDate);
}
return new Tuple<Episode, DateTime>(null, lastWatchedDate);
}
private IEnumerable<Series> FilterSeries(GetNextUpEpisodes request, IEnumerable<Series> items)
{
if (!string.IsNullOrWhiteSpace(request.SeriesId))
{
var id = new Guid(request.SeriesId);
items = items.Where(i => i.Id == id);
}
return items;
});
}
/// <summary>

@ -122,7 +122,7 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<PropertyGroup>
<PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i

@ -116,7 +116,7 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<PropertyGroup>
<PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i

@ -1,6 +1,10 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Querying;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Channels
{
@ -17,5 +21,31 @@ namespace MediaBrowser.Controller.Channels
return base.IsVisible(user);
}
public override async Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
{
try
{
// Don't blow up here because it could cause parent screens with other content to fail
return await ChannelManager.GetChannelItemsInternal(new ChannelItemQuery
{
ChannelId = Id.ToString("N"),
Limit = query.Limit,
StartIndex = query.StartIndex,
UserId = query.User.Id.ToString("N"),
SortBy = query.SortBy,
SortOrder = query.SortOrder
}, CancellationToken.None);
}
catch
{
// Already logged at lower levels
return new QueryResult<BaseItem>
{
};
}
}
}
}

@ -1,6 +1,9 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Querying;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Channels
{
@ -33,5 +36,32 @@ namespace MediaBrowser.Controller.Channels
{
return ExternalId;
}
public override async Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
{
try
{
// Don't blow up here because it could cause parent screens with other content to fail
return await ChannelManager.GetChannelItemsInternal(new ChannelItemQuery
{
ChannelId = ChannelId,
FolderId = Id.ToString("N"),
Limit = query.Limit,
StartIndex = query.StartIndex,
UserId = query.User.Id.ToString("N"),
SortBy = query.SortBy,
SortOrder = query.SortOrder
}, CancellationToken.None);
}
catch
{
// Already logged at lower levels
return new QueryResult<BaseItem>
{
};
}
}
}
}

@ -11,8 +11,6 @@ namespace MediaBrowser.Controller.Channels
{
public class ChannelVideoItem : Video, IChannelMediaItem
{
public static IChannelManager ChannelManager { get; set; }
public string ExternalId { get; set; }
public string ChannelId { get; set; }

@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Drawing
public int? UnplayedCount { get; set; }
public double? PercentPlayed { get; set; }
public double PercentPlayed { get; set; }
public string BackgroundColor { get; set; }
@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Drawing
return (!Quality.HasValue || Quality.Value == 100) &&
IsOutputFormatDefault(originalImagePath) &&
!AddPlayedIndicator &&
!PercentPlayed.HasValue &&
PercentPlayed.Equals(0) &&
!UnplayedCount.HasValue &&
string.IsNullOrEmpty(BackgroundColor);
}

@ -1,5 +1,6 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
@ -240,6 +241,7 @@ namespace MediaBrowser.Controller.Entities
public static IFileSystem FileSystem { get; set; }
public static IUserDataManager UserDataManager { get; set; }
public static ILiveTvManager LiveTvManager { get; set; }
public static IChannelManager ChannelManager { get; set; }
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.

@ -6,6 +6,8 @@ using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using MoreLinq;
using System;
using System.Collections;
using System.Collections.Generic;
@ -14,7 +16,6 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MoreLinq;
namespace MediaBrowser.Controller.Entities
{
@ -24,6 +25,7 @@ namespace MediaBrowser.Controller.Entities
public class Folder : BaseItem, IHasThemeMedia, IHasTags
{
public static IUserManager UserManager { get; set; }
public static IUserViewManager UserViewManager { get; set; }
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
@ -770,6 +772,24 @@ namespace MediaBrowser.Controller.Entities
return item;
}
public virtual Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
{
var user = query.User;
var items = query.Recursive
? GetRecursiveChildren(user)
: GetChildren(user, true);
var result = SortAndFilter(items, query);
return Task.FromResult(result);
}
protected QueryResult<BaseItem> SortAndFilter(IEnumerable<BaseItem> items, UserItemsQuery query)
{
return UserViewBuilder.SortAndFilter(items, null, query, LibraryManager, UserDataManager);
}
/// <summary>
/// Gets allowed children of an item
/// </summary>
@ -944,7 +964,7 @@ namespace MediaBrowser.Controller.Entities
.OfType<CollectionFolder>()
.SelectMany(i => i.PhysicalLocations)
.ToList();
return LinkedChildren
.Select(i =>
{
@ -985,10 +1005,10 @@ namespace MediaBrowser.Controller.Entities
/// Gets the linked children.
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
public IEnumerable<Tuple<LinkedChild,BaseItem>> GetLinkedChildrenInfos()
public IEnumerable<Tuple<LinkedChild, BaseItem>> GetLinkedChildrenInfos()
{
return LinkedChildren
.Select(i => new Tuple<LinkedChild,BaseItem>(i, GetLinkedChild(i)))
.Select(i => new Tuple<LinkedChild, BaseItem>(i, GetLinkedChild(i)))
.Where(i => i.Item2 != null);
}
@ -1183,7 +1203,7 @@ namespace MediaBrowser.Controller.Entities
var isUnplayed = true;
var itemUserData = UserDataManager.GetUserData(user.Id, child.GetUserDataKey());
// Incrememt totalPercentPlayed
if (itemUserData != null)
{

@ -0,0 +1,35 @@
using MediaBrowser.Model.Entities;
using System;
namespace MediaBrowser.Controller.Entities
{
public class UserItemsQuery
{
public bool Recursive { get; set; }
public int? StartIndex { get; set; }
public int? Limit { get; set; }
public string[] SortBy { get; set; }
public SortOrder SortOrder { get; set; }
public User User { get; set; }
public Func<BaseItem, User, bool> Filter { get; set; }
public bool? IsFolder { get; set; }
public bool? IsFavorite { get; set; }
public bool? IsPlayed { get; set; }
public bool? IsResumable { get; set; }
public string[] MediaTypes { get; set; }
public UserItemsQuery()
{
SortBy = new string[] { };
MediaTypes = new string[] { };
}
}
}

@ -1,5 +1,7 @@
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.Linq;
@ -14,6 +16,17 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class UserRootFolder : Folder
{
public override async Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
{
var result = await UserViewManager.GetUserViews(new UserViewQuery
{
UserId = query.User.Id.ToString("N")
}, CancellationToken.None).ConfigureAwait(false);
return SortAndFilter(result, query);
}
/// <summary>
/// Get the children of this folder from the actual file system
/// </summary>

@ -1,12 +1,9 @@
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Entities
@ -14,56 +11,37 @@ namespace MediaBrowser.Controller.Entities
public class UserView : Folder
{
public string ViewType { get; set; }
public static IUserViewManager UserViewManager { get; set; }
public Guid ParentId { get; set; }
public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
public static ITVSeriesManager TVSeriesManager;
public override Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
{
var mediaFolders = GetMediaFolders(user);
return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager)
.GetUserItems(this, ViewType, query);
}
switch (ViewType)
public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
{
var result = GetUserItems(new UserItemsQuery
{
case CollectionType.LiveTvChannels:
return LiveTvManager.GetInternalChannels(new LiveTvChannelQuery
{
UserId = user.Id.ToString("N")
}, CancellationToken.None).Result.Items;
case CollectionType.LiveTvRecordingGroups:
return LiveTvManager.GetInternalRecordings(new RecordingQuery
{
UserId = user.Id.ToString("N"),
Status = RecordingStatus.Completed
}, CancellationToken.None).Result.Items;
case CollectionType.LiveTv:
return GetLiveTvFolders(user).Result;
case CollectionType.Folders:
return user.RootFolder.GetChildren(user, includeLinkedChildren);
case CollectionType.Games:
return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
.OfType<GameSystem>();
case CollectionType.BoxSets:
return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
.OfType<BoxSet>();
case CollectionType.TvShows:
return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
.OfType<Series>();
case CollectionType.Trailers:
return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
.OfType<Trailer>();
default:
return mediaFolders.SelectMany(i => i.GetChildren(user, includeLinkedChildren));
}
User = user
}).Result;
return result.Items;
}
private async Task<IEnumerable<BaseItem>> GetLiveTvFolders(User user)
public override IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true)
{
var list = new List<BaseItem>();
var result = GetUserItems(new UserItemsQuery
{
User = user,
Recursive = true
list.Add(await UserViewManager.GetUserView(CollectionType.LiveTvChannels, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
list.Add(await UserViewManager.GetUserView(CollectionType.LiveTvRecordingGroups, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
}).Result;
return list;
return result.Items;
}
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
@ -71,16 +49,6 @@ namespace MediaBrowser.Controller.Entities
return GetChildren(user, false);
}
private IEnumerable<Folder> GetMediaFolders(User user)
{
var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList();
return user.RootFolder
.GetChildren(user, true, true)
.OfType<Folder>()
.Where(i => !excludeFolderIds.Contains(i.Id) && !IsExcludedFromGrouping(i));
}
public static bool IsExcludedFromGrouping(Folder folder)
{
var standaloneTypes = new List<string>

@ -0,0 +1,614 @@
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Entities
{
public class UserViewBuilder
{
private readonly IChannelManager _channelManager;
private readonly ILiveTvManager _liveTvManager;
private readonly IUserViewManager _userViewManager;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager;
private readonly ITVSeriesManager _tvSeriesManager;
public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager)
{
_userViewManager = userViewManager;
_liveTvManager = liveTvManager;
_channelManager = channelManager;
_libraryManager = libraryManager;
_logger = logger;
_userDataManager = userDataManager;
_tvSeriesManager = tvSeriesManager;
}
public async Task<QueryResult<BaseItem>> GetUserItems(Folder parent, string viewType, UserItemsQuery query)
{
var user = query.User;
switch (viewType)
{
case CollectionType.Channels:
{
var result = await _channelManager.GetChannelsInternal(new ChannelQuery
{
UserId = user.Id.ToString("N"),
Limit = query.Limit,
StartIndex = query.StartIndex
}, CancellationToken.None).ConfigureAwait(false);
return GetResult(result);
}
case CollectionType.LiveTvChannels:
{
var result = await _liveTvManager.GetInternalChannels(new LiveTvChannelQuery
{
UserId = query.User.Id.ToString("N"),
Limit = query.Limit,
StartIndex = query.StartIndex
}, CancellationToken.None).ConfigureAwait(false);
return GetResult(result);
}
case CollectionType.LiveTvNowPlaying:
{
var result = await _liveTvManager.GetRecommendedProgramsInternal(new RecommendedProgramQuery
{
UserId = query.User.Id.ToString("N"),
Limit = query.Limit,
IsAiring = true
}, CancellationToken.None).ConfigureAwait(false);
return GetResult(result);
}
case CollectionType.LiveTvRecordingGroups:
{
var result = await _liveTvManager.GetInternalRecordings(new RecordingQuery
{
UserId = query.User.Id.ToString("N"),
Status = RecordingStatus.Completed,
Limit = query.Limit,
StartIndex = query.StartIndex
}, CancellationToken.None).ConfigureAwait(false);
return GetResult(result);
}
case CollectionType.LiveTv:
{
var result = await GetLiveTvFolders(user).ConfigureAwait(false);
return GetResult(result, query);
}
case CollectionType.Folders:
return GetResult(user.RootFolder.GetChildren(user, true), query);
case CollectionType.Games:
return await GetGameView(user, parent, query).ConfigureAwait(false);
case CollectionType.BoxSets:
return GetResult(GetMediaFolders(user).SelectMany(i => i.GetRecursiveChildren(user)).OfType<BoxSet>(), query);
case CollectionType.TvShows:
return await GetTvView(parent, user, query).ConfigureAwait(false);
case CollectionType.Music:
return await GetMusicFolders(parent, user, query).ConfigureAwait(false);
case CollectionType.Movies:
return await GetMovieFolders(parent, user, query).ConfigureAwait(false);
case CollectionType.GameGenres:
return GetGameGenres(parent, user, query);
case CollectionType.GameSystems:
return GetGameSystems(parent, user, query);
case CollectionType.LatestGames:
return GetLatestGames(parent, user, query);
case CollectionType.RecentlyPlayedGames:
return GetRecentlyPlayedGames(parent, user, query);
case CollectionType.GameFavorites:
return GetFavoriteGames(parent, user, query);
case CollectionType.TvSeries:
return GetTvSeries(parent, user, query);
case CollectionType.TvGenres:
return GetTvGenres(parent, user, query);
case CollectionType.TvResume:
return GetTvResume(parent, user, query);
case CollectionType.TvNextUp:
return GetTvNextUp(parent, query);
case CollectionType.TvLatest:
return GetTvLatest(parent, user, query);
case CollectionType.MovieFavorites:
return GetFavoriteMovies(parent, user, query);
case CollectionType.MovieLatest:
return GetMovieLatest(parent, user, query);
case CollectionType.MovieGenres:
return GetMovieGenres(parent, user, query);
case CollectionType.MovieResume:
return GetMovieResume(parent, user, query);
case CollectionType.MovieMovies:
return GetMovieMovies(parent, user, query);
case CollectionType.MovieCollections:
return GetMovieCollections(parent, user, query);
default:
return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), query);
}
}
private int GetSpecialItemsLimit()
{
return 50;
}
private async Task<QueryResult<BaseItem>> GetMusicFolders(Folder parent, User user, UserItemsQuery query)
{
if (query.Recursive)
{
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }), query);
}
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }).OfType<MusicArtist>(), query);
}
private async Task<QueryResult<BaseItem>> GetMovieFolders(Folder parent, User user, UserItemsQuery query)
{
if (query.Recursive)
{
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie || i is BoxSet), query);
}
var list = new List<BaseItem>();
list.Add(await GetUserView(CollectionType.MovieResume, user, "0", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.MovieLatest, user, "1", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.MovieMovies, user, "2", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.MovieCollections, user, "3", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.MovieFavorites, user, "4", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.MovieGenres, user, "5", parent).ConfigureAwait(false));
return GetResult(list, query);
}
private QueryResult<BaseItem> GetFavoriteMovies(Folder parent, User user, UserItemsQuery query)
{
query.IsFavorite = true;
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), query);
}
private QueryResult<BaseItem> GetMovieMovies(Folder parent, User user, UserItemsQuery query)
{
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), query);
}
private QueryResult<BaseItem> GetMovieCollections(Folder parent, User user, UserItemsQuery query)
{
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is BoxSet), query);
}
private QueryResult<BaseItem> GetMovieLatest(Folder parent, User user, UserItemsQuery query)
{
query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), GetSpecialItemsLimit(), query);
}
private QueryResult<BaseItem> GetMovieResume(Folder parent, User user, UserItemsQuery query)
{
query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
query.IsResumable = true;
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), GetSpecialItemsLimit(), query);
}
private QueryResult<BaseItem> GetMovieGenres(Folder parent, User user, UserItemsQuery query)
{
var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty })
.Where(i => i is Movie)
.SelectMany(i => i.Genres)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(i =>
{
try
{
return _libraryManager.GetGenre(i);
}
catch
{
// Full exception logged at lower levels
_logger.Error("Error getting genre");
return null;
}
})
.Where(i => i != null);
return GetResult(genres, query);
}
private async Task<QueryResult<BaseItem>> GetTvView(Folder parent, User user, UserItemsQuery query)
{
if (query.Recursive)
{
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).Where(i => i is Series || i is Season || i is Episode), query);
}
var list = new List<BaseItem>();
list.Add(await GetUserView(CollectionType.TvResume, user, "0", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.TvNextUp, user, "1", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.TvLatest, user, "2", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.TvSeries, user, "3", parent).ConfigureAwait(false));
//list.Add(await GetUserView(CollectionType.TvFavorites, user, "4", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.TvGenres, user, "5", parent).ConfigureAwait(false));
return GetResult(list, query);
}
private async Task<QueryResult<BaseItem>> GetGameView(User user, Folder parent, UserItemsQuery query)
{
if (query.Recursive)
{
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }), query);
}
var list = new List<BaseItem>();
list.Add(await GetUserView(CollectionType.LatestGames, user, "0", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.GameFavorites, user, "2", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.GameSystems, user, "3", parent).ConfigureAwait(false));
list.Add(await GetUserView(CollectionType.GameGenres, user, "4", parent).ConfigureAwait(false));
return GetResult(list, query);
}
private QueryResult<BaseItem> GetLatestGames(Folder parent, User user, UserItemsQuery query)
{
query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<Game>(), GetSpecialItemsLimit(), query);
}
private QueryResult<BaseItem> GetRecentlyPlayedGames(Folder parent, User user, UserItemsQuery query)
{
query.IsPlayed = true;
query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<Game>(), GetSpecialItemsLimit(), query);
}
private QueryResult<BaseItem> GetFavoriteGames(Folder parent, User user, UserItemsQuery query)
{
query.IsFavorite = true;
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<Game>(), query);
}
private QueryResult<BaseItem> GetTvLatest(Folder parent, User user, UserItemsQuery query)
{
query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Episode>(), GetSpecialItemsLimit(), query);
}
private QueryResult<BaseItem> GetTvNextUp(Folder parent, UserItemsQuery query)
{
var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows, string.Empty });
var result = _tvSeriesManager.GetNextUp(new NextUpQuery
{
Limit = query.Limit,
StartIndex = query.StartIndex,
UserId = query.User.Id.ToString("N")
}, parentFolders);
return result;
}
private QueryResult<BaseItem> GetTvResume(Folder parent, User user, UserItemsQuery query)
{
query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
query.IsResumable = true;
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Episode>(), GetSpecialItemsLimit(), query);
}
private QueryResult<BaseItem> GetTvSeries(Folder parent, User user, UserItemsQuery query)
{
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Series>(), query);
}
private QueryResult<BaseItem> GetTvGenres(Folder parent, User user, UserItemsQuery query)
{
var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty })
.OfType<Series>()
.SelectMany(i => i.Genres)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(i =>
{
try
{
return _libraryManager.GetGenre(i);
}
catch
{
// Full exception logged at lower levels
_logger.Error("Error getting genre");
return null;
}
})
.Where(i => i != null);
return GetResult(genres, query);
}
private QueryResult<BaseItem> GetGameSystems(Folder parent, User user, UserItemsQuery query)
{
return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<GameSystem>(), query);
}
private QueryResult<BaseItem> GetGameGenres(Folder parent, User user, UserItemsQuery query)
{
var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.Games })
.OfType<Game>()
.SelectMany(i => i.Genres)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(i =>
{
try
{
return _libraryManager.GetGameGenre(i);
}
catch
{
// Full exception logged at lower levels
_logger.Error("Error getting game genre");
return null;
}
})
.Where(i => i != null);
return GetResult(genres, query);
}
private QueryResult<BaseItem> GetResult<T>(QueryResult<T> result)
where T : BaseItem
{
return new QueryResult<BaseItem>
{
Items = result.Items,
TotalRecordCount = result.TotalRecordCount
};
}
private QueryResult<BaseItem> GetResult<T>(IEnumerable<T> items,
UserItemsQuery query)
where T : BaseItem
{
return GetResult(items, null, query);
}
private QueryResult<BaseItem> GetResult<T>(IEnumerable<T> items,
int? totalRecordLimit,
UserItemsQuery query)
where T : BaseItem
{
return SortAndFilter(items, totalRecordLimit, query, _libraryManager, _userDataManager);
}
public static QueryResult<BaseItem> SortAndFilter(IEnumerable<BaseItem> items,
int? totalRecordLimit,
UserItemsQuery query,
ILibraryManager libraryManager,
IUserDataManager userDataManager)
{
var user = query.User;
items = items.Where(i => Filter(i, user, query, userDataManager));
return Sort(items, totalRecordLimit, query, libraryManager);
}
public static QueryResult<BaseItem> Sort(IEnumerable<BaseItem> items,
int? totalRecordLimit,
UserItemsQuery query,
ILibraryManager libraryManager)
{
var user = query.User;
items = libraryManager.ReplaceVideosWithPrimaryVersions(items);
if (query.SortBy.Length > 0)
{
items = libraryManager.Sort(items, user, query.SortBy, query.SortOrder);
}
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<BaseItem>
{
TotalRecordCount = totalCount,
Items = itemsArray
};
}
private static bool Filter(BaseItem item, User user, UserItemsQuery query, IUserDataManager userDataManager)
{
if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (query.IsFolder.HasValue && query.IsFolder.Value != item.IsFolder)
{
return false;
}
if (query.Filter != null && !query.Filter(item, user))
{
return false;
}
UserItemData userData = null;
if (query.IsFavorite.HasValue)
{
userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey());
if (userData.IsFavorite != query.IsFavorite.Value)
{
return false;
}
}
if (query.IsResumable.HasValue)
{
userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey());
var isResumable = userData.PlaybackPositionTicks > 0;
if (isResumable != query.IsResumable.Value)
{
return false;
}
}
if (query.IsPlayed.HasValue)
{
if (item.IsPlayed(user) != query.IsPlayed.Value)
{
return false;
}
}
return true;
}
private IEnumerable<Folder> GetMediaFolders(User user)
{
var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList();
return user.RootFolder
.GetChildren(user, true, true)
.OfType<Folder>()
.Where(i => !excludeFolderIds.Contains(i.Id) && !UserView.IsExcludedFromGrouping(i));
}
private IEnumerable<Folder> GetMediaFolders(User user, string[] viewTypes)
{
return GetMediaFolders(user)
.Where(i =>
{
var folder = i as ICollectionFolder;
return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
});
}
private IEnumerable<Folder> GetMediaFolders(Folder parent, User user, string[] viewTypes)
{
if (parent == null || parent is UserView)
{
return GetMediaFolders(user, viewTypes);
}
return new[] { parent };
}
private IEnumerable<BaseItem> GetRecursiveChildren(Folder parent, User user, string[] viewTypes)
{
if (parent == null || parent is UserView)
{
return GetMediaFolders(user, viewTypes).SelectMany(i => i.GetRecursiveChildren(user));
}
return parent.GetRecursiveChildren(user);
}
private async Task<IEnumerable<BaseItem>> GetLiveTvFolders(User user)
{
var list = new List<BaseItem>();
list.Add(await _userViewManager.GetUserView(CollectionType.LiveTvNowPlaying, user, "0", CancellationToken.None).ConfigureAwait(false));
list.Add(await _userViewManager.GetUserView(CollectionType.LiveTvChannels, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
list.Add(await _userViewManager.GetUserView(CollectionType.LiveTvRecordingGroups, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
return list;
}
private async Task<UserView> GetUserView(string type, User user, string sortName, Folder parent)
{
var view = await _userViewManager.GetUserView(type, user, sortName, CancellationToken.None)
.ConfigureAwait(false);
if (parent.Id != view.ParentId)
{
view.ParentId = parent.Id;
await view.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None)
.ConfigureAwait(false);
}
return view;
}
}
}

@ -244,6 +244,15 @@ namespace MediaBrowser.Controller.LiveTv
Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query,
CancellationToken cancellationToken);
/// <summary>
/// Gets the recommended programs internal.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;QueryResult&lt;LiveTvProgram&gt;&gt;.</returns>
Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query,
CancellationToken cancellationToken);
/// <summary>
/// Gets the live tv information.
/// </summary>

@ -157,7 +157,9 @@
<Compile Include="Entities\IHasAwards.cs" />
<Compile Include="Entities\Photo.cs" />
<Compile Include="Entities\PhotoAlbum.cs" />
<Compile Include="Entities\UserItemsQuery.cs" />
<Compile Include="Entities\UserView.cs" />
<Compile Include="Entities\UserViewBuilder.cs" />
<Compile Include="FileOrganization\IFileOrganizationService.cs" />
<Compile Include="Library\DeleteOptions.cs" />
<Compile Include="Library\ILibraryPostScanTask.cs" />
@ -336,6 +338,7 @@
<Compile Include="Sync\ISyncRepository.cs" />
<Compile Include="Themes\IAppThemeManager.cs" />
<Compile Include="Themes\InternalThemeImage.cs" />
<Compile Include="TV\ITVSeriesManager.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
@ -360,7 +363,7 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -0,0 +1,24 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Querying;
using System.Collections.Generic;
namespace MediaBrowser.Controller.TV
{
public interface ITVSeriesManager
{
/// <summary>
/// Gets the next up.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>QueryResult&lt;BaseItem&gt;.</returns>
QueryResult<BaseItem> GetNextUp(NextUpQuery query);
/// <summary>
/// Gets the next up.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="parentsFolders">The parents folders.</param>
/// <returns>QueryResult&lt;BaseItem&gt;.</returns>
QueryResult<BaseItem> GetNextUp(NextUpQuery request, IEnumerable<Folder> parentsFolders);
}
}

@ -1,5 +1,4 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
@ -22,8 +21,6 @@ namespace MediaBrowser.Dlna.ContentDirectory
private readonly IDlnaManager _dlna;
private readonly IServerConfigurationManager _config;
private readonly IUserManager _userManager;
private readonly IUserViewManager _userViewManager;
private readonly IChannelManager _channelManager;
public ContentDirectory(IDlnaManager dlna,
IUserDataManager userDataManager,
@ -32,7 +29,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
IServerConfigurationManager config,
IUserManager userManager,
ILogger logger,
IHttpClient httpClient, IUserViewManager userViewManager, IChannelManager channelManager)
IHttpClient httpClient)
: base(logger, httpClient)
{
_dlna = dlna;
@ -41,8 +38,6 @@ namespace MediaBrowser.Dlna.ContentDirectory
_libraryManager = libraryManager;
_config = config;
_userManager = userManager;
_userViewManager = userViewManager;
_channelManager = channelManager;
}
private int SystemUpdateId
@ -78,9 +73,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
_userDataManager,
user,
SystemUpdateId,
_config,
_userViewManager,
_channelManager)
_config)
.ProcessControlRequest(request);
}

@ -1,26 +1,18 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Dlna.Didl;
using MediaBrowser.Dlna.Server;
using MediaBrowser.Dlna.Service;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -45,21 +37,17 @@ namespace MediaBrowser.Dlna.ContentDirectory
private readonly DidlBuilder _didlBuilder;
private readonly DeviceProfile _profile;
private readonly IUserViewManager _userViewManager;
private readonly IChannelManager _channelManager;
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, IUserViewManager userViewManager, IChannelManager channelManager)
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config)
: base(config, logger)
{
_libraryManager = libraryManager;
_userDataManager = userDataManager;
_user = user;
_systemUpdateId = systemUpdateId;
_userViewManager = userViewManager;
_channelManager = channelManager;
_profile = profile;
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress);
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, userDataManager);
}
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
@ -202,7 +190,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
{
var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
var childrenResult = (await GetUserItems(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
totalCount = childrenResult.TotalRecordCount;
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, totalCount, filter, id));
@ -213,7 +201,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
{
var folder = (Folder)item;
var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
var childrenResult = (await GetUserItems(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Length;
@ -223,7 +211,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
if (i.IsFolder)
{
var f = (Folder)i;
var childCount = (await GetChildrenSorted(f, user, sortCriteria, null, 0).ConfigureAwait(false))
var childCount = (await GetUserItems(f, user, sortCriteria, null, 0).ConfigureAwait(false))
.TotalRecordCount;
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter));
@ -321,216 +309,97 @@ namespace MediaBrowser.Dlna.ContentDirectory
private async Task<QueryResult<BaseItem>> GetChildrenSorted(Folder folder, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
{
// TODO: Make a recursive version of GetChildrenSorted (although sorting isn't needed)
var result = folder.GetRecursiveChildren(user, true);
var sortOrders = new List<string>();
if (!folder.IsPreSorted)
{
sortOrders.Add(ItemSortBy.SortName);
}
var items = FilterUnsupportedContent(result);
var mediaTypes = new List<string>();
bool? isFolder = null;
if (search.SearchType == SearchType.Audio)
{
items = items.OfType<Audio>();
mediaTypes.Add(MediaType.Audio);
isFolder = false;
}
else if (search.SearchType == SearchType.Video)
{
items = items.OfType<Video>();
mediaTypes.Add(MediaType.Video);
isFolder = false;
}
else if (search.SearchType == SearchType.Image)
{
items = items.OfType<Photo>();
mediaTypes.Add(MediaType.Photo);
isFolder = false;
}
else if (search.SearchType == SearchType.Playlist)
{
items = items.OfType<Playlist>();
//items = items.OfType<Playlist>();
isFolder = true;
}
else if (search.SearchType == SearchType.MusicAlbum)
{
items = items.OfType<MusicAlbum>();
//items = items.OfType<MusicAlbum>();
isFolder = true;
}
items = SortItems(items, user, sort);
return ToResult(items, startIndex, limit);
return await folder.GetUserItems(new UserItemsQuery
{
Limit = limit,
StartIndex = startIndex,
SortBy = sortOrders.ToArray(),
SortOrder = sort.SortOrder,
User = user,
Recursive = true,
Filter = FilterUnsupportedContent,
IsFolder = isFolder,
MediaTypes = mediaTypes.ToArray()
}).ConfigureAwait(false);
}
private async Task<QueryResult<BaseItem>> GetChildrenSorted(Folder folder, User user, SortCriteria sort, int? startIndex, int? limit)
private async Task<QueryResult<BaseItem>> GetUserItems(Folder folder, User user, SortCriteria sort, int? startIndex, int? limit)
{
if (folder is UserRootFolder)
{
var result = await _userViewManager.GetUserViews(new UserViewQuery
{
UserId = user.Id.ToString("N")
}, CancellationToken.None).ConfigureAwait(false);
return ToResult(result, startIndex, limit);
}
var view = folder as UserView;
if (view != null)
var sortOrders = new List<string>();
if (!folder.IsPreSorted)
{
var result = await GetUserViewChildren(view, user, sort).ConfigureAwait(false);
return ToResult(result, startIndex, limit);
sortOrders.Add(ItemSortBy.SortName);
}
var channel = folder as Channel;
if (channel != null)
return await folder.GetUserItems(new UserItemsQuery
{
try
{
// Don't blow up here because it could cause parent screens with other content to fail
return await _channelManager.GetChannelItemsInternal(new ChannelItemQuery
{
ChannelId = channel.Id.ToString("N"),
Limit = limit,
StartIndex = startIndex,
UserId = user.Id.ToString("N")
}, CancellationToken.None);
}
catch
{
// Already logged at lower levels
}
}
Limit = limit,
StartIndex = startIndex,
SortBy = sortOrders.ToArray(),
SortOrder = sort.SortOrder,
User = user,
Filter = FilterUnsupportedContent
var channelFolderItem = folder as ChannelFolderItem;
if (channelFolderItem != null)
{
try
{
// Don't blow up here because it could cause parent screens with other content to fail
return await _channelManager.GetChannelItemsInternal(new ChannelItemQuery
{
ChannelId = channelFolderItem.ChannelId,
FolderId = channelFolderItem.Id.ToString("N"),
Limit = limit,
StartIndex = startIndex,
UserId = user.Id.ToString("N")
}, CancellationToken.None);
}
catch
{
// Already logged at lower levels
}
}
return ToResult(GetPlainFolderChildrenSorted(folder, user, sort), startIndex, limit);
}).ConfigureAwait(false);
}
private QueryResult<BaseItem> ToResult(IEnumerable<BaseItem> items, int? startIndex, int? limit)
private bool FilterUnsupportedContent(BaseItem i, User user)
{
var list = items.ToArray();
var totalCount = list.Length;
if (startIndex.HasValue)
// Unplayable
if (i.LocationType == LocationType.Virtual && !i.IsFolder)
{
list = list.Skip(startIndex.Value).ToArray();
return false;
}
if (limit.HasValue)
// Unplayable
var supportsPlaceHolder = i as ISupportsPlaceHolders;
if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder)
{
list = list.Take(limit.Value).ToArray();
return false;
}
return new QueryResult<BaseItem>
if (i is Game || i is Book)
{
Items = list,
TotalRecordCount = totalCount
};
}
private async Task<IEnumerable<BaseItem>> GetUserViewChildren(UserView folder, User user, SortCriteria sort)
{
if (string.Equals(folder.ViewType, CollectionType.Channels, StringComparison.OrdinalIgnoreCase))
{
var result = await _channelManager.GetChannelsInternal(new ChannelQuery()
{
UserId = user.Id.ToString("N")
}, CancellationToken.None).ConfigureAwait(false);
return result.Items;
//return false;
}
if (string.Equals(folder.ViewType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
return SortItems(folder.GetChildren(user, true).OfType<Series>(), user, sort);
}
if (string.Equals(folder.ViewType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
return SortItems(folder.GetRecursiveChildren(user, true).OfType<Movie>(), user, sort);
}
if (string.Equals(folder.ViewType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
{
return SortItems(folder.GetChildren(user, true).OfType<MusicArtist>(), user, sort);
}
if (string.Equals(folder.ViewType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
{
return SortItems(folder.GetChildren(user, true), user, sort);
}
if (string.Equals(folder.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
{
return SortItems(folder.GetChildren(user, true), user, sort);
}
if (string.Equals(folder.ViewType, CollectionType.LiveTvRecordingGroups, StringComparison.OrdinalIgnoreCase))
{
return SortItems(folder.GetChildren(user, true), user, sort);
}
if (string.Equals(folder.ViewType, CollectionType.LiveTvChannels, StringComparison.OrdinalIgnoreCase))
{
return SortItems(folder.GetChildren(user, true), user, sort);
}
return GetPlainFolderChildrenSorted(folder, user, sort);
}
private IEnumerable<BaseItem> GetPlainFolderChildrenSorted(Folder folder, User user, SortCriteria sort)
{
var items = folder.GetChildren(user, true);
items = FilterUnsupportedContent(items);
if (folder.IsPreSorted)
{
return items;
}
return SortItems(items, user, sort);
}
private IEnumerable<BaseItem> SortItems(IEnumerable<BaseItem> items, User user, SortCriteria sort)
{
return _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, sort.SortOrder);
}
private IEnumerable<BaseItem> FilterUnsupportedContent(IEnumerable<BaseItem> items)
{
return items.Where(i =>
{
// Unplayable
if (i.LocationType == LocationType.Virtual && !i.IsFolder)
{
return false;
}
// Unplayable
var supportsPlaceHolder = i as ISupportsPlaceHolders;
if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder)
{
return false;
}
if (i is Game || i is Book)
{
return false;
}
return true;
});
return true;
}
private BaseItem GetItemFromObjectId(string id, User user)

@ -1,4 +1,4 @@
using System.IO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing;
@ -6,16 +6,16 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Dlna.Didl
{
@ -32,12 +32,14 @@ namespace MediaBrowser.Dlna.Didl
private readonly IImageProcessor _imageProcessor;
private readonly string _serverAddress;
private readonly User _user;
private readonly IUserDataManager _userDataManager;
public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress)
public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, IUserDataManager userDataManager)
{
_profile = profile;
_imageProcessor = imageProcessor;
_serverAddress = serverAddress;
_userDataManager = userDataManager;
_user = user;
}
@ -677,7 +679,20 @@ namespace MediaBrowser.Dlna.Didl
var result = element.OwnerDocument;
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
var playbackPercentage = 0;
if (item is Video)
{
var userData = _userDataManager.GetUserDataDto(item, _user);
playbackPercentage = Convert.ToInt32(userData.PlayedPercentage ?? 0);
if (playbackPercentage >= 100)
{
playbackPercentage = 0;
}
}
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, playbackPercentage, "jpg");
var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
@ -687,7 +702,7 @@ namespace MediaBrowser.Dlna.Didl
element.AppendChild(icon);
// TOOD: Remove these default values
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, playbackPercentage, "jpg");
icon = result.CreateElement("upnp", "icon", NS_UPNP);
icon.InnerText = iconUrlInfo.Url;
element.AppendChild(icon);
@ -703,18 +718,19 @@ namespace MediaBrowser.Dlna.Didl
}
}
AddImageResElement(item, element, 4096, 4096, "jpg", "JPEG_LRG");
AddImageResElement(item, element, 4096, 4096, "png", "PNG_LRG");
AddImageResElement(item, element, 1024, 768, "jpg", "JPEG_MED");
AddImageResElement(item, element, 640, 480, "jpg", "JPEG_SM");
AddImageResElement(item, element, 160, 160, "jpg", "JPEG_TN");
AddImageResElement(item, element, 160, 160, "png", "PNG_TN");
AddImageResElement(item, element, 4096, 4096, playbackPercentage, "jpg", "JPEG_LRG");
AddImageResElement(item, element, 4096, 4096, playbackPercentage, "png", "PNG_LRG");
AddImageResElement(item, element, 1024, 768, playbackPercentage, "jpg", "JPEG_MED");
AddImageResElement(item, element, 640, 480, playbackPercentage, "jpg", "JPEG_SM");
AddImageResElement(item, element, 160, 160, playbackPercentage, "jpg", "JPEG_TN");
AddImageResElement(item, element, 160, 160, playbackPercentage, "png", "PNG_TN");
}
private void AddImageResElement(BaseItem item,
XmlElement element,
int maxWidth,
int maxHeight,
int playbackPercentage,
string format,
string org_Pn)
{
@ -727,7 +743,7 @@ namespace MediaBrowser.Dlna.Didl
var result = element.OwnerDocument;
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format);
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, playbackPercentage, format);
var res = result.CreateElement(string.Empty, "res", NS_DIDL);
@ -849,16 +865,18 @@ namespace MediaBrowser.Dlna.Didl
internal int? Height;
}
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, int playbackPercentage, string format)
{
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}",
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/{7}",
_serverAddress,
info.ItemId,
info.Type,
info.ImageTag,
format,
maxWidth,
maxHeight);
maxWidth.ToString(CultureInfo.InvariantCulture),
maxHeight.ToString(CultureInfo.InvariantCulture),
playbackPercentage.ToString(CultureInfo.InvariantCulture)
);
var width = info.Width;
var height = info.Height;

@ -34,14 +34,15 @@ namespace MediaBrowser.Dlna.Main
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
private readonly IImageProcessor _imageProcessor;
private readonly IUserDataManager _userDataManager;
private SsdpHandler _ssdpHandler;
private DeviceDiscovery _deviceDiscovery;
private readonly List<string> _registeredServerIds = new List<string>();
private bool _dlnaServerStarted;
public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IImageProcessor imageProcessor)
public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IImageProcessor imageProcessor, IUserDataManager userDataManager)
{
_config = config;
_appHost = appHost;
@ -53,6 +54,7 @@ namespace MediaBrowser.Dlna.Main
_userManager = userManager;
_dlnaManager = dlnaManager;
_imageProcessor = imageProcessor;
_userDataManager = userDataManager;
_logger = logManager.GetLogger("Dlna");
}
@ -218,7 +220,8 @@ namespace MediaBrowser.Dlna.Main
_imageProcessor,
_deviceDiscovery,
_httpClient,
_config);
_config,
_userDataManager);
_manager.Start();
}

@ -32,6 +32,7 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IDlnaManager _dlnaManager;
private readonly IUserManager _userManager;
private readonly IImageProcessor _imageProcessor;
private readonly IUserDataManager _userDataManager;
private readonly DeviceDiscovery _deviceDiscovery;
private readonly string _serverAddress;
@ -51,7 +52,7 @@ namespace MediaBrowser.Dlna.PlayTo
private Timer _updateTimer;
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, DeviceDiscovery deviceDiscovery)
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, DeviceDiscovery deviceDiscovery, IUserDataManager userDataManager)
{
_session = session;
_itemRepository = itemRepository;
@ -62,6 +63,7 @@ namespace MediaBrowser.Dlna.PlayTo
_imageProcessor = imageProcessor;
_serverAddress = serverAddress;
_deviceDiscovery = deviceDiscovery;
_userDataManager = userDataManager;
_logger = logger;
}
@ -474,7 +476,7 @@ namespace MediaBrowser.Dlna.PlayTo
playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
var itemXml = new DidlBuilder(profile, user, _imageProcessor, serverAddress).GetItemDidl(item, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
var itemXml = new DidlBuilder(profile, user, _imageProcessor, serverAddress, _userDataManager).GetItemDidl(item, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
playlistItem.Didl = itemXml;

@ -29,10 +29,11 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IImageProcessor _imageProcessor;
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
private readonly IUserDataManager _userDataManager;
private readonly DeviceDiscovery _deviceDiscovery;
public PlayToManager(ILogger logger, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, DeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config)
public PlayToManager(ILogger logger, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, DeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager)
{
_logger = logger;
_sessionManager = sessionManager;
@ -45,6 +46,7 @@ namespace MediaBrowser.Dlna.PlayTo
_deviceDiscovery = deviceDiscovery;
_httpClient = httpClient;
_config = config;
_userDataManager = userDataManager;
}
public void Start()
@ -103,7 +105,8 @@ namespace MediaBrowser.Dlna.PlayTo
_userManager,
_imageProcessor,
serverAddress,
_deviceDiscovery);
_deviceDiscovery,
_userDataManager);
controller.Init(device);

@ -22,6 +22,8 @@ namespace MediaBrowser.Dlna.Ssdp
/// </summary>
public int SendCount { get; private set; }
public bool HandleBindError { get; set; }
private readonly ILogger _logger;
public Datagram(IPEndPoint toEndPoint, IPEndPoint fromEndPoint, ILogger logger, string message, int totalSendCount)
@ -42,7 +44,17 @@ namespace MediaBrowser.Dlna.Ssdp
if (FromEndPoint != null)
{
client.Bind(FromEndPoint);
try
{
client.Bind(FromEndPoint);
}
catch
{
if (!HandleBindError)
{
throw;
}
}
}
client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, ToEndPoint, result =>

@ -65,7 +65,7 @@ namespace MediaBrowser.Dlna.Ssdp
string mx = null;
args.Headers.TryGetValue("mx", out mx);
int delaySeconds;
if (!string.IsNullOrWhiteSpace(mx) &&
if (!string.IsNullOrWhiteSpace(mx) &&
int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds)
&& delaySeconds > 0)
{
@ -124,18 +124,23 @@ namespace MediaBrowser.Dlna.Ssdp
IPEndPoint localAddress,
int sendCount = 1)
{
SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
SendDatagram(header, values, _ssdpEndp, localAddress, false, sendCount);
}
public void SendDatagram(string header,
Dictionary<string, string> values,
IPEndPoint endpoint,
IPEndPoint localAddress,
bool handleBindError,
int sendCount = 1)
{
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount);
var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount)
{
HandleBindError = handleBindError
};
if (_messageQueue.Count == 0)
{
dgram.Send();
@ -170,10 +175,7 @@ namespace MediaBrowser.Dlna.Ssdp
values["ST"] = d.Type;
values["USN"] = d.USN;
// Commenting this out because binding to the local ipendpoint often throws an error
//SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0));
SendDatagram(header, values, endpoint, null);
SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0), true);
if (_config.GetDlnaConfiguration().EnableDebugLogging)
{

@ -91,7 +91,7 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -26,7 +26,28 @@
public const string Playlists = "playlists";
public const string Folders = "folders";
public const string LiveTvNowPlaying = "LiveTvNowPlaying";
public const string LiveTvChannels = "LiveTvChannels";
public const string LiveTvRecordingGroups = "LiveTvRecordingGroups";
public const string TvSeries = "TvSeries";
public const string TvGenres = "TvGenres";
public const string TvLatest = "TvLatest";
public const string TvNextUp = "TvNextUp";
public const string TvResume = "TvResume";
public const string TvFavorites = "TvFavorites";
public const string MovieLatest = "MovieLatest";
public const string MovieResume = "MovieResume";
public const string MovieMovies = "MovieMovies";
public const string MovieCollections = "MovieCollections";
public const string MovieFavorites = "MovieFavorites";
public const string MovieGenres = "MovieGenres";
public const string LatestGames = "LatestGames";
public const string RecentlyPlayedGames = "RecentlyPlayedGames";
public const string GameSystems = "GameSystems";
public const string GameGenres = "GameGenres";
public const string GameFavorites = "GameFavorites";
}
}

@ -372,7 +372,7 @@
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i
)</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<Import Project="Fody.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

@ -211,7 +211,7 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -198,7 +198,7 @@ namespace MediaBrowser.Server.Implementations.Connect
private string GetConnectUrl(string handler)
{
return "http://mb3admin.com/admin/connect/" + handler;
return "http://mediabrowser.tv:8095/" + handler;
}
}
}

@ -196,7 +196,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
try
{
var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed.HasValue;
var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed > 0;
using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
{
@ -308,7 +308,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="options">The options.</param>
private void DrawIndicator(Graphics graphics, int imageWidth, int imageHeight, ImageProcessingOptions options)
{
if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && !options.PercentPlayed.HasValue)
if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && options.PercentPlayed.Equals(0))
{
return;
}
@ -328,11 +328,11 @@ namespace MediaBrowser.Server.Implementations.Drawing
new UnplayedCountIndicator().DrawUnplayedCountIndicator(graphics, currentImageSize, options.UnplayedCount.Value);
}
if (options.PercentPlayed.HasValue)
if (options.PercentPlayed >= 0)
{
var currentImageSize = new Size(imageWidth, imageHeight);
new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed.Value);
new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed);
}
}
catch (Exception ex)
@ -437,7 +437,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <summary>
/// Gets the cache file path based on a set of parameters
/// </summary>
private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, bool addPlayedIndicator, double? percentPlayed, int? unwatchedCount, string backgroundColor)
private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, string backgroundColor)
{
var filename = originalPath;
@ -462,9 +462,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
hasIndicator = true;
}
if (percentPlayed.HasValue)
if (percentPlayed > 0)
{
filename += "p=" + percentPlayed.Value;
filename += "p=" + percentPlayed;
hasIndicator = true;
}

@ -726,7 +726,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return result;
}
public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken)
public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken)
{
IEnumerable<LiveTvProgram> programs = _programs.Values;
@ -771,7 +771,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv
await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false);
var returnArray = programList
var returnArray = programList.ToArray();
var result = new QueryResult<LiveTvProgram>
{
Items = returnArray,
TotalRecordCount = returnArray.Length
};
return result;
}
public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken)
{
var internalResult = await GetRecommendedProgramsInternal(query, cancellationToken).ConfigureAwait(false);
var user = _userManager.GetUserById(new Guid(query.UserId));
var returnArray = internalResult.Items
.Select(i =>
{
var channel = GetChannel(i);
@ -785,7 +802,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var result = new QueryResult<ProgramInfoDto>
{
Items = returnArray,
TotalRecordCount = returnArray.Length
TotalRecordCount = internalResult.TotalRecordCount
};
return result;

@ -841,6 +841,24 @@
"ViewTypeBoxSets": "Collections",
"ViewTypeChannels": "Channels",
"ViewTypeLiveTV": "Live TV",
"ViewTypeLiveTvNowPlaying": "Now Airing",
"ViewTypeLatestGames": "Latest Games",
"ViewTypeRecentlyPlayedGames": "Recently Played",
"ViewTypeGameFavorites": "Favorites",
"ViewTypeGameSystems": "Game Systems",
"ViewTypeGameGenres": "Genres",
"ViewTypeTvResume": "Resume",
"ViewTypeTvNextUp": "Next Up",
"ViewTypeTvLatest": "Latest",
"ViewTypeTvSeries": "Series",
"ViewTypeTvGenres": "Genres",
"ViewTypeTvFavorites": "Favorites",
"ViewTypeMovieResume": "Resume",
"ViewTypeMovieLatest": "Latest",
"ViewTypeMovieMovies": "Movies",
"ViewTypeMovieCollections": "Collections",
"ViewTypeMovieFavorites": "Favorites",
"ViewTypeMovieGenres": "Genres",
"HeaderOtherDisplaySettings": "Display Settings",
"HeaderMyViews": "My Views",
"LabelSelectFolderGroups": "Automatically group content from the following folders into views such as Movies, Music and TV:",

@ -293,6 +293,7 @@
<Compile Include="Sync\SyncManager.cs" />
<Compile Include="Sync\SyncRepository.cs" />
<Compile Include="Themes\AppThemeManager.cs" />
<Compile Include="TV\TVSeriesManager.cs" />
<Compile Include="Udp\UdpMessageReceivedEventArgs.cs" />
<Compile Include="Udp\UdpServer.cs" />
</ItemGroup>
@ -500,7 +501,7 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
@ -508,4 +509,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

@ -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<BaseItem> 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<Series>();
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items);
return GetResult(episodes, null, request);
}
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, IEnumerable<Folder> 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<Series>();
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items);
return GetResult(episodes, null, request);
}
private IEnumerable<BaseItem> 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<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<Series> 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);
}
/// <summary>
/// Gets the next up.
/// </summary>
/// <param name="series">The series.</param>
/// <param name="user">The user.</param>
/// <returns>Task{Episode}.</returns>
private Tuple<Episode, DateTime> 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<Episode, DateTime>(nextUp, lastWatchedDate);
}
return new Tuple<Episode, DateTime>(null, lastWatchedDate);
}
private IEnumerable<Series> FilterSeries(NextUpQuery request, IEnumerable<Series> items)
{
if (!string.IsNullOrWhiteSpace(request.SeriesId))
{
var id = new Guid(request.SeriesId);
items = items.Where(i => i.Id == id);
}
return items;
}
private QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> 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<BaseItem>
{
TotalRecordCount = totalCount,
Items = itemsArray
};
}
}
}

@ -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; }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationHost" /> 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<IAppThemeManager>(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<IContentDirectory>(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;
}
/// <summary>

@ -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<BaseItem> 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<BaseItem> children = isPhysical ? new[] { _libraryManager.RootFolder } : _libraryManager.RootFolder.GetChildren(_currentUser, true);
children = OrderByName(children, _currentUser);
IEnumerable<BaseItem> children = isPhysical ? new[] { _libraryManager.RootFolder } : GetItems(null, _currentUser);
foreach (Folder folder in children)
foreach (var folder in children.OfType<Folder>())
{
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
}
}
/// <summary>
/// Orders the name of the by.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
private IEnumerable<BaseItem> OrderByName(IEnumerable<BaseItem> items, User user)
{
return OrderBy(items, user, ItemSortBy.SortName);
}
/// <summary>
/// Orders the name of the by.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
private IEnumerable<BaseItem> OrderBy(IEnumerable<BaseItem> items, User user, string order)
{
return _libraryManager.Sort(items, user, new[] { order }, SortOrder.Ascending);
}
/// <summary>
/// The INDEN t_ STRING
/// </summary>

@ -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();
}

@ -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)

@ -2136,7 +2136,7 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

Loading…
Cancel
Save