diff options
81 files changed, 1291 insertions, 769 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index bb9d2b864..7c5f7cde4 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -192,13 +192,13 @@ namespace MediaBrowser.Api _activeTranscodingJobs.Add(job); - ReportTranscodingProgress(job, state, null, null, null, null); + ReportTranscodingProgress(job, state, null, null, null, null, null); return job; } } - public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded) + public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) { var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; @@ -208,6 +208,7 @@ namespace MediaBrowser.Api job.CompletionPercentage = percentComplete; job.TranscodingPositionTicks = ticks; job.BytesTranscoded = bytesTranscoded; + job.BitRate = bitRate; } var deviceId = state.Request.DeviceId; @@ -219,7 +220,7 @@ namespace MediaBrowser.Api _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo { - Bitrate = state.TotalOutputBitrate, + Bitrate = bitRate ?? state.TotalOutputBitrate, AudioCodec = audioCodec, VideoCodec = videoCodec, Container = state.OutputContainer, @@ -694,6 +695,7 @@ namespace MediaBrowser.Api public long? BytesDownloaded { get; set; } public long? BytesTranscoded { get; set; } + public int? BitRate { get; set; } public long? TranscodingPositionTicks { get; set; } public long? DownloadPositionTicks { get; set; } diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 44a367be0..3ff432d74 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -139,6 +139,10 @@ namespace MediaBrowser.Api { options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value; } + if (hasDtoOptions.EnableUserData.HasValue) + { + options.EnableUserData = hasDtoOptions.EnableUserData.Value; + } if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes)) { diff --git a/MediaBrowser.Api/IHasDtoOptions.cs b/MediaBrowser.Api/IHasDtoOptions.cs index dac366113..6ed1670c2 100644 --- a/MediaBrowser.Api/IHasDtoOptions.cs +++ b/MediaBrowser.Api/IHasDtoOptions.cs @@ -4,6 +4,7 @@ namespace MediaBrowser.Api public interface IHasDtoOptions : IHasItemFields { bool? EnableImages { get; set; } + bool? EnableUserData { get; set; } int? ImageTypeLimit { get; set; } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 91157cd11..545ac162f 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -82,6 +82,9 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "AddCurrentProgram", Description = "Optional. Adds current program info to each channel", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public bool AddCurrentProgram { get; set; } + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + public GetChannels() { AddCurrentProgram = true; @@ -149,6 +152,9 @@ namespace MediaBrowser.Api.LiveTv public bool EnableTotalRecordCount { get; set; } + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + public GetRecordings() { EnableTotalRecordCount = true; @@ -271,6 +277,9 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string EnableImageTypes { get; set; } + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + /// <summary> /// Fields to return within the items, in addition to basic information /// </summary> @@ -331,6 +340,9 @@ namespace MediaBrowser.Api.LiveTv /// <value>The fields.</value> [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Fields { get; set; } + + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } } [Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")] @@ -726,7 +738,12 @@ namespace MediaBrowser.Api.LiveTv var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(request.UserId); - var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, GetDtoOptions(Request), user).ConfigureAwait(false)).ToArray(); + var options = GetDtoOptions(request); + RemoveFields(options); + + options.AddCurrentProgram = request.AddCurrentProgram; + + var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, options, user).ConfigureAwait(false)).ToArray(); var result = new QueryResult<BaseItemDto> { @@ -737,6 +754,14 @@ namespace MediaBrowser.Api.LiveTv return ToOptimizedSerializedResultUsingCache(result); } + private void RemoveFields(DtoOptions options) + { + options.Fields.Remove(ItemFields.CanDelete); + options.Fields.Remove(ItemFields.CanDownload); + options.Fields.Remove(ItemFields.DisplayPreferencesId); + options.Fields.Remove(ItemFields.Etag); + } + public object Get(GetChannel request) { var user = string.IsNullOrWhiteSpace(request.UserId) ? null : _userManager.GetUserById(request.UserId); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index ac967e194..39fdc98f5 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1172,6 +1172,7 @@ namespace MediaBrowser.Api.Playback double? percent = null; TimeSpan? transcodingPosition = null; long? bytesTranscoded = null; + int? bitRate = null; var parts = line.Split(' '); @@ -1235,11 +1236,32 @@ namespace MediaBrowser.Api.Playback } } } + else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase)) + { + var rate = part.Split(new[] { '=' }, 2).Last(); + + int? scale = null; + if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1) + { + scale = 1024; + rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase); + } + + if (scale.HasValue) + { + float val; + + if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val)) + { + bitRate = (int)Math.Ceiling(val * scale.Value); + } + } + } } if (framerate.HasValue || percent.HasValue) { - ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded); + ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate); } } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 56842e69d..b8cb6b14f 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -370,9 +370,9 @@ namespace MediaBrowser.Api.Playback.Progressive outputHeaders[item.Key] = item.Value; } - Func<Stream, Task> streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream, CancellationToken.None); + var streamSource = new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, CancellationToken.None); - return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders); + return ResultFactory.GetAsyncStreamWriter(streamSource); } finally { diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 63d71b85e..0a9a44641 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -4,38 +4,55 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.Net; +using System.Collections.Generic; +using ServiceStack.Web; namespace MediaBrowser.Api.Playback.Progressive { - public class ProgressiveFileCopier + public class ProgressiveFileCopier : IAsyncStreamSource, IHasOptions { private readonly IFileSystem _fileSystem; private readonly TranscodingJob _job; private readonly ILogger _logger; + private readonly string _path; + private readonly CancellationToken _cancellationToken; + private readonly Dictionary<string, string> _outputHeaders; // 256k private const int BufferSize = 81920; private long _bytesWritten = 0; - public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job, ILogger logger) + public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _fileSystem = fileSystem; + _path = path; + _outputHeaders = outputHeaders; _job = job; _logger = logger; + _cancellationToken = cancellationToken; } - public async Task StreamFile(string path, Stream outputStream, CancellationToken cancellationToken) + public IDictionary<string, string> Options + { + get + { + return _outputHeaders; + } + } + + public async Task WriteToAsync(Stream outputStream) { try { var eofCount = 0; - using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) { while (eofCount < 15) { - var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, cancellationToken).ConfigureAwait(false); + var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false); //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); @@ -46,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Progressive { eofCount++; } - await Task.Delay(100, cancellationToken).ConfigureAwait(false); + await Task.Delay(100, _cancellationToken).ConfigureAwait(false); } else { diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs index 604227a15..969399288 100644 --- a/MediaBrowser.Api/PlaylistService.cs +++ b/MediaBrowser.Api/PlaylistService.cs @@ -72,7 +72,7 @@ namespace MediaBrowser.Api } [Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")] - public class GetPlaylistItems : IReturn<QueryResult<BaseItemDto>>, IHasItemFields + public class GetPlaylistItems : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions { [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } @@ -104,6 +104,18 @@ namespace MediaBrowser.Api /// <value>The fields.</value> [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Fields { get; set; } + + [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableImages { get; set; } + + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + + [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? ImageTypeLimit { get; set; } + + [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string EnableImageTypes { get; set; } } [Authenticated] diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index a1e47bd8f..1621c8056 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -29,8 +29,20 @@ namespace MediaBrowser.Api public string ExcludeArtistIds { get; set; } } - public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasItemFields + public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasDtoOptions { + [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableImages { get; set; } + + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + + [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? ImageTypeLimit { get; set; } + + [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string EnableImageTypes { get; set; } + /// <summary> /// Gets or sets the user id. /// </summary> diff --git a/MediaBrowser.Api/Sync/SyncHelper.cs b/MediaBrowser.Api/Sync/SyncHelper.cs index 0d3e8707d..2f857000c 100644 --- a/MediaBrowser.Api/Sync/SyncHelper.cs +++ b/MediaBrowser.Api/Sync/SyncHelper.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Api.Sync } break; } - if (item.IsFolder && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre) + if (item.IsFolderItem && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre) { options.Add(SyncJobOption.Quality); options.Add(SyncJobOption.Profile); @@ -44,7 +44,7 @@ namespace MediaBrowser.Api.Sync { if (item.SupportsSync ?? false) { - if (item.IsFolder || item.IsGameGenre || item.IsMusicGenre || item.IsGenre || item.IsArtist || item.IsStudio || item.IsPerson) + if (item.IsFolderItem || item.IsGameGenre || item.IsMusicGenre || item.IsGenre || item.IsArtist || item.IsStudio || item.IsPerson) { options.Add(SyncJobOption.SyncNewContent); options.Add(SyncJobOption.ItemLimit); diff --git a/MediaBrowser.Api/Sync/SyncService.cs b/MediaBrowser.Api/Sync/SyncService.cs index a15ce216f..b9544d71b 100644 --- a/MediaBrowser.Api/Sync/SyncService.cs +++ b/MediaBrowser.Api/Sync/SyncService.cs @@ -66,6 +66,7 @@ namespace MediaBrowser.Api.Sync public string Id { get; set; } } + [Route("/Sync/Items/Cancel", "POST", Summary = "Cancels items from a sync target")] [Route("/Sync/{TargetId}/Items", "DELETE", Summary = "Cancels items from a sync target")] public class CancelItems : IReturnVoid { @@ -211,7 +212,7 @@ namespace MediaBrowser.Api.Sync return ToOptimizedResult(result); } - public void Delete(CancelItems request) + public void Any(CancelItems request) { var itemIds = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 3f248ea8f..daaa6343d 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -69,6 +69,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string EnableImageTypes { get; set; } + + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } } [Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")] @@ -117,6 +120,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string EnableImageTypes { get; set; } + + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } } [Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")] @@ -184,6 +190,10 @@ namespace MediaBrowser.Api [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string EnableImageTypes { get; set; } + + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + } [Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")] @@ -226,6 +236,10 @@ namespace MediaBrowser.Api [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string EnableImageTypes { get; set; } + + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + } /// <summary> @@ -409,23 +423,14 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("No series exists with Id " + request.Id); } - var seasons = series.GetSeasons(user); - - if (request.IsSpecialSeason.HasValue) + var seasons = (await series.GetItems(new InternalItemsQuery(user) { - var val = request.IsSpecialSeason.Value; + IsMissing = request.IsMissing, + IsVirtualUnaired = request.IsVirtualUnaired, + IsSpecialSeason = request.IsSpecialSeason, + AdjacentTo = request.AdjacentTo - seasons = seasons.Where(i => i.IsSpecialSeason == val); - } - - seasons = FilterVirtualSeasons(request, seasons); - - // This must be the last filter - if (!string.IsNullOrEmpty(request.AdjacentTo)) - { - seasons = UserViewBuilder.FilterForAdjacency(seasons, request.AdjacentTo) - .Cast<Season>(); - } + }).ConfigureAwait(false)).Items.OfType<Season>(); var dtoOptions = GetDtoOptions(request); @@ -439,23 +444,6 @@ namespace MediaBrowser.Api }; } - private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items) - { - if (request.IsMissing.HasValue) - { - var val = request.IsMissing.Value; - items = items.Where(i => (i.IsMissingSeason) == val); - } - - if (request.IsVirtualUnaired.HasValue) - { - var val = request.IsVirtualUnaired.Value; - items = items.Where(i => i.IsVirtualUnaired == val); - } - - return items; - } - public async Task<object> Get(GetEpisodes request) { var user = _userManager.GetUserById(request.UserId); @@ -490,7 +478,7 @@ namespace MediaBrowser.Api } else { - episodes = series.GetEpisodes(user, season); + episodes = series.GetSeasonEpisodes(user, season); } } else diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 3e9a541c0..96acb1f60 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -226,6 +226,9 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableImages { get; set; } + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? ImageTypeLimit { get; set; } diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 97a81b790..ce7905b42 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -149,39 +149,35 @@ namespace MediaBrowser.Api.UserLibrary item = user == null ? _libraryManager.RootFolder : user.RootFolder; } - // Default list type = children - - var folder = item as Folder; - if (folder == null) - { - folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder(); - } - if (!string.IsNullOrEmpty(request.Ids)) { - request.Recursive = true; var query = GetItemsQuery(request, user); - var result = await folder.GetItems(query).ConfigureAwait(false); - - if (string.IsNullOrWhiteSpace(request.SortBy)) + var specificItems = _libraryManager.GetItemList(query).ToArray(); + if (query.SortBy.Length == 0) { var ids = query.ItemIds.ToList(); // Try to preserve order - result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray(); + specificItems = specificItems.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray(); } - - return result; + return new QueryResult<BaseItem> + { + Items = specificItems.ToArray(), + TotalRecordCount = specificItems.Length + }; } - if (request.Recursive) + // Default list type = children + + var folder = item as Folder; + if (folder == null) { - return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); + folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder(); } - if (user == null) + if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null) { - return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false); + return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); } var userRoot = item as UserRootFolder; diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 3be11bdc5..c392ef463 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -12,6 +12,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using CommonIO; +using MediaBrowser.Controller.Providers; namespace MediaBrowser.Api.UserLibrary { @@ -244,6 +246,9 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string EnableImageTypes { get; set; } + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + public GetLatestMedia() { Limit = 20; @@ -262,14 +267,16 @@ namespace MediaBrowser.Api.UserLibrary private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; private readonly IUserViewManager _userViewManager; + private readonly IFileSystem _fileSystem; - public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager) + public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager, IFileSystem fileSystem) { _userManager = userManager; _libraryManager = libraryManager; _userDataRepository = userDataRepository; _dtoService = dtoService; _userViewManager = userViewManager; + _fileSystem = fileSystem; } /// <summary> @@ -426,12 +433,14 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public object Get(GetItem request) + public async Task<object> Get(GetItem request) { var user = _userManager.GetUserById(request.UserId); var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id); + await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false); + var dtoOptions = GetDtoOptions(request); var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); @@ -439,6 +448,27 @@ namespace MediaBrowser.Api.UserLibrary return ToOptimizedSerializedResultUsingCache(result); } + private async Task RefreshItemOnDemandIfNeeded(BaseItem item) + { + if (item is Person) + { + var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary); + var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3; + + if (!hasMetdata) + { + var options = new MetadataRefreshOptions(_fileSystem) + { + MetadataRefreshMode = MetadataRefreshMode.FullRefresh, + ImageRefreshMode = ImageRefreshMode.FullRefresh, + ForceSave = performFullRefresh + }; + + await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false); + } + } + } + /// <summary> /// Gets the specified request. /// </summary> diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index d627cc67a..e69b64948 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -19,12 +19,16 @@ namespace MediaBrowser.Controller.Dto public bool EnableImages { get; set; } public bool AddProgramRecordingInfo { get; set; } public string DeviceId { get; set; } + public bool EnableUserData { get; set; } + public bool AddCurrentProgram { get; set; } public DtoOptions() { Fields = new List<ItemFields>(); ImageTypeLimit = int.MaxValue; EnableImages = true; + EnableUserData = true; + AddCurrentProgram = true; Fields = Enum.GetNames(typeof (ItemFields)) .Select(i => (ItemFields) Enum.Parse(typeof (ItemFields), i, true)) diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 4aa99ae87..efc450248 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -5,6 +5,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; using CommonIO; using MediaBrowser.Controller.Providers; @@ -84,7 +86,7 @@ namespace MediaBrowser.Controller.Entities } } - private void ResetCachedChildren() + private void ClearCache() { lock (_childIdsLock) { @@ -114,7 +116,7 @@ namespace MediaBrowser.Controller.Entities public override bool BeforeMetadataRefresh() { - ResetCachedChildren(); + ClearCache(); var changed = base.BeforeMetadataRefresh() || _requiresRefresh; _requiresRefresh = false; @@ -123,7 +125,7 @@ namespace MediaBrowser.Controller.Entities private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations) { - ResetCachedChildren(); + ClearCache(); var path = ContainingFolderPath; @@ -165,6 +167,21 @@ namespace MediaBrowser.Controller.Entities return args; } + protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) + { + return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren); + } + + protected override async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) + { + ClearCache(); + + await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService) + .ConfigureAwait(false); + + ClearCache(); + } + /// <summary> /// Adds the virtual child. /// </summary> @@ -181,15 +198,6 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Get the children of this folder from the actual file system - /// </summary> - /// <returns>IEnumerable{BaseItem}.</returns> - protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) - { - return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren); - } - - /// <summary> /// Finds the virtual child. /// </summary> /// <param name="id">The id.</param> diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 56f9a5b88..81d1deaa2 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -270,5 +270,54 @@ namespace MediaBrowser.Controller.Entities.Audio return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.ArtistsPath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + if (IsAccessedByName) + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + if (IsAccessedByName) + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 9aa5625fa..bd991d9f4 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -92,5 +92,48 @@ namespace MediaBrowser.Controller.Entities.Audio return LibraryManager.GetItemList(query); } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.MusicGenrePath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index cc3646cdc..cbbb9a89a 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2213,6 +2213,15 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public virtual bool StopRefreshIfLocalMetadataFound + { + get + { + return true; + } + } + public virtual IEnumerable<Guid> GetIdsForAncestorQuery() { return new[] { Id }; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 23055690c..b5c76c0eb 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -13,6 +13,7 @@ using System.Threading; using System.Threading.Tasks; using CommonIO; using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Channels; @@ -701,7 +702,7 @@ namespace MediaBrowser.Controller.Entities items = GetRecursiveChildren(user, query); } - return PostFilterAndSort(items, query); + return PostFilterAndSort(items, query, true, true); } if (!(this is UserRootFolder) && !(this is AggregateFolder)) @@ -902,7 +903,15 @@ namespace MediaBrowser.Controller.Entities if (query.ItemIds.Length > 0) { var specificItems = query.ItemIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList(); - return Task.FromResult(PostFilterAndSort(specificItems, query)); + + if (query.SortBy.Length == 0) + { + var ids = query.ItemIds.ToList(); + + // Try to preserve order + specificItems = specificItems.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToList(); + } + return Task.FromResult(PostFilterAndSort(specificItems, query, true, true)); } return GetItemsInternal(query); @@ -958,12 +967,12 @@ namespace MediaBrowser.Controller.Entities : GetChildren(user, true).Where(filter); } - return PostFilterAndSort(items, query); + return PostFilterAndSort(items, query, true, true); } - protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query) + protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool collapseBoxSetItems, bool enableSorting) { - return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager); + return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager, collapseBoxSetItems, enableSorting); } public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren) @@ -1427,7 +1436,7 @@ namespace MediaBrowser.Controller.Entities itemDto.RecursiveItemCount = allItemsQueryResult.TotalRecordCount; } - double recursiveItemCount = allItemsQueryResult.TotalRecordCount; + var recursiveItemCount = allItemsQueryResult.TotalRecordCount; double unplayedCount = unplayedQueryResult.TotalRecordCount; if (recursiveItemCount > 0) @@ -1437,6 +1446,14 @@ namespace MediaBrowser.Controller.Entities dto.Played = dto.PlayedPercentage.Value >= 100; dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount; } + + if (itemDto != null) + { + if (this is Season || this is MusicAlbum) + { + itemDto.ChildCount = recursiveItemCount; + } + } } } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index 5d66bf3ab..6448828fb 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -84,5 +84,48 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GameGenrePath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index c7fe25a96..1736ba8c7 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -87,5 +87,48 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GenrePath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index d5891c655..26f425ff0 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -58,6 +58,6 @@ namespace MediaBrowser.Controller.Entities string GetPresentationUniqueKey(); string CreatePresentationUniqueKey(); - + bool StopRefreshIfLocalMetadataFound { get; } } } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 69cab5ec5..deea63112 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -37,6 +37,7 @@ namespace MediaBrowser.Controller.Entities public string[] Genres { get; set; } public string[] Keywords { get; set; } + public bool? IsSpecialSeason { get; set; } public bool? IsMissing { get; set; } public bool? IsUnaired { get; set; } public bool? IsVirtualUnaired { get; set; } @@ -50,6 +51,7 @@ namespace MediaBrowser.Controller.Entities public string PresentationUniqueKey { get; set; } public string Path { get; set; } + public string PathNotStartsWith { get; set; } public string Name { get; set; } public string SlugName { get; set; } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index c7a833c58..f0270497c 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -179,5 +179,15 @@ namespace MediaBrowser.Controller.Entities.Movies return list; } + + [IgnoreDataMember] + public override bool StopRefreshIfLocalMetadataFound + { + get + { + // Need people id's from internet metadata + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 4ee140b2b..f21bc0a71 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -122,6 +122,64 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validFilename = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + string subFolderPrefix = null; + + foreach (char c in validFilename) + { + if (char.IsLetterOrDigit(c)) + { + subFolderPrefix = c.ToString(); + break; + } + } + + var path = ConfigurationManager.ApplicationPaths.PeoplePath; + + return string.IsNullOrEmpty(subFolderPrefix) ? + System.IO.Path.Combine(path, validFilename) : + System.IO.Path.Combine(path, subFolderPrefix, validFilename); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 7e3d6fe8e..04b09b744 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -85,5 +85,48 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.StudioPath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index c64de399f..842b2fd60 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -85,7 +85,11 @@ namespace MediaBrowser.Controller.Entities.TV public override int GetChildCount(User user) { - return GetChildren(user, true).Count(); + Logger.Debug("Season {0} getting child cound", (Path ?? Name)); + var result = GetChildren(user, true).Count(); + Logger.Debug("Season {0} child cound: ", result); + + return result; } /// <summary> @@ -137,24 +141,6 @@ namespace MediaBrowser.Controller.Entities.TV return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name; } - [IgnoreDataMember] - public bool IsMissingSeason - { - get { return (IsVirtualItem) && !IsUnaired; } - } - - [IgnoreDataMember] - public bool IsVirtualUnaired - { - get { return (IsVirtualItem) && IsUnaired; } - } - - [IgnoreDataMember] - public bool IsSpecialSeason - { - get { return (IndexNumber ?? -1) == 0; } - } - protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query) { if (query.User == null) @@ -166,10 +152,15 @@ namespace MediaBrowser.Controller.Entities.TV Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); + var id = Guid.NewGuid().ToString("N"); + + Logger.Debug("Season.GetItemsInternal entering GetEpisodes. Request id: " + id); var items = GetEpisodes(user).Where(filter); - var result = PostFilterAndSort(items, query); + Logger.Debug("Season.GetItemsInternal entering PostFilterAndSort. Request id: " + id); + var result = PostFilterAndSort(items, query, false, false); + Logger.Debug("Season.GetItemsInternal complete. Request id: " + id); return Task.FromResult(result); } @@ -180,19 +171,17 @@ namespace MediaBrowser.Controller.Entities.TV /// <returns>IEnumerable{Episode}.</returns> public IEnumerable<Episode> GetEpisodes(User user) { - var config = user.Configuration; - - return GetEpisodes(Series, user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); + return GetEpisodes(Series, user); } - public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) + public IEnumerable<Episode> GetEpisodes(Series series, User user) { - return GetEpisodes(series, user, includeMissingEpisodes, includeVirtualUnairedEpisodes, null); + return GetEpisodes(series, user, null); } - public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes) + public IEnumerable<Episode> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes) { - return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes, allSeriesEpisodes); + return series.GetSeasonEpisodes(user, this, allSeriesEpisodes); } public IEnumerable<Episode> GetEpisodes() diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index f01eddceb..4915cfedc 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -207,7 +207,30 @@ namespace MediaBrowser.Controller.Entities.TV { var config = user.Configuration; - return GetSeasons(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); + var seriesKey = GetUniqueSeriesKey(this); + + Logger.Debug("GetSeasons SeriesKey: {0}", seriesKey); + var query = new InternalItemsQuery(user) + { + AncestorWithPresentationUniqueKey = seriesKey, + IncludeItemTypes = new[] {typeof (Season).Name}, + SortBy = new[] {ItemSortBy.SortName} + }; + + if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes) + { + query.IsVirtualItem = false; + } + else if (!config.DisplayMissingEpisodes) + { + query.IsMissing = false; + } + else if (!config.DisplayUnairedEpisodes) + { + query.IsVirtualUnaired = false; + } + + return LibraryManager.GetItemList(query).Cast<Season>(); } protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query) @@ -237,55 +260,43 @@ namespace MediaBrowser.Controller.Entities.TV Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); var items = GetSeasons(user).Where(filter); - var result = PostFilterAndSort(items, query); + var result = PostFilterAndSort(items, query, false, true); return Task.FromResult(result); } - public IEnumerable<Season> GetSeasons(User user, bool includeMissingSeasons, bool includeVirtualUnaired) + public IEnumerable<Episode> GetEpisodes(User user) { - IEnumerable<Season> seasons; + var seriesKey = GetUniqueSeriesKey(this); + Logger.Debug("GetEpisodes seriesKey: {0}", seriesKey); - seasons = LibraryManager.GetItemList(new InternalItemsQuery(user) + var query = new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), - IncludeItemTypes = new[] { typeof(Season).Name }, - SortBy = new[] { ItemSortBy.SortName } - - }).Cast<Season>(); - - if (!includeMissingSeasons) + AncestorWithPresentationUniqueKey = seriesKey, + IncludeItemTypes = new[] {typeof (Episode).Name, typeof (Season).Name}, + SortBy = new[] {ItemSortBy.SortName} + }; + var config = user.Configuration; + if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes) { - seasons = seasons.Where(i => !(i.IsMissingSeason)); + query.IsVirtualItem = false; } - if (!includeVirtualUnaired) + else if (!config.DisplayMissingEpisodes) { - seasons = seasons.Where(i => !i.IsVirtualUnaired); + query.IsMissing = false; } - - return seasons; - } - - public IEnumerable<Episode> GetEpisodes(User user) - { - var config = user.Configuration; - - return GetEpisodes(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); - } - - public IEnumerable<Episode> GetEpisodes(User user, bool includeMissing, bool includeVirtualUnaired) - { - var allItems = LibraryManager.GetItemList(new InternalItemsQuery(user) + else if (!config.DisplayUnairedEpisodes) { - AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), - IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name }, - SortBy = new[] { ItemSortBy.SortName } + query.IsVirtualUnaired = false; + } + + var allItems = LibraryManager.GetItemList(query).ToList(); - }).ToList(); + Logger.Debug("GetEpisodes return {0} items from database", allItems.Count); var allSeriesEpisodes = allItems.OfType<Episode>().ToList(); var allEpisodes = allItems.OfType<Season>() - .SelectMany(i => i.GetEpisodes(this, user, includeMissing, includeVirtualUnaired, allSeriesEpisodes)) + .SelectMany(i => i.GetEpisodes(this, user, allSeriesEpisodes)) .Reverse() .ToList(); @@ -362,78 +373,68 @@ namespace MediaBrowser.Controller.Entities.TV progress.Report(100); } - public IEnumerable<Episode> GetEpisodes(User user, Season season) - { - var config = user.Configuration; - - return GetEpisodes(user, season, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); - } - private IEnumerable<Episode> GetAllEpisodes(User user) { - return LibraryManager.GetItemList(new InternalItemsQuery(user) + Logger.Debug("Series.GetAllEpisodes entering GetItemList"); + + var result = LibraryManager.GetItemList(new InternalItemsQuery(user) { AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), IncludeItemTypes = new[] { typeof(Episode).Name }, SortBy = new[] { ItemSortBy.SortName } - }).Cast<Episode>(); - } + }).Cast<Episode>().ToList(); - public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) - { - IEnumerable<Episode> episodes = GetAllEpisodes(user); + Logger.Debug("Series.GetAllEpisodes returning {0} episodes", result.Count); - return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes); + return result; } - public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes) + public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason) { - if (allSeriesEpisodes == null) + var seriesKey = GetUniqueSeriesKey(this); + Logger.Debug("GetSeasonEpisodes seriesKey: {0}", seriesKey); + + var query = new InternalItemsQuery(user) + { + AncestorWithPresentationUniqueKey = seriesKey, + IncludeItemTypes = new[] { typeof(Episode).Name }, + SortBy = new[] { ItemSortBy.SortName } + }; + var config = user.Configuration; + if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes) { - return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes); + query.IsVirtualItem = false; } - - var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons); - - if (!includeMissingEpisodes) + else if (!config.DisplayMissingEpisodes) { - episodes = episodes.Where(i => !i.IsMissingEpisode); + query.IsMissing = false; } - if (!includeVirtualUnairedEpisodes) + else if (!config.DisplayUnairedEpisodes) { - episodes = episodes.Where(i => !i.IsVirtualUnaired); + query.IsVirtualUnaired = false; } - var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder; + var allItems = LibraryManager.GetItemList(query).OfType<Episode>(); - return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending) - .Cast<Episode>(); + return GetSeasonEpisodes(user, parentSeason, allItems); } - /// <summary> - /// Filters the episodes by season. - /// </summary> - public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials) + public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason, IEnumerable<Episode> allSeriesEpisodes) { - if (!includeSpecials || seasonNumber < 1) + if (allSeriesEpisodes == null) { - return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber); + Logger.Debug("GetSeasonEpisodes allSeriesEpisodes is null"); + return GetSeasonEpisodes(user, parentSeason); } - return episodes.Where(i => - { - var episode = i; - - if (episode != null) - { - var currentSeasonNumber = episode.AiredSeasonNumber; + Logger.Debug("GetSeasonEpisodes FilterEpisodesBySeason"); + var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons); - return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber; - } + var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder; - return false; - }); + return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending) + .Cast<Episode>(); } /// <summary> @@ -464,6 +465,32 @@ namespace MediaBrowser.Controller.Entities.TV }); } + /// <summary> + /// Filters the episodes by season. + /// </summary> + public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials) + { + if (!includeSpecials || seasonNumber < 1) + { + return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber); + } + + return episodes.Where(i => + { + var episode = i; + + if (episode != null) + { + var currentSeasonNumber = episode.AiredSeasonNumber; + + return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber; + } + + return false; + }); + } + + protected override bool GetBlockUnratedValue(UserPolicy config) { return config.BlockUnratedItems.Contains(UnratedItem.Series); @@ -519,5 +546,15 @@ namespace MediaBrowser.Controller.Entities.TV return list; } + + [IgnoreDataMember] + public override bool StopRefreshIfLocalMetadataFound + { + get + { + // Need people id's from internet metadata + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 6604be977..306ce35ec 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -124,5 +124,15 @@ namespace MediaBrowser.Controller.Entities return list; } + + [IgnoreDataMember] + public override bool StopRefreshIfLocalMetadataFound + { + get + { + // Need people id's from internet metadata + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index d043cba47..aff1f5e28 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.Entities } } - private void ResetCachedChildren() + private void ClearCache() { lock (_childIdsLock) { @@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Entities var user = query.User; Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); - return PostFilterAndSort(result.Where(filter), query); + return PostFilterAndSort(result.Where(filter), query, true, true); } public override int GetChildCount(User user) @@ -94,7 +94,7 @@ namespace MediaBrowser.Controller.Entities public override bool BeforeMetadataRefresh() { - ResetCachedChildren(); + ClearCache(); var hasChanges = base.BeforeMetadataRefresh(); @@ -107,13 +107,22 @@ namespace MediaBrowser.Controller.Entities return hasChanges; } + protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) + { + ClearCache(); + + return base.GetNonCachedChildren(directoryService); + } + protected override async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { - ResetCachedChildren(); + ClearCache(); await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService) .ConfigureAwait(false); + ClearCache(); + // Not the best way to handle this, but it solves an issue // CollectionFolders aren't always getting saved after changes // This means that grabbing the item by Id may end up returning the old one diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index d0f7efa8c..9f3acc3fc 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -424,7 +424,7 @@ namespace MediaBrowser.Controller.Entities query.SortBy = new string[] { }; - return PostFilterAndSort(items, parent, null, query); + return PostFilterAndSort(items, parent, null, query, false, true); } private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query) @@ -780,7 +780,7 @@ namespace MediaBrowser.Controller.Entities { items = items.Where(i => Filter(i, query.User, query, _userDataManager, _libraryManager)); - return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config); + return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config, true, true); } public static bool FilterItem(BaseItem item, InternalItemsQuery query) @@ -791,9 +791,11 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, BaseItem queryParent, int? totalRecordLimit, - InternalItemsQuery query) + InternalItemsQuery query, + bool collapseBoxSetItems, + bool enableSorting) { - return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config); + return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config, collapseBoxSetItems, enableSorting); } public static QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, @@ -801,7 +803,9 @@ namespace MediaBrowser.Controller.Entities int? totalRecordLimit, InternalItemsQuery query, ILibraryManager libraryManager, - IServerConfigurationManager configurationManager) + IServerConfigurationManager configurationManager, + bool collapseBoxSetItems, + bool enableSorting) { var user = query.User; @@ -810,7 +814,10 @@ namespace MediaBrowser.Controller.Entities query.IsVirtualUnaired, query.IsUnaired); - items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager); + if (collapseBoxSetItems) + { + items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager); + } // This must be the last filter if (!string.IsNullOrEmpty(query.AdjacentTo)) @@ -818,7 +825,7 @@ namespace MediaBrowser.Controller.Entities items = FilterForAdjacency(items, query.AdjacentTo); } - return Sort(items, totalRecordLimit, query, libraryManager); + return SortAndPage(items, totalRecordLimit, query, libraryManager, enableSorting); } public static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items, @@ -1093,8 +1100,6 @@ namespace MediaBrowser.Controller.Entities bool? isVirtualUnaired, bool? isUnaired) { - items = FilterVirtualSeasons(items, isMissing, isVirtualUnaired, isUnaired); - if (isMissing.HasValue) { var val = isMissing.Value; @@ -1140,61 +1145,10 @@ namespace MediaBrowser.Controller.Entities return items; } - private static IEnumerable<BaseItem> FilterVirtualSeasons( - IEnumerable<BaseItem> items, - bool? isMissing, - bool? isVirtualUnaired, - bool? isUnaired) - { - if (isMissing.HasValue) - { - var val = isMissing.Value; - items = items.Where(i => - { - var e = i as Season; - if (e != null) - { - return (e.IsMissingSeason) == val; - } - return true; - }); - } - - if (isUnaired.HasValue) - { - var val = isUnaired.Value; - items = items.Where(i => - { - var e = i as Season; - if (e != null) - { - return e.IsUnaired == val; - } - return true; - }); - } - - if (isVirtualUnaired.HasValue) - { - var val = isVirtualUnaired.Value; - items = items.Where(i => - { - var e = i as Season; - if (e != null) - { - return e.IsVirtualUnaired == val; - } - return true; - }); - } - - return items; - } - - public static QueryResult<BaseItem> Sort(IEnumerable<BaseItem> items, + public static QueryResult<BaseItem> SortAndPage(IEnumerable<BaseItem> items, int? totalRecordLimit, InternalItemsQuery query, - ILibraryManager libraryManager) + ILibraryManager libraryManager, bool enableSorting) { var user = query.User; diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index db896f1fc..4197ea93e 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -112,5 +112,48 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.YearPath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs index c07934d0b..c89a60a6f 100644 --- a/MediaBrowser.Controller/IServerApplicationPaths.cs +++ b/MediaBrowser.Controller/IServerApplicationPaths.cs @@ -106,5 +106,7 @@ namespace MediaBrowser.Controller /// </summary> /// <value>The internal metadata path.</value> string InternalMetadataPath { get; } + + string ArtistsPath { get; } } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 0462117cb..e7eaa1dc0 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -236,6 +236,7 @@ <Compile Include="Net\IAuthorizationContext.cs" /> <Compile Include="Net\IAuthService.cs" /> <Compile Include="Net\IHasAuthorization.cs" /> + <Compile Include="Net\IAsyncStreamSource.cs" /> <Compile Include="Net\IHasResultFactory.cs" /> <Compile Include="Net\IHasSession.cs" /> <Compile Include="Net\IHttpResultFactory.cs" /> diff --git a/MediaBrowser.Controller/Net/IAsyncStreamSource.cs b/MediaBrowser.Controller/Net/IAsyncStreamSource.cs new file mode 100644 index 000000000..0f41f60a5 --- /dev/null +++ b/MediaBrowser.Controller/Net/IAsyncStreamSource.cs @@ -0,0 +1,18 @@ +using ServiceStack.Web; +using System.IO; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Net +{ + /// <summary> + /// Interface IAsyncStreamSource + /// Enables asynchronous writing to http resonse streams + /// </summary> + public interface IAsyncStreamSource + { + /// <summary> + /// Asynchronously write to the response stream. + /// </summary> + Task WriteToAsync(Stream responseStream); + } +} diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs index 49d4614d8..8fdb1ce37 100644 --- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Net /// <returns>System.Object.</returns> object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null); - object GetAsyncStreamWriter(Func<Stream,Task> streamWriter, IDictionary<string, string> responseHeaders = null); + object GetAsyncStreamWriter(IAsyncStreamSource streamSource); /// <summary> /// Gets the optimized result. diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 437f0e157..edfec902b 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -170,6 +170,10 @@ namespace MediaBrowser.Controller.Persistence QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query); + + List<string> GetStudioNames(); + + List<string> GetAllArtistNames(); } } diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 1ec7a4ce0..6ad5899da 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net; using System.Security; using System.Threading; using System.Threading.Tasks; @@ -91,6 +92,7 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IServerConfigurationManager _config; public DateTime DateLastActivity { get; private set; } + public Action OnDeviceUnavailable { get; set; } public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config) { @@ -134,6 +136,9 @@ namespace MediaBrowser.Dlna.PlayTo private async void RefreshVolume() { + if (_disposed) + return; + try { await GetVolume().ConfigureAwait(false); @@ -149,6 +154,9 @@ namespace MediaBrowser.Dlna.PlayTo private bool _timerActive; private void RestartTimer() { + if (_disposed) + return; + if (!_timerActive) { lock (_timerLock) @@ -169,6 +177,9 @@ namespace MediaBrowser.Dlna.PlayTo /// </summary> private void RestartTimerInactive() { + if (_disposed) + return; + if (_timerActive) { lock (_timerLock) @@ -398,6 +409,7 @@ namespace MediaBrowser.Dlna.PlayTo #region Get data private int _successiveStopCount; + private int _connectFailureCount; private async void TimerCallback(object sender) { if (_disposed) @@ -435,6 +447,8 @@ namespace MediaBrowser.Dlna.PlayTo } } + _connectFailureCount = 0; + if (_disposed) return; @@ -455,8 +469,33 @@ namespace MediaBrowser.Dlna.PlayTo } } } + catch (WebException ex) + { + if (_disposed) + return; + + _logger.ErrorException("Error updating device info for {0}", ex, Properties.Name); + + _successiveStopCount++; + _connectFailureCount++; + + if (_successiveStopCount >= maxSuccessiveStopReturns) + { + RestartTimerInactive(); + } + if (_connectFailureCount >= maxSuccessiveStopReturns) + { + if (OnDeviceUnavailable != null) + { + OnDeviceUnavailable(); + } + } + } catch (Exception ex) { + if (_disposed) + return; + _logger.ErrorException("Error updating device info for {0}", ex, Properties.Name); _successiveStopCount++; diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 873ae5ae4..7429330bd 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -103,11 +103,25 @@ namespace MediaBrowser.Dlna.PlayTo _device.PlaybackProgress += _device_PlaybackProgress; _device.PlaybackStopped += _device_PlaybackStopped; _device.MediaChanged += _device_MediaChanged; + _device.OnDeviceUnavailable = OnDeviceUnavailable; + _device.Start(); _deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft; } + private void OnDeviceUnavailable() + { + try + { + _sessionManager.ReportSessionEnded(_session.Id); + } + catch + { + // Could throw if the session is already gone + } + } + void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e) { string nts; @@ -125,14 +139,7 @@ namespace MediaBrowser.Dlna.PlayTo if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 || nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1) { - try - { - _sessionManager.ReportSessionEnded(_session.Id); - } - catch - { - // Could throw if the session is already gone - } + OnDeviceUnavailable(); } } } @@ -647,6 +654,7 @@ namespace MediaBrowser.Dlna.PlayTo _device.PlaybackStopped -= _device_PlaybackStopped; _device.MediaChanged -= _device_MediaChanged; _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; + _device.OnDeviceUnavailable = null; _device.Dispose(); } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index c8a28e832..e90f6bdc3 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -343,14 +343,16 @@ namespace MediaBrowser.MediaEncoding.Encoder // If that doesn't pan out, then do a recursive search var files = Directory.GetFiles(path); - var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase)); - var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); + var excludeExtensions = new[] { ".c" }; + + var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); + var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath)) { files = Directory.GetFiles(path, "*", SearchOption.AllDirectories); - ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase)); + ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); if (!string.IsNullOrWhiteSpace(ffmpegPath)) { diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 9e9bc0780..700af682b 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text; using System.Xml; using CommonIO; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; @@ -793,7 +794,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrWhiteSpace(artists)) { audio.Artists = SplitArtists(artists, new[] { '/', ';' }, false) - .Distinct(StringComparer.OrdinalIgnoreCase) + .DistinctNames() .ToList(); } else @@ -806,7 +807,7 @@ namespace MediaBrowser.MediaEncoding.Probing else { audio.Artists = SplitArtists(artist, _nameDelimiters, true) - .Distinct(StringComparer.OrdinalIgnoreCase) + .DistinctNames() .ToList(); } } @@ -828,7 +829,7 @@ namespace MediaBrowser.MediaEncoding.Probing else { audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true) - .Distinct(StringComparer.OrdinalIgnoreCase) + .DistinctNames() .ToList(); } diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index c6f7bbb9d..e15df37c1 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -3,5 +3,11 @@ public class LibraryOptions { public bool EnableArchiveMediaFiles { get; set; } + public bool EnablePhotos { get; set; } + + public LibraryOptions() + { + EnablePhotos = true; + } } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 80e81a41a..0710417c8 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -570,7 +570,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue); } - int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(), playlistItem.TargetAudioChannels, playlistItem.TargetAudioCodec, audioStream); + int audioBitrate = GetAudioBitrate(playlistItem.SubProtocol, options.GetMaxBitrate(), playlistItem.TargetAudioChannels, playlistItem.TargetAudioCodec, audioStream); playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate); int? maxBitrateSetting = options.GetMaxBitrate(); @@ -593,7 +593,7 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } - private int GetAudioBitrate(int? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream) + private int GetAudioBitrate(string subProtocol, int? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream) { var defaultBitrate = 128000; if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3")) @@ -611,7 +611,14 @@ namespace MediaBrowser.Model.Dlna { if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3")) { - defaultBitrate = Math.Max(448000, defaultBitrate); + if (string.Equals(subProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + defaultBitrate = Math.Max(384000, defaultBitrate); + } + else + { + defaultBitrate = Math.Max(448000, defaultBitrate); + } } else { diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 7d69359d1..348a781ae 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -346,7 +346,16 @@ namespace MediaBrowser.Model.Dto /// Gets or sets a value indicating whether this instance is folder. /// </summary> /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value> - public bool IsFolder { get; set; } + public bool? IsFolder { get; set; } + + [IgnoreDataMember] + public bool IsFolderItem + { + get + { + return IsFolder ?? false; + } + } /// <summary> /// Gets or sets the parent id. @@ -656,7 +665,7 @@ namespace MediaBrowser.Model.Dto { get { - return RunTimeTicks.HasValue || IsFolder || IsGenre || IsMusicGenre || IsArtist; + return RunTimeTicks.HasValue || IsFolderItem || IsGenre || IsMusicGenre || IsArtist; } } diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs index 3a6ad0444..0ece1e4a0 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs @@ -59,5 +59,11 @@ namespace MediaBrowser.Model.LiveTv /// </summary> /// <value><c>true</c> if [add current program]; otherwise, <c>false</c>.</value> public bool AddCurrentProgram { get; set; } + public bool EnableUserData { get; set; } + + public LiveTvChannelQuery() + { + EnableUserData = true; + } } } diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs index 0141191c1..bf459237a 100644 --- a/MediaBrowser.Model/LiveTv/ProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs @@ -15,9 +15,11 @@ namespace MediaBrowser.Model.LiveTv SortBy = new string[] { }; Genres = new string[] { }; EnableTotalRecordCount = true; + EnableUserData = true; } public bool EnableTotalRecordCount { get; set; } + public bool EnableUserData { get; set; } /// <summary> /// Fields to return within the items, in addition to basic information diff --git a/MediaBrowser.Model/Sync/SyncJobQuery.cs b/MediaBrowser.Model/Sync/SyncJobQuery.cs index e86ec929f..bb99b5d5f 100644 --- a/MediaBrowser.Model/Sync/SyncJobQuery.cs +++ b/MediaBrowser.Model/Sync/SyncJobQuery.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.Model.Sync /// </summary> /// <value>The user identifier.</value> public string UserId { get; set; } + public string ExcludeTargetIds { get; set; } /// <summary> /// Gets or sets the status. /// </summary> diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 0483a74ed..2a69948b1 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -42,6 +42,13 @@ namespace MediaBrowser.Providers.Manager var config = ProviderManager.GetMetadataOptions(item); var updateType = ItemUpdateType.None; + var requiresRefresh = false; + + if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) + { + // TODO: If this returns true, should we instead just change metadata refresh mode to Full? + requiresRefresh = item.RequiresRefresh(); + } var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem); var localImagesFailed = false; @@ -70,14 +77,10 @@ namespace MediaBrowser.Providers.Manager bool hasRefreshedMetadata = true; bool hasRefreshedImages = true; - var requiresRefresh = false; // Next run metadata providers if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) { - // TODO: If this returns true, should we instead just change metadata refresh mode to Full? - requiresRefresh = item.RequiresRefresh(); - var providers = GetProviders(item, refreshOptions, requiresRefresh) .ToList(); @@ -532,7 +535,7 @@ namespace MediaBrowser.Providers.Manager } // Local metadata is king - if any is found don't run remote providers - if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh)) + if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || !item.StopRefreshIfLocalMetadataFound)) { var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken) .ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 1710ec2b0..a0ce80610 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -81,46 +81,24 @@ namespace MediaBrowser.Providers.Music private IEnumerable<RemoteSearchResult> GetResultsFromResponse(XmlDocument doc) { - var ns = new XmlNamespaceManager(doc.NameTable); - ns.AddNamespace("mb", MusicBrainzBaseUrl + "/ns/mmd-2.0#"); - - var list = new List<RemoteSearchResult>(); - - var nodes = doc.SelectNodes("//mb:release-list/mb:release", ns); - - if (nodes != null) + return ReleaseResult.Parse(doc).Select(i => { - foreach (var node in nodes.Cast<XmlNode>()) + var result = new RemoteSearchResult { - if (node.Attributes != null) - { - string name = null; - - string mbzId = node.Attributes["id"].Value; - - var nameNode = node.SelectSingleNode("//mb:title", ns); - - if (nameNode != null) - { - name = nameNode.InnerText; - } - - if (!string.IsNullOrWhiteSpace(mbzId) && !string.IsNullOrWhiteSpace(name)) - { - var result = new RemoteSearchResult - { - Name = name - }; - - result.SetProviderId(MetadataProviders.MusicBrainzAlbum, mbzId); + Name = i.Title + }; - list.Add(result); - } - } + if (!string.IsNullOrWhiteSpace(i.ReleaseId)) + { + result.SetProviderId(MetadataProviders.MusicBrainzAlbum, i.ReleaseId); + } + if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) + { + result.SetProviderId(MetadataProviders.MusicBrainzAlbum, i.ReleaseGroupId); } - } - return list; + return result; + }); } public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo id, CancellationToken cancellationToken) @@ -208,7 +186,7 @@ namespace MediaBrowser.Providers.Music var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false); - return ReleaseResult.Parse(doc); + return ReleaseResult.Parse(doc, 1).FirstOrDefault(); } private async Task<ReleaseResult> GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken) @@ -219,32 +197,32 @@ namespace MediaBrowser.Providers.Music var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false); - return ReleaseResult.Parse(doc); + return ReleaseResult.Parse(doc, 1).FirstOrDefault(); } private class ReleaseResult { public string ReleaseId; public string ReleaseGroupId; + public string Title; - public static ReleaseResult Parse(XmlDocument doc) + public static List<ReleaseResult> Parse(XmlDocument doc, int? limit = null) { var docElem = doc.DocumentElement; + var list = new List<ReleaseResult>(); if (docElem == null) { - return new ReleaseResult(); + return list; } var releaseList = docElem.FirstChild; if (releaseList == null) { - return new ReleaseResult(); + return list; } var nodes = releaseList.ChildNodes; - string releaseId = null; - string releaseGroupId = null; if (nodes != null) { @@ -252,18 +230,42 @@ namespace MediaBrowser.Providers.Music { if (string.Equals(node.Name, "release", StringComparison.OrdinalIgnoreCase)) { - releaseId = node.Attributes["id"].Value; - releaseGroupId = GetReleaseGroupIdFromReleaseNode(node); - break; + var releaseId = node.Attributes["id"].Value; + var releaseGroupId = GetReleaseGroupIdFromReleaseNode(node); + + list.Add(new ReleaseResult + { + ReleaseId = releaseId, + ReleaseGroupId = releaseGroupId, + Title = GetTitleFromReleaseNode(node) + }); + + if (limit.HasValue && list.Count >= limit.Value) + { + break; + } } } } - return new ReleaseResult + return list; + } + + private static string GetTitleFromReleaseNode(XmlNode node) + { + var subNodes = node.ChildNodes; + if (subNodes != null) { - ReleaseId = releaseId, - ReleaseGroupId = releaseGroupId - }; + foreach (var subNode in subNodes.Cast<XmlNode>()) + { + if (string.Equals(subNode.Name, "title", StringComparison.OrdinalIgnoreCase)) + { + return subNode.InnerText; + } + } + } + + return null; } private static string GetReleaseGroupIdFromReleaseNode(XmlNode node) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 4f903a2c2..be68162ca 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -329,7 +329,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (user != null) { - await AttachUserSpecificInfo(dto, item, user, fields).ConfigureAwait(false); + await AttachUserSpecificInfo(dto, item, user, options).ConfigureAwait(false); } var hasMediaSources = item as IHasMediaSources; @@ -446,19 +446,20 @@ namespace MediaBrowser.Server.Implementations.Dto /// <summary> /// Attaches the user specific info. /// </summary> - /// <param name="dto">The dto.</param> - /// <param name="item">The item.</param> - /// <param name="user">The user.</param> - /// <param name="fields">The fields.</param> - private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields) + private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions dtoOptions) { + var fields = dtoOptions.Fields; + if (item.IsFolder) { var folder = (Folder)item; - dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user).ConfigureAwait(false); + if (dtoOptions.EnableUserData) + { + dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user).ConfigureAwait(false); + } - if (item.SourceType == SourceType.Library) + if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library) { dto.ChildCount = GetChildCount(folder, user); } @@ -476,7 +477,10 @@ namespace MediaBrowser.Server.Implementations.Dto else { - dto.UserData = _userDataRepository.GetUserDataDto(item, user).Result; + if (dtoOptions.EnableUserData) + { + dto.UserData = _userDataRepository.GetUserDataDto(item, user).Result; + } } dto.PlayAccess = item.GetPlayAccess(user); @@ -484,7 +488,10 @@ namespace MediaBrowser.Server.Implementations.Dto if (fields.Contains(ItemFields.BasicSyncInfo) || fields.Contains(ItemFields.SyncInfo)) { var userCanSync = user != null && user.Policy.EnableSync; - dto.SupportsSync = userCanSync && _syncManager.SupportsSync(item); + if (userCanSync && _syncManager.SupportsSync(item)) + { + dto.SupportsSync = true; + } } if (fields.Contains(ItemFields.SeasonUserData)) @@ -948,20 +955,23 @@ namespace MediaBrowser.Server.Implementations.Dto dto.Genres = item.Genres; } - dto.ImageTags = new Dictionary<ImageType, string>(); - - // Prevent implicitly captured closure - var currentItem = item; - foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)) - .ToList()) + if (options.EnableImages) { - if (options.GetImageLimit(image.Type) > 0) - { - var tag = GetImageCacheTag(item, image); + dto.ImageTags = new Dictionary<ImageType, string>(); - if (tag != null) + // Prevent implicitly captured closure + var currentItem = item; + foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)) + .ToList()) + { + if (options.GetImageLimit(image.Type) > 0) { - dto.ImageTags[image.Type] = tag; + var tag = GetImageCacheTag(item, image); + + if (tag != null) + { + dto.ImageTags[image.Type] = tag; + } } } } @@ -969,7 +979,16 @@ namespace MediaBrowser.Server.Implementations.Dto dto.Id = GetDtoId(item); dto.IndexNumber = item.IndexNumber; dto.ParentIndexNumber = item.ParentIndexNumber; - dto.IsFolder = item.IsFolder; + + if (item.IsFolder) + { + dto.IsFolder = true; + } + else if (item is IHasMediaSources) + { + dto.IsFolder = false; + } + dto.MediaType = item.MediaType; dto.LocationType = item.LocationType; if (item.IsHD.HasValue && item.IsHD.Value) @@ -1512,97 +1531,6 @@ namespace MediaBrowser.Server.Implementations.Dto } /// <summary> - /// Since it can be slow to make all of these calculations independently, this method will provide a way to do them all at once - /// </summary> - /// <param name="folder">The folder.</param> - /// <param name="user">The user.</param> - /// <param name="dto">The dto.</param> - /// <param name="fields">The fields.</param> - /// <param name="syncProgress">The synchronize progress.</param> - /// <returns>Task.</returns> - private async Task SetSpecialCounts(Folder folder, User user, BaseItemDto dto, List<ItemFields> fields, Dictionary<string, SyncJobItemStatus> syncProgress) - { - var recursiveItemCount = 0; - var unplayed = 0; - - double totalPercentPlayed = 0; - double totalSyncPercent = 0; - - var children = await folder.GetItems(new InternalItemsQuery - { - IsFolder = false, - Recursive = true, - ExcludeLocationTypes = new[] { LocationType.Virtual }, - User = user - - }).ConfigureAwait(false); - - // Loop through each recursive child - foreach (var child in children.Items) - { - var userdata = _userDataRepository.GetUserData(user, child); - - recursiveItemCount++; - - var isUnplayed = true; - - // Incrememt totalPercentPlayed - if (userdata != null) - { - if (userdata.Played) - { - totalPercentPlayed += 100; - - isUnplayed = false; - } - else if (userdata.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0) - { - double itemPercent = userdata.PlaybackPositionTicks; - itemPercent /= child.RunTimeTicks.Value; - totalPercentPlayed += itemPercent; - } - } - - if (isUnplayed) - { - unplayed++; - } - - double percent = 0; - SyncJobItemStatus syncItemProgress; - if (syncProgress.TryGetValue(child.Id.ToString("N"), out syncItemProgress)) - { - switch (syncItemProgress) - { - case SyncJobItemStatus.Synced: - percent = 100; - break; - case SyncJobItemStatus.Converting: - case SyncJobItemStatus.ReadyToTransfer: - case SyncJobItemStatus.Transferring: - percent = 50; - break; - } - } - totalSyncPercent += percent; - } - - dto.RecursiveItemCount = recursiveItemCount; - dto.UserData.UnplayedItemCount = unplayed; - - if (recursiveItemCount > 0) - { - dto.UserData.PlayedPercentage = totalPercentPlayed / recursiveItemCount; - - var pct = totalSyncPercent / recursiveItemCount; - if (pct > 0) - { - dto.SyncPercent = pct; - } - } - } - - /// <summary> /// Attaches the primary image aspect ratio. /// </summary> /// <param name="dto">The dto.</param> diff --git a/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriter.cs index 5aa01c706..e44b0c6af 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriter.cs @@ -4,38 +4,41 @@ using System.IO; using System.Threading.Tasks; using ServiceStack; using ServiceStack.Web; +using MediaBrowser.Controller.Net; namespace MediaBrowser.Server.Implementations.HttpServer { - public class AsyncStreamWriterFunc : IStreamWriter, IAsyncStreamWriter, IHasOptions + public class AsyncStreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions { /// <summary> /// Gets or sets the source stream. /// </summary> /// <value>The source stream.</value> - private Func<Stream, Task> Writer { get; set; } - - /// <summary> - /// Gets the options. - /// </summary> - /// <value>The options.</value> - public IDictionary<string, string> Options { get; private set; } + private IAsyncStreamSource _source; public Action OnComplete { get; set; } public Action OnError { get; set; } /// <summary> - /// Initializes a new instance of the <see cref="StreamWriter" /> class. + /// Initializes a new instance of the <see cref="AsyncStreamWriter" /> class. /// </summary> - public AsyncStreamWriterFunc(Func<Stream, Task> writer, IDictionary<string, string> headers) + public AsyncStreamWriter(IAsyncStreamSource source) { - Writer = writer; + _source = source; + } - if (headers == null) + public IDictionary<string, string> Options + { + get { - headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + var hasOptions = _source as IHasOptions; + if (hasOptions != null) + { + return hasOptions.Options; + } + + return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } - Options = headers; } /// <summary> @@ -44,13 +47,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="responseStream">The response stream.</param> public void WriteTo(Stream responseStream) { - var task = Writer(responseStream); + var task = _source.WriteToAsync(responseStream); Task.WaitAll(task); } public async Task WriteToAsync(Stream responseStream) { - await Writer(responseStream).ConfigureAwait(false); + await _source.WriteToAsync(responseStream).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 90055d8ec..633208739 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -331,6 +331,46 @@ namespace MediaBrowser.Server.Implementations.HttpServer return url; } + private string NormalizeConfiguredLocalAddress(string address) + { + var index = address.Trim('/').IndexOf('/'); + + if (index != -1) + { + address = address.Substring(index + 1); + } + + return address.Trim('/'); + } + + private bool ValidateHost(Uri url) + { + var hosts = _config + .Configuration + .LocalNetworkAddresses + .Select(NormalizeConfiguredLocalAddress) + .ToList(); + + if (hosts.Count == 0) + { + return true; + } + + var host = url.Host ?? string.Empty; + + _logger.Debug("Validating host {0}", host); + + if (_networkManager.IsInPrivateAddressSpace(host)) + { + hosts.Add("localhost"); + hosts.Add("127.0.0.1"); + + return hosts.Any(i => host.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1); + } + + return true; + } + /// <summary> /// Overridable method that can be used to implement a custom hnandler /// </summary> @@ -350,6 +390,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer return ; } + if (!ValidateHost(url)) + { + httpRes.StatusCode = 400; + httpRes.ContentType = "text/plain"; + httpRes.Write("Invalid host"); + + httpRes.Close(); + return; + } + var operationName = httpReq.OperationName; var localPath = url.LocalPath; diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 27065d0f5..c55e98388 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -704,9 +704,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer throw error; } - public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null) + public object GetAsyncStreamWriter(IAsyncStreamSource streamSource) { - return new AsyncStreamWriterFunc(streamWriter, responseHeaders); + return new AsyncStreamWriter(streamSource); } } }
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 8c2b927e3..ea9e58ee4 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -38,7 +38,6 @@ namespace MediaBrowser.Server.Implementations.IO /// </summary> private readonly IReadOnlyList<string> _alwaysIgnoreFiles = new List<string> { - "thumbs.db", "small.jpg", "albumart.jpg", @@ -47,6 +46,16 @@ namespace MediaBrowser.Server.Implementations.IO "TempSBE" }; + private readonly IReadOnlyList<string> _alwaysIgnoreExtensions = new List<string> + { + // thumbs.db + ".db", + + // bts sync files + ".bts", + ".sync" + }; + /// <summary> /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. /// </summary> @@ -411,7 +420,9 @@ namespace MediaBrowser.Server.Implementations.IO var filename = Path.GetFileName(path); - var monitorPath = !(!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)); + var monitorPath = !string.IsNullOrEmpty(filename) && + !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) && + !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase); // Ignore certain files var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 52961668d..cc3a7e41f 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -364,7 +364,7 @@ namespace MediaBrowser.Server.Implementations.Library if (item.IsFolder) { - if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel)) + if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel) && !(item is AggregateFolder)) { if (item.SourceType != SourceType.Library) { @@ -829,7 +829,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Person}.</returns> public Person GetPerson(string name) { - return GetItemByName<Person>(ConfigurationManager.ApplicationPaths.PeoplePath, name); + return CreateItemByName<Person>(Person.GetPath(name), name); } /// <summary> @@ -839,7 +839,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Studio}.</returns> public Studio GetStudio(string name) { - return GetItemByName<Studio>(ConfigurationManager.ApplicationPaths.StudioPath, name); + return CreateItemByName<Studio>(Studio.GetPath(name), name); } /// <summary> @@ -849,7 +849,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Genre}.</returns> public Genre GetGenre(string name) { - return GetItemByName<Genre>(ConfigurationManager.ApplicationPaths.GenrePath, name); + return CreateItemByName<Genre>(Genre.GetPath(name), name); } /// <summary> @@ -859,7 +859,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{MusicGenre}.</returns> public MusicGenre GetMusicGenre(string name) { - return GetItemByName<MusicGenre>(ConfigurationManager.ApplicationPaths.MusicGenrePath, name); + return CreateItemByName<MusicGenre>(MusicGenre.GetPath(name), name); } /// <summary> @@ -869,15 +869,10 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{GameGenre}.</returns> public GameGenre GetGameGenre(string name) { - return GetItemByName<GameGenre>(ConfigurationManager.ApplicationPaths.GameGenrePath, name); + return CreateItemByName<GameGenre>(GameGenre.GetPath(name), name); } /// <summary> - /// The us culture - /// </summary> - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - /// <summary> /// Gets a Year /// </summary> /// <param name="value">The value.</param> @@ -890,19 +885,9 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentOutOfRangeException("Years less than or equal to 0 are invalid."); } - return GetItemByName<Year>(ConfigurationManager.ApplicationPaths.YearPath, value.ToString(UsCulture)); - } + var name = value.ToString(CultureInfo.InvariantCulture); - /// <summary> - /// Gets the artists path. - /// </summary> - /// <value>The artists path.</value> - public string ArtistsPath - { - get - { - return Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, "artists"); - } + return CreateItemByName<Year>(Year.GetPath(name), name); } /// <summary> @@ -912,48 +897,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Genre}.</returns> public MusicArtist GetArtist(string name) { - return GetItemByName<MusicArtist>(ArtistsPath, name); - } - - private T GetItemByName<T>(string path, string name) - where T : BaseItem, new() - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException("name"); - } - - // Trim the period at the end because windows will have a hard time with that - var validFilename = _fileSystem.GetValidFilename(name) - .Trim() - .TrimEnd('.'); - - string subFolderPrefix = null; - - var type = typeof(T); - - if (type == typeof(Person)) - { - foreach (char c in validFilename) - { - if (char.IsLetterOrDigit(c)) - { - subFolderPrefix = c.ToString(); - break; - } - } - } - - var fullPath = string.IsNullOrEmpty(subFolderPrefix) ? - Path.Combine(path, validFilename) : - Path.Combine(path, subFolderPrefix, validFilename); - - return CreateItemByName<T>(fullPath, name); + return CreateItemByName<MusicArtist>(MusicArtist.GetPath(name), name); } private T CreateItemByName<T>(string path, string name) @@ -1521,7 +1465,12 @@ namespace MediaBrowser.Server.Implementations.Library private void AddUserToQuery(InternalItemsQuery query, User user) { - if (query.AncestorIds.Length == 0 && !query.ParentId.HasValue && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0 && string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)) + if (query.AncestorIds.Length == 0 && + !query.ParentId.HasValue && + query.ChannelIds.Length == 0 && + query.TopParentIds.Length == 0 && + string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey) + && query.ItemIds.Length == 0) { var userViews = _userviewManager().GetUserViews(new UserViewQuery { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 78df465b1..3f9475480 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -34,8 +34,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers // Must be an image file within a photo collection var collectionType = args.GetCollectionType(); + if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) || - string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) + (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) { if (IsImageFile(args.Path, _imageProcessor)) { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 144f788a7..7bb66ed89 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -50,7 +50,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers { return new CollectionFolder { - CollectionType = GetCollectionType(args) + CollectionType = GetCollectionType(args), + PhysicalLocationsList = args.PhysicalLocations.ToList() }; } } diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs index cf6f070d0..a6d6b5cb8 100644 --- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs +++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs @@ -166,12 +166,12 @@ namespace MediaBrowser.Server.Implementations.Library ExcludeItemTypes = excludeItemTypes.ToArray(), IncludeItemTypes = includeItemTypes.ToArray(), Limit = query.Limit, - IncludeItemsByName = true - + IncludeItemsByName = true, + IsVirtualItem = false }); // Add search hints based on item name - hints.AddRange(mediaItems.Where(IncludeInSearch).Select(item => + hints.AddRange(mediaItems.Select(item => { var index = GetIndex(item.Name, searchTerm, terms); @@ -187,20 +187,6 @@ namespace MediaBrowser.Server.Implementations.Library return Task.FromResult(returnValue); } - private bool IncludeInSearch(BaseItem item) - { - var episode = item as Episode; - - if (episode != null) - { - if (episode.IsMissingEpisode) - { - return false; - } - } - return true; - } - /// <summary> /// Gets the index. /// </summary> diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs index 079867ddd..91b035a35 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Logging; using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -16,15 +17,17 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// </summary> private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; /// <summary> /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - public ArtistsPostScanTask(ILibraryManager libraryManager, ILogger logger) + public ArtistsPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -35,7 +38,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return new ArtistsValidator(_libraryManager, _logger).Run(progress, cancellationToken); + return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs index 353be1a44..3dcdbeae9 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -24,16 +25,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// The _logger /// </summary> private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; /// <summary> /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> - public ArtistsValidator(ILibraryManager libraryManager, ILogger logger) + public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -44,18 +47,17 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var items = _libraryManager.GetAllArtists(new InternalItemsQuery()) - .Items - .Select(i => i.Item1) - .ToList(); + var names = _itemRepo.GetAllArtistNames(); var numComplete = 0; - var count = items.Count; + var count = names.Count; - foreach (var item in items) + foreach (var name in names) { try { + var item = _libraryManager.GetArtist(name); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) @@ -65,7 +67,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error refreshing {0}", ex, item.Name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs index 0ff609da1..77c6d5146 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Logging; using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -17,15 +18,17 @@ namespace MediaBrowser.Server.Implementations.Library.Validators private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; /// <summary> /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger) + public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -36,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return new StudiosValidator(_libraryManager, _logger).Run(progress, cancellationToken); + return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs index 722b74891..a19b8158a 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -15,15 +16,17 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// </summary> private readonly ILibraryManager _libraryManager; + private readonly IItemRepository _itemRepo; /// <summary> /// The _logger /// </summary> private readonly ILogger _logger; - public StudiosValidator(ILibraryManager libraryManager, ILogger logger) + public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -34,18 +37,17 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var items = _libraryManager.GetStudios(new InternalItemsQuery()) - .Items - .Select(i => i.Item1) - .ToList(); + var names = _itemRepo.GetStudioNames(); var numComplete = 0; - var count = items.Count; + var count = names.Count; - foreach (var item in items) + foreach (var name in names) { try { + var item = _libraryManager.GetStudio(name); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) @@ -55,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error refreshing {0}", ex, item.Name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 09d156661..ccbcb910d 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -951,6 +951,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var queryResult = _libraryManager.QueryItems(internalQuery); + RemoveFields(options); + var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ConfigureAwait(false)).ToArray(); var result = new QueryResult<BaseItemDto> @@ -1031,6 +1033,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var user = _userManager.GetUserById(query.UserId); + RemoveFields(options); + var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); var result = new QueryResult<BaseItemDto> @@ -1662,6 +1666,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var internalResult = await GetInternalRecordings(query, cancellationToken).ConfigureAwait(false); + RemoveFields(options); + var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); return new QueryResult<BaseItemDto> @@ -1922,7 +1928,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var channelIds = tuples.Select(i => i.Item2.Id.ToString("N")).Distinct().ToArray(); - var programs = _libraryManager.GetItemList(new InternalItemsQuery(user) + var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, ChannelIds = channelIds, @@ -1932,7 +1938,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv SortBy = new[] { "StartDate" }, TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") } - }).ToList(); + }).ToList() : new List<BaseItem>(); + + RemoveFields(options); foreach (var tuple in tuples) { @@ -1944,14 +1952,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv dto.ChannelType = channel.ChannelType; dto.ServiceName = GetService(channel).Name; - dto.MediaSources = channel.GetMediaSources(true).ToList(); + if (options.Fields.Contains(ItemFields.MediaSources)) + { + dto.MediaSources = channel.GetMediaSources(true).ToList(); + } var channelIdString = channel.Id.ToString("N"); - var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString)); - - if (currentProgram != null) + if (options.AddCurrentProgram) { - dto.CurrentProgram = _dtoService.GetBaseItemDto(currentProgram, options, user); + var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString)); + + if (currentProgram != null) + { + dto.CurrentProgram = _dtoService.GetBaseItemDto(currentProgram, options, user); + } } } } @@ -2447,6 +2461,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv return _dtoService.GetBaseItemDto(folder, new DtoOptions(), user); } + private void RemoveFields(DtoOptions options) + { + options.Fields.Remove(ItemFields.CanDelete); + options.Fields.Remove(ItemFields.CanDownload); + options.Fields.Remove(ItemFields.DisplayPreferencesId); + options.Fields.Remove(ItemFields.Etag); + } + public async Task<Folder> GetInternalLiveTvFolder(CancellationToken cancellationToken) { var name = _localization.GetLocalizedString("ViewTypeLiveTV"); diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 5b83e7cbe..5c508aacd 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -136,7 +136,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts RequiresOpening = false, RequiresClosing = false, - ReadAtNativeFramerate = true + ReadAtNativeFramerate = false }; return new List<MediaSourceInfo> { mediaSource }; diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 52723688f..442465430 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -156,7 +156,7 @@ <Compile Include="EntryPoints\ServerEventNotifier.cs" /> <Compile Include="EntryPoints\UserDataChangeNotifier.cs" /> <Compile Include="FileOrganization\OrganizerScheduledTask.cs" /> - <Compile Include="HttpServer\AsyncStreamWriterFunc.cs" /> + <Compile Include="HttpServer\AsyncStreamWriter.cs" /> <Compile Include="HttpServer\IHttpListener.cs" /> <Compile Include="HttpServer\Security\AuthorizationContext.cs" /> <Compile Include="HttpServer\ContainerAdapter.cs" /> diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 0e36ede7a..2ac625ebc 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -2152,7 +2152,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { if (query.User != null) { - query.SortBy = new[] { ItemSortBy.IsPlayed, "SimilarityScore", ItemSortBy.Random }; + query.SortBy = new[] { "SimilarityScore", ItemSortBy.Random }; } else { @@ -3095,6 +3095,17 @@ namespace MediaBrowser.Server.Implementations.Persistence whereClauses.Add("LocationType<>'Virtual'"); } } + if (query.IsSpecialSeason.HasValue) + { + if (query.IsSpecialSeason.Value) + { + whereClauses.Add("IndexNumber = 0"); + } + else + { + whereClauses.Add("IndexNumber <> 0"); + } + } if (query.IsUnaired.HasValue) { if (query.IsUnaired.Value) @@ -3141,17 +3152,17 @@ namespace MediaBrowser.Server.Implementations.Persistence } if (query.ItemIds.Length > 0) { - var excludeIds = new List<string>(); + var includeIds = new List<string>(); var index = 0; foreach (var id in query.ItemIds) { - excludeIds.Add("Guid = @IncludeId" + index); + includeIds.Add("Guid = @IncludeId" + index); cmd.Parameters.Add(cmd, "@IncludeId" + index, DbType.Guid).Value = new Guid(id); index++; } - whereClauses.Add(string.Join(" OR ", excludeIds.ToArray())); + whereClauses.Add(string.Join(" OR ", includeIds.ToArray())); } if (query.ExcludeItemIds.Length > 0) { @@ -3869,6 +3880,52 @@ namespace MediaBrowser.Server.Implementations.Persistence return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName); } + public List<string> GetStudioNames() + { + return GetItemValueNames(new[] { 3 }); + } + + public List<string> GetAllArtistNames() + { + return GetItemValueNames(new[] { 0, 1 }); + } + + private List<string> GetItemValueNames(int[] itemValueTypes) + { + CheckDisposed(); + + var now = DateTime.UtcNow; + + var typeClause = itemValueTypes.Length == 1 ? + ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : + ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")"); + + var list = new List<string>(); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = "Select Value From ItemValues where " + typeClause + " Group By CleanValue"; + + var commandBehavior = CommandBehavior.SequentialAccess | CommandBehavior.SingleResult; + + using (var reader = cmd.ExecuteReader(commandBehavior)) + { + LogQueryTime("GetItemValueNames", cmd, now); + + while (reader.Read()) + { + if (!reader.IsDBNull(0)) + { + list.Add(reader.GetString(0)); + } + } + } + + } + + return list; + } + private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) { if (query == null) diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs index 8a04f29a2..237d49fda 100644 --- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs +++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs @@ -88,6 +88,14 @@ namespace MediaBrowser.Server.Implementations } } + public string ArtistsPath + { + get + { + return Path.Combine(ItemsByNamePath, "artists"); + } + } + /// <summary> /// Gets the path to the Genre directory /// </summary> diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 84aab5e1f..f495e557a 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1001,7 +1001,8 @@ namespace MediaBrowser.Server.Implementations.Session var series = episode.Series; if (series != null) { - var episodes = series.GetEpisodes(user, false, false) + var episodes = series.GetEpisodes(user) + .Where(i => !i.IsVirtualItem) .SkipWhile(i => i.Id != episode.Id) .ToList(); diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs index d7c77e655..6b7bcfa01 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs @@ -414,6 +414,20 @@ namespace MediaBrowser.Server.Implementations.Sync whereClauses.Add("TargetId=@TargetId"); cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId; } + if (!string.IsNullOrWhiteSpace(query.ExcludeTargetIds)) + { + var excludeIds = (query.ExcludeTargetIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (excludeIds.Length == 1) + { + whereClauses.Add("TargetId<>@ExcludeTargetId"); + cmd.Parameters.Add(cmd, "@ExcludeTargetId", DbType.String).Value = excludeIds[0]; + } + else if (excludeIds.Length > 1) + { + whereClauses.Add("TargetId<>@ExcludeTargetId"); + cmd.Parameters.Add(cmd, "@ExcludeTargetId", DbType.String).Value = excludeIds[0]; + } + } if (!string.IsNullOrWhiteSpace(query.UserId)) { whereClauses.Add("UserId=@UserId"); diff --git a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj index dc19921b6..9422b7fa3 100644 --- a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj +++ b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj @@ -639,6 +639,18 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\syncsettings.html">
<Link>Resources\dashboard-ui\syncsettings.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\touchicon.png">
+ <Link>Resources\dashboard-ui\touchicon.png</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\touchicon114.png">
+ <Link>Resources\dashboard-ui\touchicon114.png</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\touchicon144.png">
+ <Link>Resources\dashboard-ui\touchicon144.png</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\touchicon72.png">
+ <Link>Resources\dashboard-ui\touchicon72.png</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\tv.html">
<Link>Resources\dashboard-ui\tv.html</Link>
</BundleResource>
@@ -1005,8 +1017,11 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\apiclient.js">
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\apiclient.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\appstorage.js">
- <Link>Resources\dashboard-ui\bower_components\emby-apiclient\appstorage.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\appstorage-localstorage.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-apiclient\appstorage-localstorage.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\appstorage-memory.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-apiclient\appstorage-memory.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\bower.json">
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\bower.json</Link>
@@ -1050,9 +1065,6 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\sync\serversync.js">
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\sync\serversync.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-icons\emby-icons.html">
- <Link>Resources\dashboard-ui\bower_components\emby-icons\emby-icons.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\.bower.json">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\.bower.json</Link>
</BundleResource>
@@ -1179,6 +1191,9 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\cardbuilder\peoplecardbuilder.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\cardbuilder\peoplecardbuilder.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\cardbuilder\roundcard.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\cardbuilder\roundcard.css</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\collectioneditor\collectioneditor.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\collectioneditor\collectioneditor.js</Link>
</BundleResource>
@@ -1248,6 +1263,12 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-slider\emby-slider.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-slider\emby-slider.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-tabs\emby-tabs.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-tabs\emby-tabs.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-tabs\emby-tabs.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-tabs\emby-tabs.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-textarea\emby-textarea.css">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-textarea\emby-textarea.css</Link>
</BundleResource>
@@ -3183,36 +3204,6 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\test\index.html">
<Link>Resources\dashboard-ui\bower_components\paper-checkbox\test\index.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\paper-fab.html">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\paper-fab.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\test\a11y.html">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\test\a11y.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-fab\test\index.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\.bower.json">
<Link>Resources\dashboard-ui\bower_components\paper-icon-button\.bower.json</Link>
</BundleResource>
@@ -3339,93 +3330,6 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\test\paper-textarea.html">
<Link>Resources\dashboard-ui\bower_components\paper-input\test\paper-textarea.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-item\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-item\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-item\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-item\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-item\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\all-imports.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\all-imports.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-item\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\paper-icon-item.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\paper-icon-item.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\paper-item-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\paper-item-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\paper-item-body.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\paper-item-body.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\paper-item-shared-styles.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\paper-item-shared-styles.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\paper-item.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\paper-item.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\paper-item\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\test\paper-item.html">
- <Link>Resources\dashboard-ui\bower_components\paper-item\test\paper-item.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-material\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-material\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-material\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-material\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-material\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-material\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-material\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\paper-material-shared-styles.html">
- <Link>Resources\dashboard-ui\bower_components\paper-material\paper-material-shared-styles.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\paper-material.html">
- <Link>Resources\dashboard-ui\bower_components\paper-material\paper-material.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-material\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-material\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-material\test\paper-material.html">
- <Link>Resources\dashboard-ui\bower_components\paper-material\test\paper-material.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\.bower.json">
<Link>Resources\dashboard-ui\bower_components\paper-progress\.bower.json</Link>
</BundleResource>
@@ -3717,6 +3621,12 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\viewcontainer-lite.js">
<Link>Resources\dashboard-ui\components\viewcontainer-lite.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\appfooter\appfooter.css">
+ <Link>Resources\dashboard-ui\components\appfooter\appfooter.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\appfooter\appfooter.js">
+ <Link>Resources\dashboard-ui\components\appfooter\appfooter.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\channelmapper\channelmapper.js">
<Link>Resources\dashboard-ui\components\channelmapper\channelmapper.js</Link>
</BundleResource>
@@ -3726,6 +3636,12 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\directorybrowser\directorybrowser.js">
<Link>Resources\dashboard-ui\components\directorybrowser\directorybrowser.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\dockedtabs\dockedtabs.css">
+ <Link>Resources\dashboard-ui\components\dockedtabs\dockedtabs.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\dockedtabs\dockedtabs.js">
+ <Link>Resources\dashboard-ui\components\dockedtabs\dockedtabs.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\fileorganizer\fileorganizer.js">
<Link>Resources\dashboard-ui\components\fileorganizer\fileorganizer.js</Link>
</BundleResource>
@@ -3771,6 +3687,12 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\imageuploader\imageuploader.template.html">
<Link>Resources\dashboard-ui\components\imageuploader\imageuploader.template.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\libraryoptionseditor\libraryoptionseditor.js">
+ <Link>Resources\dashboard-ui\components\libraryoptionseditor\libraryoptionseditor.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\libraryoptionseditor\libraryoptionseditor.template.html">
+ <Link>Resources\dashboard-ui\components\libraryoptionseditor\libraryoptionseditor.template.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\medialibrarycreator\medialibrarycreator.js">
<Link>Resources\dashboard-ui\components\medialibrarycreator\medialibrarycreator.js</Link>
</BundleResource>
@@ -3864,9 +3786,6 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\logo.png">
<Link>Resources\dashboard-ui\css\images\logo.png</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\logo536.png">
- <Link>Resources\dashboard-ui\css\images\logo536.png</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\mblogoicon.png">
<Link>Resources\dashboard-ui\css\images\mblogoicon.png</Link>
</BundleResource>
@@ -3876,18 +3795,6 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\rotten.png">
<Link>Resources\dashboard-ui\css\images\rotten.png</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\touchicon.png">
- <Link>Resources\dashboard-ui\css\images\touchicon.png</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\touchicon114.png">
- <Link>Resources\dashboard-ui\css\images\touchicon114.png</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\touchicon144.png">
- <Link>Resources\dashboard-ui\css\images\touchicon144.png</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\touchicon72.png">
- <Link>Resources\dashboard-ui\css\images\touchicon72.png</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\userflyoutdefault.png">
<Link>Resources\dashboard-ui\css\images\userflyoutdefault.png</Link>
</BundleResource>
@@ -4548,6 +4455,9 @@ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\ar.json">
<Link>Resources\dashboard-ui\strings\ar.json</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\be-BY.json">
+ <Link>Resources\dashboard-ui\strings\be-BY.json</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\bg-BG.json">
<Link>Resources\dashboard-ui\strings\bg-BG.json</Link>
</BundleResource>
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 74438abbc..9eb8a4736 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -821,42 +821,11 @@ namespace MediaBrowser.Server.Startup.Common private string CertificatePath { get; set; } - private string NormalizeConfiguredLocalAddress(string address) - { - var index = address.Trim('/').IndexOf('/'); - - if (index != -1) - { - address = address.Substring(index + 1); - } - - return address.Trim('/'); - } private IEnumerable<string> GetUrlPrefixes() { - var hosts = ServerConfigurationManager - .Configuration - .LocalNetworkAddresses - .Select(NormalizeConfiguredLocalAddress) - .ToList(); + var hosts = new List<string>(); - if (hosts.Count == 0) - { - hosts.Add("+"); - } - - if (!hosts.Contains("+", StringComparer.OrdinalIgnoreCase)) - { - if (!hosts.Contains("localhost", StringComparer.OrdinalIgnoreCase)) - { - hosts.Add("localhost"); - } - - if (!hosts.Contains("127.0.0.1", StringComparer.OrdinalIgnoreCase)) - { - hosts.Add("127.0.0.1"); - } - } + hosts.Add("+"); return hosts.SelectMany(i => { diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config index 6d840c191..fa7bc9f89 100644 --- a/MediaBrowser.ServerApplication/App.config +++ b/MediaBrowser.ServerApplication/App.config @@ -17,7 +17,7 @@ <add key="ClientSettingsProvider.ServiceUri" value=""/> </appSettings> <startup useLegacyV2RuntimeActivationPolicy="true"> - <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/> </startup> <runtime> <gcAllowVeryLargeObjects enabled="true"/> diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index b01d8c43f..9c5470358 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -9,7 +9,7 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>MediaBrowser.ServerApplication</RootNamespace> <AssemblyName>MediaBrowser.ServerApplication</AssemblyName> - <TargetFrameworkVersion>v4.6</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> <TargetFrameworkProfile /> diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 4e4a98060..601ebfba7 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -17,7 +17,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using CommonIO; -using WebMarkupMin.Core.Minifiers; +using WebMarkupMin.Core; namespace MediaBrowser.WebDashboard.Api { diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index c5af1cee7..65e7fa5d3 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -11,8 +11,6 @@ using System.Threading.Tasks; using CommonIO; using MediaBrowser.Controller.Net; using WebMarkupMin.Core; -using WebMarkupMin.Core.Minifiers; -using WebMarkupMin.Core.Settings; namespace MediaBrowser.WebDashboard.Api { diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 9b51fd489..61facf8ec 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -63,9 +63,9 @@ <Reference Include="ServiceStack.Interfaces">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference>
- <Reference Include="WebMarkupMin.Core, Version=1.0.1.0, Culture=neutral, PublicKeyToken=99472178d266584b, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\WebMarkupMin.Core.1.0.1\lib\net40\WebMarkupMin.Core.dll</HintPath>
+ <Reference Include="WebMarkupMin.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=99472178d266584b, processorArchitecture=MSIL">
+ <HintPath>..\packages\WebMarkupMin.Core.2.1.0\lib\net40-client\WebMarkupMin.Core.dll</HintPath>
+ <Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -101,6 +101,12 @@ <Content Include="dashboard-ui\autoorganizesmart.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\components\appfooter\appfooter.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\components\appfooter\appfooter.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\components\apphost.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -167,6 +173,9 @@ <Content Include="dashboard-ui\components\remotecontrolautoplay.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\components\syncjoblist\syncjoblist.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\components\tvproviders\xmltv.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index a2d13fdf5..3637c6c84 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -2,5 +2,5 @@ <packages> <package id="CommonIO" version="1.0.0.9" targetFramework="net45" /> <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" /> - <package id="WebMarkupMin.Core" version="1.0.1" targetFramework="net45" /> + <package id="WebMarkupMin.Core" version="2.1.0" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 2e34135a6..0c8501e62 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -231,7 +231,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers DateTime added; if (DateTime.TryParseExact(val, BaseNfoSaver.DateAddedFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out added)) { - item.EndDate = added.ToUniversalTime(); + item.DateCreated = added.ToUniversalTime(); } else if (DateTime.TryParse(val, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out added)) { diff --git a/SharedVersion.cs b/SharedVersion.cs index 8de20ebed..ded2038b0 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; [assembly: AssemblyVersion("3.1.*")] -//[assembly: AssemblyVersion("3.1.80")] +//[assembly: AssemblyVersion("3.0.6060")] |
