diff options
| author | Eric Reed <ebr@mediabrowser3.com> | 2013-03-11 11:03:41 -0400 |
|---|---|---|
| committer | Eric Reed <ebr@mediabrowser3.com> | 2013-03-11 11:03:41 -0400 |
| commit | 6470c62c5048dd22caff3a2f4cb296e7e0e5937a (patch) | |
| tree | a566b28ca659291da99bdded88b4b366cc2572e1 | |
| parent | 889bd32e9e798fe816f4ae1d0051e755c30eac5e (diff) | |
| parent | 39020714f75b1bd1fe41355d4e9dadc9620ed8a1 (diff) | |
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
74 files changed, 1343 insertions, 1124 deletions
diff --git a/MediaBrowser.Api/Javascript/JavascriptApiClientService.cs b/MediaBrowser.Api/Javascript/JavascriptApiClientService.cs deleted file mode 100644 index 5ea6f3ebe..000000000 --- a/MediaBrowser.Api/Javascript/JavascriptApiClientService.cs +++ /dev/null @@ -1,59 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Server.Implementations.HttpServer; -using ServiceStack.ServiceHost; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.Javascript -{ - /// <summary> - /// Class GetJavascriptApiClient - /// </summary> - [Route("/JsApiClient.js", "GET")] - [ServiceStack.ServiceHost.Api(("Gets an api wrapper written in Javascript"))] - public class GetJavascriptApiClient - { - /// <summary> - /// Version identifier for caching - /// </summary> - /// <value>The v.</value> - public string V { get; set; } - } - - /// <summary> - /// Class JavascriptApiClientService - /// </summary> - public class JavascriptApiClientService : BaseRestService - { - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetJavascriptApiClient request) - { - TimeSpan? cacheDuration = null; - - // If there's a version number in the query string we can cache this unconditionally - if (!string.IsNullOrEmpty(request.V)) - { - cacheDuration = TimeSpan.FromDays(365); - } - - var assembly = GetType().Assembly.GetName(); - - return ToStaticResult(assembly.Version.ToString().GetMD5(), null, cacheDuration, MimeTypes.GetMimeType("script.js"), GetStream); - } - - /// <summary> - /// Gets the stream. - /// </summary> - /// <returns>Stream.</returns> - private Task<Stream> GetStream() - { - return Task.FromResult(GetType().Assembly.GetManifestResourceStream("MediaBrowser.Api.Javascript.ApiClient.js")); - } - } -} diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index ae15500df..f0633c4b6 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; using MediaBrowser.Server.Implementations.HttpServer; using ServiceStack.ServiceHost; using System; diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 8444dc46c..fc02410cc 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -85,7 +85,6 @@ <Compile Include="Images\ImageRequest.cs" /> <Compile Include="Images\ImageService.cs" /> <Compile Include="Images\ImageWriter.cs" /> - <Compile Include="Javascript\JavascriptApiClientService.cs" /> <Compile Include="Library\LibraryHelpers.cs" /> <Compile Include="Library\LibraryService.cs" /> <Compile Include="Library\LibraryStructureService.cs" /> @@ -107,6 +106,7 @@ <Compile Include="ApiEntryPoint.cs" /> <Compile Include="SystemService.cs" /> <Compile Include="UserLibrary\BaseItemsByNameService.cs" /> + <Compile Include="UserLibrary\BaseItemsRequest.cs" /> <Compile Include="UserLibrary\GenresService.cs" /> <Compile Include="UserLibrary\ItemsService.cs" /> <Compile Include="UserLibrary\PersonsService.cs" /> @@ -141,7 +141,6 @@ <None Include="packages.config" /> </ItemGroup> <ItemGroup> - <EmbeddedResource Include="Javascript\ApiClient.js" /> <Content Include="options.xml" /> </ItemGroup> <ItemGroup /> diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index f22a9e09b..570c6eea0 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Server.Implementations.HttpServer; using System; using System.Collections.Generic; using System.ComponentModel; @@ -14,7 +15,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Server.Implementations.HttpServer; namespace MediaBrowser.Api.Playback { @@ -650,10 +650,8 @@ namespace MediaBrowser.Api.Playback state.VideoStream = GetMediaStream(media.MediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video, true); state.SubtitleStream = GetMediaStream(media.MediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false); } - else - { - state.AudioStream = GetMediaStream(media.MediaStreams, null, MediaStreamType.Audio, true); - } + + state.AudioStream = GetMediaStream(media.MediaStreams, null, MediaStreamType.Audio, true); return state; } diff --git a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs index ecdab94b3..98033c057 100644 --- a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs @@ -18,8 +18,8 @@ namespace MediaBrowser.Api.Playback.Hls } - [Route("/Audio/{Id}/segments/{SegmentId}.mp3", "GET")] - [Route("/Audio/{Id}/segments/{SegmentId}.aac", "GET")] + [Route("/Audio/{Id}/segments/{SegmentId}/stream.mp3", "GET")] + [Route("/Audio/{Id}/segments/{SegmentId}/stream.aac", "GET")] [ServiceStack.ServiceHost.Api(Description = "Gets an Http live streaming segment file. Internal use only.")] public class GetHlsAudioSegment { diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 1fb8a504f..c27219bbf 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -127,7 +127,7 @@ namespace MediaBrowser.Api.Playback.Hls // The segement paths within the playlist are phsyical, so strip that out to make it relative fileText = fileText.Replace(Path.GetDirectoryName(playlist) + Path.DirectorySeparatorChar, string.Empty); - fileText = fileText.Replace(SegmentFilePrefix, "segments/"); + fileText = fileText.Replace(SegmentFilePrefix, "segments/").Replace(".ts", "/stream.ts").Replace(".aac", "/stream.aac").Replace(".mp3", "/stream.mp3"); // Even though we specify target duration of 9, ffmpeg seems unable to keep all segments under that amount fileText = fileText.Replace("#EXT-X-TARGETDURATION:9", "#EXT-X-TARGETDURATION:10"); diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index dfbed538f..578eb93ac 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Api.Playback.Hls } - [Route("/Videos/{Id}/segments/{SegmentId}.ts", "GET")] + [Route("/Videos/{Id}/segments/{SegmentId}/stream.ts", "GET")] [ServiceStack.ServiceHost.Api(Description = "Gets an Http live streaming segment file. Internal use only.")] public class GetHlsVideoSegment { diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 251cd4bd6..a4acc2845 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -135,11 +135,7 @@ namespace MediaBrowser.Api.Playback.Progressive ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); } - return new ProgressiveStreamWriter - { - Path = outputPath, - State = state - }; + return new ProgressiveStreamWriter(outputPath, state, Logger); } /// <summary> diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index e9e134002..efab3bbc6 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -9,9 +9,22 @@ namespace MediaBrowser.Api.Playback.Progressive { public class ProgressiveStreamWriter : IStreamWriter { - public string Path { get; set; } - public StreamState State { get; set; } - public ILogger Logger { get; set; } + private string Path { get; set; } + private StreamState State { get; set; } + private ILogger Logger { get; set; } + + /// <summary> + /// Initializes a new instance of the <see cref="ProgressiveStreamWriter" /> class. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="state">The state.</param> + /// <param name="logger">The logger.</param> + public ProgressiveStreamWriter(string path, StreamState state, ILogger logger) + { + Path = path; + State = state; + Logger = logger; + } /// <summary> /// Writes to. @@ -38,6 +51,8 @@ namespace MediaBrowser.Api.Playback.Progressive catch (Exception ex) { Logger.ErrorException("Error streaming media", ex); + + throw; } finally { diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index d8ffa61fd..10455c028 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.Api.Playback.Progressive [Route("/Videos/{Id}/stream.mkv", "GET")] [Route("/Videos/{Id}/stream.mpeg", "GET")] [Route("/Videos/{Id}/stream.avi", "GET")] + [Route("/Videos/{Id}/stream.m2ts", "GET")] [Route("/Videos/{Id}/stream", "GET")] [ServiceStack.ServiceHost.Api(Description = "Gets a video stream")] public class GetVideoStream : VideoStreamRequest diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 975f8bdfe..5fb2097cd 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -1,7 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using MediaBrowser.Server.Implementations.HttpServer; using ServiceStack.ServiceHost; using System; @@ -23,12 +23,16 @@ namespace MediaBrowser.Api.UserLibrary /// The _user manager /// </summary> protected readonly IUserManager UserManager; + /// <summary> + /// The library manager + /// </summary> protected readonly ILibraryManager LibraryManager; /// <summary> /// Initializes a new instance of the <see cref="BaseItemsByNameService{TItemType}" /> class. /// </summary> /// <param name="userManager">The user manager.</param> + /// <param name="libraryManager">The library manager.</param> protected BaseItemsByNameService(IUserManager userManager, ILibraryManager libraryManager) { UserManager = userManager; @@ -44,7 +48,7 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId); - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, UserManager, LibraryManager, user.Id); + var item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.ParentId, UserManager, LibraryManager, user.Id); IEnumerable<BaseItem> items; @@ -157,54 +161,7 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class GetItemsByName /// </summary> - public class GetItemsByName : IReturn<ItemsResult> + public class GetItemsByName : BaseItemsRequest, IReturn<ItemsResult> { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the start index. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// Gets or sets the size of the page. - /// </summary> - /// <value>The size of the page.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this <see cref="GetItemsByName" /> is recursive. - /// </summary> - /// <value><c>true</c> if recursive; otherwise, <c>false</c>.</value> - [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool Recursive { get; set; } - - /// <summary> - /// Gets or sets the sort order. - /// </summary> - /// <value>The sort order.</value> - [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public SortOrder? SortOrder { get; set; } - - /// <summary> - /// If specified the search will be localized within a specific item or folder - /// </summary> - /// <value>The item id.</value> - [ApiMember(Name = "Id", Description = "If specified the search will be localized within a specific item or folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <value>The fields.</value> - public string Fields { get; set; } } } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs new file mode 100644 index 000000000..ed1beab6f --- /dev/null +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -0,0 +1,58 @@ +using MediaBrowser.Model.Entities; +using ServiceStack.ServiceHost; +using System; + +namespace MediaBrowser.Api.UserLibrary +{ + public abstract class BaseItemsRequest + { + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public Guid UserId { get; set; } + + /// <summary> + /// Skips over a given number of items within the results. Use for paging. + /// </summary> + /// <value>The start index.</value> + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// <summary> + /// The maximum number of items to return + /// </summary> + /// <value>The limit.</value> + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + + /// <summary> + /// Whether or not to perform the query recursively + /// </summary> + /// <value><c>true</c> if recursive; otherwise, <c>false</c>.</value> + [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool Recursive { get; set; } + + /// <summary> + /// Gets or sets the sort order. + /// </summary> + /// <value>The sort order.</value> + [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public SortOrder? SortOrder { get; set; } + + /// <summary> + /// Specify this to localize the search to a specific item or folder. Omit to use the root. + /// </summary> + /// <value>The parent id.</value> + [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ParentId { get; set; } + + /// <summary> + /// Fields to return within the items, in addition to basic information + /// </summary> + /// <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: AudioInfo, Chapters, DateCreated, DisplayMediaType, DisplayPreferences, Genres, ItemCounts, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SeriesInfo, SortName, Studios, Taglines, TrailerUrls, UserData", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } + } +} diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 92f267a21..ff062c183 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -1,8 +1,7 @@ using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using MediaBrowser.Server.Implementations.HttpServer; using ServiceStack.ServiceHost; using System; @@ -17,44 +16,9 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> [Route("/Users/{UserId}/Items", "GET")] [ServiceStack.ServiceHost.Api(Description = "Gets items based on a query.")] - public class GetItems : IReturn<ItemsResult> + public class GetItems : BaseItemsRequest, IReturn<ItemsResult> { /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// </summary> - /// <value>The parent id.</value> - [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ParentId { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - /// <summary> - /// Whether or not to perform the query recursively - /// </summary> - /// <value><c>true</c> if recursive; otherwise, <c>false</c>.</value> - [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool Recursive { get; set; } - - /// <summary> /// Limit results to items containing a specific person /// </summary> /// <value>The person.</value> @@ -82,40 +46,20 @@ namespace MediaBrowser.Api.UserLibrary public string IndexBy { get; set; } /// <summary> - /// The dynamic, localized sort function name - /// </summary> - /// <value>The dynamic sort by.</value> - public string DynamicSortBy { get; set; } - - /// <summary> /// What to sort the results by /// </summary> /// <value>The sort by.</value> - [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album,AlbumArtist,Artist,DateCreated,DatePlayed,PremiereDate,SortName,Random", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, CommunityRating, DateCreated, DatePlayed, PremiereDate, ProductionYear, SortName, Random, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string SortBy { get; set; } /// <summary> - /// The sort order to return results with - /// </summary> - /// <value>The sort order.</value> - [ApiMember(Name = "SortOrder", Description = "Optional. Ascending / Descending sort order", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SortOrder { get; set; } - - /// <summary> /// Filters to apply to the results /// </summary> /// <value>The filters.</value> - [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder,IsNotFolder,IsUnplayed,IsPlayed,IsFavorite,IsResumable", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsRecentlyAdded, IsResumable", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Filters { get; set; } /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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: AudioInfo, Chapters, DateCreated, DisplayMediaType, DisplayPreferences, Genres, ItemCounts, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SeriesInfo, SortName, Studios, Taglines, TrailerUrls, UserData", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Fields { get; set; } - - /// <summary> /// Limit results to items containing specific genres /// </summary> /// <value>The genres.</value> @@ -254,7 +198,7 @@ namespace MediaBrowser.Api.UserLibrary return ((Folder)item).GetRecursiveChildren(user); } - return ((Folder)item).GetChildren(user, request.IndexBy, request.DynamicSortBy, GetSortOrder(request)); + return ((Folder)item).GetChildren(user, request.IndexBy); } /// <summary> @@ -266,58 +210,9 @@ namespace MediaBrowser.Api.UserLibrary /// <returns>IEnumerable{BaseItem}.</returns> private IEnumerable<BaseItem> ApplySortOrder(GetItems request, IEnumerable<BaseItem> items, User user) { - var isFirst = true; - var descending = (GetSortOrder(request) ?? SortOrder.Ascending) == SortOrder.Descending; - - IOrderedEnumerable<BaseItem> orderedItems = null; + var orderBy = GetOrderBy(request).ToArray(); - foreach (var orderBy in GetOrderBy(request).Select(o => GetComparer(o, user))) - { - if (isFirst) - { - orderedItems = descending ? items.OrderByDescending(i => i, orderBy) : items.OrderBy(i => i, orderBy); - } - else - { - orderedItems = descending ? orderedItems.ThenByDescending(i => i, orderBy) : orderedItems.ThenBy(i => i, orderBy); - } - - isFirst = false; - } - - return orderedItems ?? items; - } - - /// <summary> - /// Gets the comparer. - /// </summary> - /// <param name="sortBy">The sort by.</param> - /// <param name="user">The user.</param> - /// <returns>IComparer{BaseItem}.</returns> - /// <exception cref="System.ArgumentException"></exception> - private IComparer<BaseItem> GetComparer(ItemSortBy sortBy, User user) - { - switch (sortBy) - { - case ItemSortBy.Album: - return new AlbumComparer(); - case ItemSortBy.AlbumArtist: - return new AlbumArtistComparer(); - case ItemSortBy.Artist: - return new ArtistComparer(); - case ItemSortBy.Random: - return new RandomComparer(); - case ItemSortBy.DateCreated: - return new DateCreatedComparer(); - case ItemSortBy.SortName: - return new SortNameComparer(); - case ItemSortBy.PremiereDate: - return new PremiereDateComparer(); - case ItemSortBy.DatePlayed: - return new DatePlayedComparer { User = user }; - default: - throw new ArgumentException(); - } + return orderBy.Length == 0 ? items : _libraryManager.Sort(items, user, orderBy, request.SortOrder ?? SortOrder.Ascending); } /// <summary> @@ -465,7 +360,7 @@ namespace MediaBrowser.Api.UserLibrary return item.ScreenshotImagePaths != null && item.ScreenshotImagePaths.Count > 0; } - if (imageType == ImageType.ChapterImage) + if (imageType == ImageType.Chapter) { var video = item as Video; @@ -522,21 +417,6 @@ namespace MediaBrowser.Api.UserLibrary } /// <summary> - /// Gets the sort order. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Nullable{SortOrder}.</returns> - private SortOrder? GetSortOrder(GetItems request) - { - if (string.IsNullOrEmpty(request.SortOrder)) - { - return null; - } - - return (SortOrder)Enum.Parse(typeof(SortOrder), request.SortOrder, true); - } - - /// <summary> /// Gets the filters. /// </summary> /// <param name="request">The request.</param> @@ -575,16 +455,16 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <returns>IEnumerable{ItemSortBy}.</returns> - private IEnumerable<ItemSortBy> GetOrderBy(GetItems request) + private IEnumerable<string> GetOrderBy(GetItems request) { var val = request.SortBy; if (string.IsNullOrEmpty(val)) { - return new ItemSortBy[] { }; + return new string[] { }; } - return val.Split(',').Select(v => (ItemSortBy)Enum.Parse(typeof(ItemSortBy), v, true)); + return val.Split(','); } /// <summary> @@ -621,201 +501,4 @@ namespace MediaBrowser.Api.UserLibrary return x.DateCreated.CompareTo(y.DateCreated); } } - - /// <summary> - /// Class RandomComparer - /// </summary> - public class RandomComparer : IComparer<BaseItem> - { - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return Guid.NewGuid().CompareTo(Guid.NewGuid()); - } - } - - /// <summary> - /// Class SortNameComparer - /// </summary> - public class SortNameComparer : IComparer<BaseItem> - { - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase); - } - } - - /// <summary> - /// Class AlbumArtistComparer - /// </summary> - public class AlbumArtistComparer : IComparer<BaseItem> - { - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); - } - - /// <summary> - /// Gets the value. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>System.String.</returns> - private string GetValue(BaseItem x) - { - var audio = x as Audio; - - return audio == null ? string.Empty : audio.AlbumArtist; - } - } - - /// <summary> - /// Class AlbumComparer - /// </summary> - public class AlbumComparer : IComparer<BaseItem> - { - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); - } - - /// <summary> - /// Gets the value. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>System.String.</returns> - private string GetValue(BaseItem x) - { - var audio = x as Audio; - - return audio == null ? string.Empty : audio.Album; - } - } - - /// <summary> - /// Class ArtistComparer - /// </summary> - public class ArtistComparer : IComparer<BaseItem> - { - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); - } - - /// <summary> - /// Gets the value. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>System.String.</returns> - private string GetValue(BaseItem x) - { - var audio = x as Audio; - - return audio == null ? string.Empty : audio.Artist; - } - } - - /// <summary> - /// Class PremiereDateComparer - /// </summary> - public class PremiereDateComparer : IComparer<BaseItem> - { - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetDate(x).CompareTo(GetDate(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private DateTime GetDate(BaseItem x) - { - if (x.PremiereDate.HasValue) - { - return x.PremiereDate.Value; - } - - if (x.ProductionYear.HasValue) - { - return new DateTime(x.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc); - } - return DateTime.MaxValue; - } - } - - /// <summary> - /// Class DatePlayedComparer - /// </summary> - public class DatePlayedComparer : IComparer<BaseItem> - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetDate(x).CompareTo(GetDate(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private DateTime GetDate(BaseItem x) - { - var userdata = x.GetUserData(User, false); - - if (userdata != null && userdata.LastPlayedDate.HasValue) - { - return userdata.LastPlayedDate.Value; - } - - return DateTime.MinValue; - } - } } diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 1182dbb05..88b7dc57c 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Connectivity; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using MediaBrowser.Server.Implementations.HttpServer; using ServiceStack.ServiceHost; using ServiceStack.Text.Controller; diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index a9c73d0e9..1950c1428 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -13,7 +13,6 @@ using MediaBrowser.Common.Security; using MediaBrowser.Common.Updates; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.System; using MediaBrowser.Model.Updates; using SimpleInjector; using System; @@ -26,6 +25,10 @@ using System.Threading.Tasks; namespace MediaBrowser.Common.Implementations { + /// <summary> + /// Class BaseApplicationHost + /// </summary> + /// <typeparam name="TApplicationPathsType">The type of the T application paths type.</typeparam> public abstract class BaseApplicationHost<TApplicationPathsType> : IApplicationHost where TApplicationPathsType : class, IApplicationPaths, new() { @@ -87,6 +90,7 @@ namespace MediaBrowser.Common.Implementations /// <summary> /// Gets assemblies that failed to load /// </summary> + /// <value>The failed assemblies.</value> public List<string> FailedAssemblies { get; protected set; } /// <summary> @@ -148,11 +152,31 @@ namespace MediaBrowser.Common.Implementations /// </summary> /// <value>The kernel.</value> protected ITaskManager TaskManager { get; private set; } + /// <summary> + /// Gets the security manager. + /// </summary> + /// <value>The security manager.</value> protected ISecurityManager SecurityManager { get; private set; } + /// <summary> + /// Gets the package manager. + /// </summary> + /// <value>The package manager.</value> protected IPackageManager PackageManager { get; private set; } + /// <summary> + /// Gets the HTTP client. + /// </summary> + /// <value>The HTTP client.</value> protected IHttpClient HttpClient { get; private set; } + /// <summary> + /// Gets the network manager. + /// </summary> + /// <value>The network manager.</value> protected INetworkManager NetworkManager { get; private set; } + /// <summary> + /// Gets the configuration manager. + /// </summary> + /// <value>The configuration manager.</value> protected IConfigurationManager ConfigurationManager { get; private set; } /// <summary> @@ -187,7 +211,21 @@ namespace MediaBrowser.Common.Implementations FindParts(); - Task.Run(() => ConfigureAutoRunAtStartup()); + await RunStartupTasks().ConfigureAwait(false); + } + + /// <summary> + /// Runs the startup tasks. + /// </summary> + /// <returns>Task.</returns> + protected virtual Task RunStartupTasks() + { + return Task.Run(() => + { + Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false)); + + Task.Run(() => ConfigureAutoRunAtStartup()); + }); } /// <summary> @@ -202,6 +240,10 @@ namespace MediaBrowser.Common.Implementations /// <value>The name of the log file prefix.</value> protected abstract string LogFilePrefixName { get; } + /// <summary> + /// Gets the configuration manager. + /// </summary> + /// <returns>IConfigurationManager.</returns> protected abstract IConfigurationManager GetConfigurationManager(); /// <summary> @@ -210,8 +252,6 @@ namespace MediaBrowser.Common.Implementations protected virtual void FindParts() { Plugins = GetExports<IPlugin>(); - - Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false)); } /// <summary> @@ -236,6 +276,7 @@ namespace MediaBrowser.Common.Implementations /// <summary> /// Registers resources that classes will depend on /// </summary> + /// <returns>Task.</returns> protected virtual Task RegisterResources() { return Task.Run(() => @@ -509,10 +550,23 @@ namespace MediaBrowser.Common.Implementations } } + /// <summary> + /// Restarts this instance. + /// </summary> public abstract void Restart(); + /// <summary> + /// Gets or sets a value indicating whether this instance can self update. + /// </summary> + /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value> public abstract bool CanSelfUpdate { get; } + /// <summary> + /// Checks for update. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task{CheckForUpdateResult}.</returns> public abstract Task<CheckForUpdateResult> CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress<double> progress); /// <summary> @@ -529,6 +583,9 @@ namespace MediaBrowser.Common.Implementations EventHelper.QueueEventIfNotNull(ApplicationUpdated, this, new GenericEventArgs<Version> { Argument = package.version }, Logger); } + /// <summary> + /// Shuts down. + /// </summary> public abstract void Shutdown(); } } diff --git a/MediaBrowser.Controller/Drawing/ImageManager.cs b/MediaBrowser.Controller/Drawing/ImageManager.cs index d95f72df7..3c6f845e5 100644 --- a/MediaBrowser.Controller/Drawing/ImageManager.cs +++ b/MediaBrowser.Controller/Drawing/ImageManager.cs @@ -356,7 +356,7 @@ namespace MediaBrowser.Controller.Drawing return item.ScreenshotImagePaths[imageIndex]; } - if (imageType == ImageType.ChapterImage) + if (imageType == ImageType.Chapter) { var video = (Video)item; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index c6f479029..ef34742df 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -13,7 +13,6 @@ using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; -using SortOrder = MediaBrowser.Controller.Sorting.SortOrder; namespace MediaBrowser.Controller.Entities { @@ -177,63 +176,6 @@ namespace MediaBrowser.Controller.Entities } } - #region Sorting - - /// <summary> - /// The _sort by options - /// </summary> - private Dictionary<string, IComparer<BaseItem>> _sortByOptions; - /// <summary> - /// Dictionary of sort options - consists of a display value (localized) and an IComparer of BaseItem - /// </summary> - /// <value>The sort by options.</value> - [IgnoreDataMember] - public Dictionary<string, IComparer<BaseItem>> SortByOptions - { - get { return _sortByOptions ?? (_sortByOptions = GetSortByOptions()); } - } - - /// <summary> - /// Returns the valid set of sort by options for this folder type. - /// Override or extend to modify. - /// </summary> - /// <returns>Dictionary{System.StringIComparer{BaseItem}}.</returns> - protected virtual Dictionary<string, IComparer<BaseItem>> GetSortByOptions() - { - return new Dictionary<string, IComparer<BaseItem>> { - {LocalizedStrings.Instance.GetString("NameDispPref"), new BaseItemComparer(SortOrder.Name, Logger)}, - {LocalizedStrings.Instance.GetString("DateDispPref"), new BaseItemComparer(SortOrder.Date, Logger)}, - {LocalizedStrings.Instance.GetString("RatingDispPref"), new BaseItemComparer(SortOrder.Rating, Logger)}, - {LocalizedStrings.Instance.GetString("RuntimeDispPref"), new BaseItemComparer(SortOrder.Runtime, Logger)}, - {LocalizedStrings.Instance.GetString("YearDispPref"), new BaseItemComparer(SortOrder.Year, Logger)} - }; - - } - - /// <summary> - /// Get a sorting comparer by name - /// </summary> - /// <param name="name">The name.</param> - /// <returns>IComparer{BaseItem}.</returns> - private IComparer<BaseItem> GetSortingFunction(string name) - { - IComparer<BaseItem> sorting; - SortByOptions.TryGetValue(name ?? "", out sorting); - return sorting ?? new BaseItemComparer(SortOrder.Name, Logger); - } - - /// <summary> - /// Get the list of sort by choices for this folder (localized). - /// </summary> - /// <value>The sort by option strings.</value> - [IgnoreDataMember] - public IEnumerable<string> SortByOptionStrings - { - get { return SortByOptions.Keys; } - } - - #endregion - #region Indexing /// <summary> @@ -877,11 +819,9 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <param name="user">The user.</param> /// <param name="indexBy">The index by.</param> - /// <param name="sortBy">The sort by.</param> - /// <param name="sortOrder">The sort order.</param> /// <returns>IEnumerable{BaseItem}.</returns> /// <exception cref="System.ArgumentNullException"></exception> - public virtual IEnumerable<BaseItem> GetChildren(User user, string indexBy = null, string sortBy = null, Model.Entities.SortOrder? sortOrder = null) + public virtual IEnumerable<BaseItem> GetChildren(User user, string indexBy = null) { if (user == null) { @@ -889,7 +829,7 @@ namespace MediaBrowser.Controller.Entities } //the true root should return our users root folder children - if (IsPhysicalRoot) return user.RootFolder.GetChildren(user, indexBy, sortBy, sortOrder); + if (IsPhysicalRoot) return user.RootFolder.GetChildren(user, indexBy); IEnumerable<BaseItem> result = null; @@ -904,14 +844,7 @@ namespace MediaBrowser.Controller.Entities result = ActualChildren.Where(c => c.IsVisible(user)); } - if (string.IsNullOrEmpty(sortBy)) - { - return result; - } - - return sortOrder.HasValue && sortOrder.Value == Model.Entities.SortOrder.Descending - ? result.OrderByDescending(i => i, GetSortingFunction(sortBy)) - : result.OrderBy(i => i, GetSortingFunction(sortBy)); + return result; } /// <summary> diff --git a/MediaBrowser.Controller/Library/DtoBuilder.cs b/MediaBrowser.Controller/Library/DtoBuilder.cs index b8e775e97..fcd3e68aa 100644 --- a/MediaBrowser.Controller/Library/DtoBuilder.cs +++ b/MediaBrowser.Controller/Library/DtoBuilder.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Library { @@ -160,8 +161,6 @@ namespace MediaBrowser.Controller.Library /// <param name="fields">The fields.</param> private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields) { - dto.IsNew = item.IsRecentlyAdded(user); - if (fields.Contains(ItemFields.UserData)) { var userData = item.GetUserData(user, false); @@ -366,18 +365,10 @@ namespace MediaBrowser.Controller.Library { var folder = (Folder)item; - dto.IsRoot = folder.IsRoot; - dto.IsVirtualFolder = folder.IsVirtualFolder; - if (fields.Contains(ItemFields.IndexOptions)) { dto.IndexOptions = folder.IndexByOptionStrings.ToArray(); } - - if (fields.Contains(ItemFields.SortOptions)) - { - dto.SortOptions = folder.SortByOptionStrings.ToArray(); - } } // Add audio info @@ -678,7 +669,7 @@ namespace MediaBrowser.Controller.Library if (!string.IsNullOrEmpty(chapterInfo.ImagePath)) { - dto.ImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.ChapterImage, chapterInfo.ImagePath); + dto.ImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Chapter, chapterInfo.ImagePath); } return dto; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 3f1930209..40131b6d2 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; @@ -158,7 +159,19 @@ namespace MediaBrowser.Controller.Library /// <param name="pluginFolders">The plugin folders.</param> /// <param name="resolvers">The resolvers.</param> /// <param name="introProviders">The intro providers.</param> + /// <param name="itemComparers">The item comparers.</param> void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders, - IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders); + IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers); + + /// <summary> + /// Sorts the specified items. + /// </summary> + /// <param name="items">The items.</param> + /// <param name="user">The user.</param> + /// <param name="sortBy">The sort by.</param> + /// <param name="sortOrder">The sort order.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, + SortOrder sortOrder); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5ae6cccac..89353b2ef 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -185,8 +185,8 @@ <Compile Include="Providers\FolderProviderFromXml.cs" /> <Compile Include="Providers\ImageFromMediaLocationProvider.cs" /> <Compile Include="Providers\MediaInfo\FFProbeVideoInfoProvider.cs" /> - <Compile Include="Sorting\BaseItemComparer.cs" /> - <Compile Include="Sorting\SortOrder.cs" /> + <Compile Include="Sorting\IBaseItemComparer.cs" /> + <Compile Include="Sorting\IUserBaseItemComparer.cs" /> <Compile Include="Updates\IInstallationManager.cs" /> <Compile Include="Weather\IWeatherProvider.cs" /> <Compile Include="Providers\BaseItemXmlParser.cs" /> @@ -202,7 +202,7 @@ </ProjectReference> </ItemGroup> <ItemGroup> - <EmbeddedResource Include="MediaInfo\ffmpeg20130209.zip" /> + <EmbeddedResource Include="MediaInfo\ffmpeg20130310.zip" /> <None Include="packages.config" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Controller/MediaInfo/ffmpeg20130209.zip.REMOVED.git-id b/MediaBrowser.Controller/MediaInfo/ffmpeg20130209.zip.REMOVED.git-id deleted file mode 100644 index 307afc51c..000000000 --- a/MediaBrowser.Controller/MediaInfo/ffmpeg20130209.zip.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -985770c0d2633a13719be2e5cf19554262415f62
\ No newline at end of file diff --git a/MediaBrowser.Controller/MediaInfo/ffmpeg20130310.zip.REMOVED.git-id b/MediaBrowser.Controller/MediaInfo/ffmpeg20130310.zip.REMOVED.git-id new file mode 100644 index 000000000..830011a04 --- /dev/null +++ b/MediaBrowser.Controller/MediaInfo/ffmpeg20130310.zip.REMOVED.git-id @@ -0,0 +1 @@ +a005e50576665b191cbd02b42d6260bffb764690
\ No newline at end of file diff --git a/MediaBrowser.Controller/Sorting/BaseItemComparer.cs b/MediaBrowser.Controller/Sorting/BaseItemComparer.cs deleted file mode 100644 index 94c16775d..000000000 --- a/MediaBrowser.Controller/Sorting/BaseItemComparer.cs +++ /dev/null @@ -1,248 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Sorting { - /// <summary> - /// Class BaseItemComparer - /// </summary> - public class BaseItemComparer : IComparer<BaseItem> { - /// <summary> - /// The _order - /// </summary> - private readonly SortOrder _order; - /// <summary> - /// The _property name - /// </summary> - private readonly string _propertyName; - /// <summary> - /// The _compare culture - /// </summary> - private readonly StringComparison _compareCulture = StringComparison.CurrentCultureIgnoreCase; - - /// <summary> - /// Gets or sets the logger. - /// </summary> - /// <value>The logger.</value> - private ILogger Logger { get; set; } - - /// <summary> - /// Initializes a new instance of the <see cref="BaseItemComparer" /> class. - /// </summary> - /// <param name="order">The order.</param> - /// <param name="logger">The logger.</param> - public BaseItemComparer(SortOrder order, ILogger logger) { - _order = order; - Logger = logger; - } - - /// <summary> - /// Initializes a new instance of the <see cref="BaseItemComparer" /> class. - /// </summary> - /// <param name="order">The order.</param> - /// <param name="compare">The compare.</param> - /// <param name="logger">The logger.</param> - public BaseItemComparer(SortOrder order, StringComparison compare, ILogger logger) - { - _order = order; - _compareCulture = compare; - Logger = logger; - } - - /// <summary> - /// Initializes a new instance of the <see cref="BaseItemComparer" /> class. - /// </summary> - /// <param name="property">The property.</param> - /// <param name="logger">The logger.</param> - public BaseItemComparer(string property, ILogger logger) - { - _order = SortOrder.Custom; - _propertyName = property; - Logger = logger; - } - - /// <summary> - /// Initializes a new instance of the <see cref="BaseItemComparer" /> class. - /// </summary> - /// <param name="property">The property.</param> - /// <param name="compare">The compare.</param> - /// <param name="logger">The logger.</param> - public BaseItemComparer(string property, StringComparison compare, ILogger logger) - { - _order = SortOrder.Custom; - _propertyName = property; - _compareCulture = compare; - Logger = logger; - } - - #region IComparer<BaseItem> Members - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) { - int compare = 0; - - switch (_order) { - - case SortOrder.Date: - compare = -x.DateCreated.CompareTo(y.DateCreated); - break; - - case SortOrder.Year: - - var xProductionYear = x.ProductionYear ?? 0; - var yProductionYear = y.ProductionYear ?? 0; - - - compare = yProductionYear.CompareTo(xProductionYear); - break; - - case SortOrder.Rating: - - var xRating = x.CommunityRating ?? 0; - var yRating = y.CommunityRating ?? 0; - - compare = yRating.CompareTo(xRating); - break; - - case SortOrder.Runtime: - var xRuntime = x.RunTimeTicks ?? 0; - var yRuntime = y.RunTimeTicks ?? 0; - - compare = xRuntime.CompareTo(yRuntime); - break; - - case SortOrder.Custom: - - Logger.Debug("Sorting on custom field " + _propertyName); - var yProp = y.GetType().GetProperty(_propertyName); - var xProp = x.GetType().GetProperty(_propertyName); - if (yProp == null || xProp == null) break; - var yVal = yProp.GetValue(y, null); - var xVal = xProp.GetValue(x,null); - if (yVal == null && xVal == null) break; - if (yVal == null) return 1; - if (xVal == null) return -1; - compare = String.Compare(xVal.ToString(), yVal.ToString(),_compareCulture); - break; - - default: - compare = 0; - break; - } - - if (compare == 0) { - - var name1 = x.SortName ?? x.Name ?? ""; - var name2 = y.SortName ?? y.Name ?? ""; - - //if (Config.Instance.EnableAlphanumericSorting) - compare = AlphaNumericCompare(name1, name2,_compareCulture); - //else - // compare = String.Compare(name1,name2,_compareCulture); - } - - return compare; - } - - - #endregion - - /// <summary> - /// Alphas the numeric compare. - /// </summary> - /// <param name="s1">The s1.</param> - /// <param name="s2">The s2.</param> - /// <param name="compareCulture">The compare culture.</param> - /// <returns>System.Int32.</returns> - private int AlphaNumericCompare(string s1, string s2, StringComparison compareCulture) { - // http://dotnetperls.com/Content/Alphanumeric-Sorting.aspx - - int len1 = s1.Length; - int len2 = s2.Length; - int marker1 = 0; - int marker2 = 0; - - // Walk through two the strings with two markers. - while (marker1 < len1 && marker2 < len2) { - char ch1 = s1[marker1]; - char ch2 = s2[marker2]; - - // Some buffers we can build up characters in for each chunk. - var space1 = new char[len1]; - var loc1 = 0; - var space2 = new char[len2]; - var loc2 = 0; - - // Walk through all following characters that are digits or - // characters in BOTH strings starting at the appropriate marker. - // Collect char arrays. - do { - space1[loc1++] = ch1; - marker1++; - - if (marker1 < len1) { - ch1 = s1[marker1]; - } else { - break; - } - } while (char.IsDigit(ch1) == char.IsDigit(space1[0])); - - do { - space2[loc2++] = ch2; - marker2++; - - if (marker2 < len2) { - ch2 = s2[marker2]; - } else { - break; - } - } while (char.IsDigit(ch2) == char.IsDigit(space2[0])); - - // If we have collected numbers, compare them numerically. - // Otherwise, if we have strings, compare them alphabetically. - var str1 = new string(space1); - var str2 = new string(space2); - - var result = 0; - - //biggest int - 2147483647 - if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]) /*&& str1.Length < 10 && str2.Length < 10*/) //this assumed the entire string was a number... - { - int thisNumericChunk; - var isValid = false; - - if (int.TryParse(str1.Substring(0, str1.Length > 9 ? 10 : str1.Length), out thisNumericChunk)) - { - int thatNumericChunk; - - if (int.TryParse(str2.Substring(0, str2.Length > 9 ? 10 : str2.Length), out thatNumericChunk)) - { - isValid = true; - result = thisNumericChunk.CompareTo(thatNumericChunk); - } - } - - if (!isValid) - { - Logger.Error("Error comparing numeric strings: " + str1 + "/" + str2); - result = String.Compare(str1, str2, compareCulture); - } - - } else { - result = String.Compare(str1,str2,compareCulture); - } - - if (result != 0) { - return result; - } - } - return len1 - len2; - } - } -} diff --git a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs new file mode 100644 index 000000000..6d0b95bcb --- /dev/null +++ b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs @@ -0,0 +1,17 @@ +using MediaBrowser.Controller.Entities; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Sorting +{ + /// <summary> + /// Interface IBaseItemComparer + /// </summary> + public interface IBaseItemComparer : IComparer<BaseItem> + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + string Name { get; } + } +} diff --git a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs new file mode 100644 index 000000000..b9da1f6a6 --- /dev/null +++ b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs @@ -0,0 +1,16 @@ +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Sorting +{ + /// <summary> + /// Represents a BaseItem comparer that requires a User to perform it's comparison + /// </summary> + public interface IUserBaseItemComparer : IBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + User User { get; set; } + } +} diff --git a/MediaBrowser.Controller/Sorting/SortOrder.cs b/MediaBrowser.Controller/Sorting/SortOrder.cs deleted file mode 100644 index 3152ac67e..000000000 --- a/MediaBrowser.Controller/Sorting/SortOrder.cs +++ /dev/null @@ -1,33 +0,0 @@ - -namespace MediaBrowser.Controller.Sorting { - /// <summary> - /// Enum SortOrder - /// </summary> - public enum SortOrder { - - /// <summary> - /// Sort by name - /// </summary> - Name, - /// <summary> - /// Sort by date added to the library - /// </summary> - Date, - /// <summary> - /// Sort by community rating - /// </summary> - Rating, - /// <summary> - /// Sort by runtime - /// </summary> - Runtime, - /// <summary> - /// Sort by year - /// </summary> - Year, - /// <summary> - /// Custom sort order added by plugins - /// </summary> - Custom - } -} diff --git a/MediaBrowser.Installer/App.xaml.cs b/MediaBrowser.Installer/App.xaml.cs index a7c1681e8..3e1230d44 100644 --- a/MediaBrowser.Installer/App.xaml.cs +++ b/MediaBrowser.Installer/App.xaml.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Data; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; +using System.Windows; namespace MediaBrowser.Installer { diff --git a/MediaBrowser.Installer/MediaBrowser.Installer.csproj b/MediaBrowser.Installer/MediaBrowser.Installer.csproj index 473e8d37a..470a18463 100644 --- a/MediaBrowser.Installer/MediaBrowser.Installer.csproj +++ b/MediaBrowser.Installer/MediaBrowser.Installer.csproj @@ -87,15 +87,11 @@ </Reference> <Reference Include="System" /> <Reference Include="System.Configuration" /> - <Reference Include="System.Data" /> <Reference Include="System.Drawing" /> - <Reference Include="System.Web" /> <Reference Include="System.Windows.Forms" /> <Reference Include="System.Xml" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Core" /> - <Reference Include="System.Xml.Linq" /> - <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Xaml"> <RequiredTargetFramework>4.0</RequiredTargetFramework> </Reference> diff --git a/MediaBrowser.Model/DTO/BaseItemDto.cs b/MediaBrowser.Model/DTO/BaseItemDto.cs index 971b0bde7..5b63f588c 100644 --- a/MediaBrowser.Model/DTO/BaseItemDto.cs +++ b/MediaBrowser.Model/DTO/BaseItemDto.cs @@ -155,20 +155,6 @@ namespace MediaBrowser.Model.Dto public bool IsFolder { get; set; } /// <summary> - /// If the item is a Folder this will determine if it's the Root or not - /// </summary> - /// <value><c>null</c> if [is root] contains no value, <c>true</c> if [is root]; otherwise, <c>false</c>.</value> - [ProtoMember(26)] - public bool? IsRoot { get; set; } - - /// <summary> - /// If the item is a Folder this will determine if it's a VF or not - /// </summary> - /// <value><c>null</c> if [is virtual folder] contains no value, <c>true</c> if [is virtual folder]; otherwise, <c>false</c>.</value> - [ProtoMember(27)] - public bool? IsVirtualFolder { get; set; } - - /// <summary> /// Gets or sets the parent id. /// </summary> /// <value>The parent id.</value> @@ -232,13 +218,6 @@ namespace MediaBrowser.Model.Dto public UserItemDataDto UserData { get; set; } /// <summary> - /// Gets or sets a value indicating whether this instance is new. - /// </summary> - /// <value><c>true</c> if this instance is new; otherwise, <c>false</c>.</value> - [ProtoMember(37)] - public bool IsNew { get; set; } - - /// <summary> /// Gets or sets the recently added item count. /// </summary> /// <value>The recently added item count.</value> @@ -316,13 +295,6 @@ namespace MediaBrowser.Model.Dto public List<DayOfWeek> AirDays { get; set; } /// <summary> - /// Gets or sets the sort options. - /// </summary> - /// <value>The sort options.</value> - [ProtoMember(53)] - public string[] SortOptions { get; set; } - - /// <summary> /// Gets or sets the index options. /// </summary> /// <value>The index options.</value> @@ -399,26 +371,6 @@ namespace MediaBrowser.Model.Dto } /// <summary> - /// Gets a value indicating whether this instance can resume. - /// </summary> - /// <value><c>true</c> if this instance can resume; otherwise, <c>false</c>.</value> - [IgnoreDataMember] - public bool CanResume - { - get { return UserData != null && UserData.PlaybackPositionTicks > 0; } - } - - /// <summary> - /// Gets the resume position ticks. - /// </summary> - /// <value>The resume position ticks.</value> - [IgnoreDataMember] - public long ResumePositionTicks - { - get { return UserData == null ? 0 : UserData.PlaybackPositionTicks; } - } - - /// <summary> /// Gets or sets the image tags. /// </summary> /// <value>The image tags.</value> @@ -475,6 +427,26 @@ namespace MediaBrowser.Model.Dto public string MediaType { get; set; } /// <summary> + /// Gets a value indicating whether this instance can resume. + /// </summary> + /// <value><c>true</c> if this instance can resume; otherwise, <c>false</c>.</value> + [IgnoreDataMember] + public bool CanResume + { + get { return UserData != null && UserData.PlaybackPositionTicks > 0; } + } + + /// <summary> + /// Gets the resume position ticks. + /// </summary> + /// <value>The resume position ticks.</value> + [IgnoreDataMember] + public long ResumePositionTicks + { + get { return UserData == null ? 0 : UserData.PlaybackPositionTicks; } + } + + /// <summary> /// Gets the backdrop count. /// </summary> /// <value>The backdrop count.</value> @@ -604,6 +576,10 @@ namespace MediaBrowser.Model.Dto get { return string.Equals(MediaType, Entities.MediaType.Game, StringComparison.OrdinalIgnoreCase); } } + /// <summary> + /// Gets a value indicating whether this instance is person. + /// </summary> + /// <value><c>true</c> if this instance is person; otherwise, <c>false</c>.</value> [IgnoreDataMember] public bool IsPerson { @@ -611,6 +587,16 @@ namespace MediaBrowser.Model.Dto } /// <summary> + /// Gets a value indicating whether this instance is root. + /// </summary> + /// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value> + [IgnoreDataMember] + public bool IsRoot + { + get { return string.Equals(Type, "AggregateFolder", StringComparison.OrdinalIgnoreCase); } + } + + /// <summary> /// Occurs when [property changed]. /// </summary> public event PropertyChangedEventHandler PropertyChanged; diff --git a/MediaBrowser.Model/DTO/ItemSortBy.cs b/MediaBrowser.Model/DTO/ItemSortBy.cs deleted file mode 100644 index 30cd1c3a4..000000000 --- a/MediaBrowser.Model/DTO/ItemSortBy.cs +++ /dev/null @@ -1,42 +0,0 @@ - -namespace MediaBrowser.Model.Dto -{ - /// <summary> - /// Enum ItemSortBy - /// </summary> - public enum ItemSortBy - { - /// <summary> - /// The album - /// </summary> - Album, - /// <summary> - /// The album artist - /// </summary> - AlbumArtist, - /// <summary> - /// The artist - /// </summary> - Artist, - /// <summary> - /// The date created - /// </summary> - DateCreated, - /// <summary> - /// The date played - /// </summary> - DatePlayed, - /// <summary> - /// The premiere date - /// </summary> - PremiereDate, - /// <summary> - /// The sort name - /// </summary> - SortName, - /// <summary> - /// The random - /// </summary> - Random - } -} diff --git a/MediaBrowser.Model/DTO/MediaType.cs b/MediaBrowser.Model/DTO/MediaType.cs deleted file mode 100644 index eae97e616..000000000 --- a/MediaBrowser.Model/DTO/MediaType.cs +++ /dev/null @@ -1,22 +0,0 @@ - -namespace MediaBrowser.Model.Dto -{ - /// <summary> - /// Enum MediaType - /// </summary> - public enum MediaType - { - /// <summary> - /// The audio - /// </summary> - Audio, - /// <summary> - /// The game - /// </summary> - Game, - /// <summary> - /// The video - /// </summary> - Video - } -} diff --git a/MediaBrowser.Model/Entities/ImageType.cs b/MediaBrowser.Model/Entities/ImageType.cs index aaae8dda3..8ad1090b8 100644 --- a/MediaBrowser.Model/Entities/ImageType.cs +++ b/MediaBrowser.Model/Entities/ImageType.cs @@ -49,6 +49,6 @@ namespace MediaBrowser.Model.Entities /// <summary> /// The chapter image /// </summary> - ChapterImage + Chapter } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 6c2a62f91..36f585e5c 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -45,8 +45,7 @@ <Compile Include="Dto\BaseItemPerson.cs" /> <Compile Include="Dto\ChapterInfoDto.cs" /> <Compile Include="Dto\IItemDto.cs" /> - <Compile Include="Dto\ItemsByNameQuery.cs" /> - <Compile Include="Dto\MediaType.cs" /> + <Compile Include="Querying\ItemsByNameQuery.cs" /> <Compile Include="Entities\BaseItemInfo.cs" /> <Compile Include="Connectivity\ClientConnectionInfo.cs" /> <Compile Include="Connectivity\ClientType.cs" /> @@ -63,8 +62,8 @@ <Compile Include="Globalization\CultureDto.cs" /> <Compile Include="IO\FileSystemEntryInfo.cs" /> <Compile Include="Dto\ImageOptions.cs" /> - <Compile Include="Dto\ItemFilter.cs" /> - <Compile Include="Dto\ItemQuery.cs" /> + <Compile Include="Querying\ItemFilter.cs" /> + <Compile Include="Querying\ItemQuery.cs" /> <Compile Include="Entities\LibraryUpdateInfo.cs" /> <Compile Include="Entities\ParentalRating.cs" /> <Compile Include="Dto\StreamOptions.cs" /> @@ -90,11 +89,11 @@ <Compile Include="Configuration\UserConfiguration.cs" /> <Compile Include="Drawing\DrawingUtils.cs" /> <Compile Include="Dto\UserItemDataDto.cs" /> - <Compile Include="Dto\ItemFields.cs" /> - <Compile Include="Dto\ItemSortBy.cs" /> + <Compile Include="Querying\ItemFields.cs" /> + <Compile Include="Querying\ItemSortBy.cs" /> <Compile Include="Dto\BaseItemDto.cs" /> <Compile Include="Dto\UserDto.cs" /> - <Compile Include="Dto\ItemsResult.cs" /> + <Compile Include="Querying\ItemsResult.cs" /> <Compile Include="Entities\DisplayPreferences.cs" /> <Compile Include="Entities\ImageType.cs" /> <Compile Include="Entities\IHasProviderIds.cs" /> diff --git a/MediaBrowser.Model/PropertyChanged.xml b/MediaBrowser.Model/PropertyChanged.xml new file mode 100644 index 000000000..e69755cf4 --- /dev/null +++ b/MediaBrowser.Model/PropertyChanged.xml @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<doc> + <assembly> + <name>PropertyChanged</name> + </assembly> + <members> + <member name="T:PropertyChanged.AlsoNotifyForAttribute"> + <summary> + Injects this property to be notified when a dependant property is set. + </summary> + </member> + <member name="M:PropertyChanged.AlsoNotifyForAttribute.#ctor(System.String)"> + <summary> + Initializes a new instance of <see cref="T:PropertyChanged.DependsOnAttribute"/>. + </summary> + <param name="property">A property that will be notified for.</param> + </member> + <member name="M:PropertyChanged.AlsoNotifyForAttribute.#ctor(System.String,System.String[])"> + <summary> + Initializes a new instance of <see cref="T:PropertyChanged.DependsOnAttribute"/>. + </summary> + <param name="property">A property that will be notified for.</param> + <param name="otherProperties">The properties that will be notified for.</param> + </member> + <member name="T:PropertyChanged.DependsOnAttribute"> + <summary> + Injects this property to be notified when a dependant property is set. + </summary> + </member> + <member name="M:PropertyChanged.DependsOnAttribute.#ctor(System.String)"> + <summary> + Initializes a new instance of <see cref="T:PropertyChanged.DependsOnAttribute"/>. + </summary> + <param name="dependency">A property that the assigned property depends on.</param> + </member> + <member name="M:PropertyChanged.DependsOnAttribute.#ctor(System.String,System.String[])"> + <summary> + Initializes a new instance of <see cref="T:PropertyChanged.DependsOnAttribute"/>. + </summary> + <param name="dependency">A property that the assigned property depends on.</param> + <param name="otherDependencies">The properties that the assigned property depends on.</param> + </member> + <member name="T:PropertyChanged.DoNotNotifyAttribute"> + <summary> + Exclude a <see cref="T:System.Type"/> or property from notification. + </summary> + </member> + <member name="T:PropertyChanged.DoNotSetChangedAttribute"> + <summary> + Exclude a <see cref="T:System.Type"/> or property from IsChanged flagging. + </summary> + </member> + </members> +</doc> diff --git a/MediaBrowser.Model/DTO/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index a24c8a8f0..8cf9796c0 100644 --- a/MediaBrowser.Model/DTO/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -1,5 +1,5 @@ -namespace MediaBrowser.Model.Dto +namespace MediaBrowser.Model.Querying { /// <summary> /// Used to control the data that gets attached to DtoBaseItems @@ -87,11 +87,6 @@ namespace MediaBrowser.Model.Dto SortName, /// <summary> - /// The fields that the server supports sorting on - /// </summary> - SortOptions, - - /// <summary> /// The studios of the item /// </summary> Studios, diff --git a/MediaBrowser.Model/DTO/ItemFilter.cs b/MediaBrowser.Model/Querying/ItemFilter.cs index 038acc587..9c7f139a8 100644 --- a/MediaBrowser.Model/DTO/ItemFilter.cs +++ b/MediaBrowser.Model/Querying/ItemFilter.cs @@ -1,5 +1,5 @@ -namespace MediaBrowser.Model.Dto +namespace MediaBrowser.Model.Querying { /// <summary> /// Enum ItemFilter diff --git a/MediaBrowser.Model/DTO/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs index 6d64a093b..3b320a011 100644 --- a/MediaBrowser.Model/DTO/ItemQuery.cs +++ b/MediaBrowser.Model/Querying/ItemQuery.cs @@ -1,7 +1,8 @@ -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using System; -namespace MediaBrowser.Model.Dto +namespace MediaBrowser.Model.Querying { /// <summary> /// Contains all the possible parameters that can be used to query for items @@ -36,7 +37,7 @@ namespace MediaBrowser.Model.Dto /// What to sort the results by /// </summary> /// <value>The sort by.</value> - public ItemSortBy[] SortBy { get; set; } + public string[] SortBy { get; set; } /// <summary> /// The sort order to return results with @@ -117,12 +118,6 @@ namespace MediaBrowser.Model.Dto public string IndexBy { get; set; } /// <summary> - /// The dynamic, localized sort function name - /// </summary> - /// <value>The dynamic sort by.</value> - public string DynamicSortBy { get; set; } - - /// <summary> /// Gets or sets the image types. /// </summary> /// <value>The image types.</value> diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs new file mode 100644 index 000000000..9599e2aac --- /dev/null +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -0,0 +1,54 @@ + +namespace MediaBrowser.Model.Querying +{ + /// <summary> + /// These represent sort orders that are known by the core + /// </summary> + public static class ItemSortBy + { + /// <summary> + /// The album + /// </summary> + public const string Album = "Album"; + /// <summary> + /// The album artist + /// </summary> + public const string AlbumArtist = "AlbumArtist"; + /// <summary> + /// The artist + /// </summary> + public const string Artist = "Artist"; + /// <summary> + /// The date created + /// </summary> + public const string DateCreated = "DateCreated"; + /// <summary> + /// The date played + /// </summary> + public const string DatePlayed = "DatePlayed"; + /// <summary> + /// The premiere date + /// </summary> + public const string PremiereDate = "PremiereDate"; + /// <summary> + /// The sort name + /// </summary> + public const string SortName = "SortName"; + /// <summary> + /// The random + /// </summary> + public const string Random = "Random"; + /// <summary> + /// The runtime + /// </summary> + public const string Runtime = "Runtime"; + /// <summary> + /// The community rating + /// </summary> + public const string CommunityRating = "CommunityRating"; + /// <summary> + /// The production year + /// </summary> + public const string ProductionYear = "ProductionYear"; + } +} diff --git a/MediaBrowser.Model/Dto/ItemsByNameQuery.cs b/MediaBrowser.Model/Querying/ItemsByNameQuery.cs index a10b29012..35964aea3 100644 --- a/MediaBrowser.Model/Dto/ItemsByNameQuery.cs +++ b/MediaBrowser.Model/Querying/ItemsByNameQuery.cs @@ -1,7 +1,7 @@ using MediaBrowser.Model.Entities; using System; -namespace MediaBrowser.Model.Dto +namespace MediaBrowser.Model.Querying { /// <summary> /// Class ItemsByNameQuery @@ -34,10 +34,10 @@ namespace MediaBrowser.Model.Dto /// <value>The sort order.</value> public SortOrder? SortOrder { get; set; } /// <summary> - /// If specified the search will be localized within a specific item or folder + /// Gets or sets the parent id. /// </summary> - /// <value>The item id.</value> - public string ItemId { get; set; } + /// <value>The parent id.</value> + public string ParentId { get; set; } /// <summary> /// Fields to return within the items, in addition to basic information /// </summary> diff --git a/MediaBrowser.Model/DTO/ItemsResult.cs b/MediaBrowser.Model/Querying/ItemsResult.cs index 623e04fdd..f8393b7fc 100644 --- a/MediaBrowser.Model/DTO/ItemsResult.cs +++ b/MediaBrowser.Model/Querying/ItemsResult.cs @@ -1,6 +1,7 @@ -using ProtoBuf; +using MediaBrowser.Model.Dto; +using ProtoBuf; -namespace MediaBrowser.Model.Dto +namespace MediaBrowser.Model.Querying { /// <summary> /// Represents the result of a query for items diff --git a/MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs b/MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs index cdb6adbe7..0445cd863 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Extensions; +using System.Net; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Model.Logging; @@ -257,7 +258,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer var stream = await factoryFn().ConfigureAwait(false); - return new StreamWriter(stream); + var httpListenerResponse = (HttpListenerResponse) Response.OriginalResponse; + httpListenerResponse.SendChunked = false; + + if (IsRangeRequest) + { + return new RangeRequestWriter(Request.Headers, httpListenerResponse, stream); + } + + httpListenerResponse.ContentLength64 = stream.Length; + return new StreamWriter(stream, Logger); } string content; diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs new file mode 100644 index 000000000..e5c4c9796 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -0,0 +1,177 @@ +using ServiceStack.Service; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.HttpServer +{ + public class RangeRequestWriter : IStreamWriter + { + /// <summary> + /// Gets or sets the source stream. + /// </summary> + /// <value>The source stream.</value> + public Stream SourceStream { get; set; } + public HttpListenerResponse Response { get; set; } + public NameValueCollection RequestHeaders { get; set; } + + /// <summary> + /// Initializes a new instance of the <see cref="StreamWriter" /> class. + /// </summary> + /// <param name="requestHeaders">The request headers.</param> + /// <param name="response">The response.</param> + /// <param name="source">The source.</param> + public RangeRequestWriter(NameValueCollection requestHeaders, HttpListenerResponse response, Stream source) + { + RequestHeaders = requestHeaders; + Response = response; + SourceStream = source; + } + + /// <summary> + /// The _requested ranges + /// </summary> + private List<KeyValuePair<long, long?>> _requestedRanges; + /// <summary> + /// Gets the requested ranges. + /// </summary> + /// <value>The requested ranges.</value> + protected IEnumerable<KeyValuePair<long, long?>> RequestedRanges + { + get + { + if (_requestedRanges == null) + { + _requestedRanges = new List<KeyValuePair<long, long?>>(); + + // Example: bytes=0-,32-63 + var ranges = RequestHeaders["Range"].Split('=')[1].Split(','); + + foreach (var range in ranges) + { + var vals = range.Split('-'); + + long start = 0; + long? end = null; + + if (!string.IsNullOrEmpty(vals[0])) + { + start = long.Parse(vals[0]); + } + if (!string.IsNullOrEmpty(vals[1])) + { + end = long.Parse(vals[1]); + } + + _requestedRanges.Add(new KeyValuePair<long, long?>(start, end)); + } + } + + return _requestedRanges; + } + } + + /// <summary> + /// Writes to. + /// </summary> + /// <param name="responseStream">The response stream.</param> + public void WriteTo(Stream responseStream) + { + Response.Headers["Accept-Ranges"] = "bytes"; + Response.StatusCode = 206; + + var task = WriteToAsync(responseStream); + + Task.WaitAll(task); + } + + /// <summary> + /// Writes to async. + /// </summary> + /// <param name="responseStream">The response stream.</param> + /// <returns>Task.</returns> + private async Task WriteToAsync(Stream responseStream) + { + using (var source = SourceStream) + { + var requestedRange = RequestedRanges.First(); + + var totalLength = SourceStream.Length; + + // If the requested range is "0-", we can optimize by just doing a stream copy + if (!requestedRange.Value.HasValue) + { + await ServeCompleteRangeRequest(source, requestedRange, responseStream, totalLength).ConfigureAwait(false); + } + + // This will have to buffer a portion of the content into memory + await ServePartialRangeRequest(source, requestedRange.Key, requestedRange.Value.Value, responseStream, totalLength).ConfigureAwait(false); + } + } + + /// <summary> + /// Handles a range request of "bytes=0-" + /// This will serve the complete content and add the content-range header + /// </summary> + /// <param name="sourceStream">The source stream.</param> + /// <param name="requestedRange">The requested range.</param> + /// <param name="responseStream">The response stream.</param> + /// <param name="totalContentLength">Total length of the content.</param> + /// <returns>Task.</returns> + private Task ServeCompleteRangeRequest(Stream sourceStream, KeyValuePair<long, long?> requestedRange, Stream responseStream, long totalContentLength) + { + var rangeStart = requestedRange.Key; + var rangeEnd = totalContentLength - 1; + var rangeLength = 1 + rangeEnd - rangeStart; + + // Content-Length is the length of what we're serving, not the original content + Response.ContentLength64 = rangeLength; + Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); + + if (rangeStart > 0) + { + sourceStream.Position = rangeStart; + } + + return sourceStream.CopyToAsync(responseStream); + } + + /// <summary> + /// Serves a partial range request + /// </summary> + /// <param name="sourceStream">The source stream.</param> + /// <param name="rangeStart">The range start.</param> + /// <param name="rangeEnd">The range end.</param> + /// <param name="responseStream">The response stream.</param> + /// <param name="totalContentLength">Total length of the content.</param> + /// <returns>Task.</returns> + private async Task ServePartialRangeRequest(Stream sourceStream, long rangeStart, long rangeEnd, Stream responseStream, long totalContentLength) + { + var rangeLength = 1 + rangeEnd - rangeStart; + + // Content-Length is the length of what we're serving, not the original content + Response.ContentLength64 = rangeLength; + Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); + + sourceStream.Position = rangeStart; + + // Fast track to just copy the stream to the end + if (rangeEnd == totalContentLength - 1) + { + await sourceStream.CopyToAsync(responseStream).ConfigureAwait(false); + } + else + { + // Read the bytes we need + var buffer = new byte[Convert.ToInt32(rangeLength)]; + await sourceStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); + + await responseStream.WriteAsync(buffer, 0, Convert.ToInt32(rangeLength)).ConfigureAwait(false); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs index ab178b6ea..6f5d6e25f 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs @@ -1,4 +1,6 @@ -using ServiceStack.Service; +using MediaBrowser.Model.Logging; +using ServiceStack.Service; +using System; using System.IO; using System.Threading.Tasks; @@ -9,6 +11,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// </summary> public class StreamWriter : IStreamWriter { + private ILogger Logger { get; set; } + /// <summary> /// Gets or sets the source stream. /// </summary> @@ -19,9 +23,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// Initializes a new instance of the <see cref="StreamWriter" /> class. /// </summary> /// <param name="source">The source.</param> - public StreamWriter(Stream source) + /// <param name="logger">The logger.</param> + public StreamWriter(Stream source, ILogger logger) { SourceStream = source; + Logger = logger; } /// <summary> @@ -42,9 +48,18 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <returns>Task.</returns> private async Task WriteToAsync(Stream responseStream) { - using (var src = SourceStream) + try { - await src.CopyToAsync(responseStream).ConfigureAwait(false); + using (var src = SourceStream) + { + await src.CopyToAsync(responseStream).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Logger.ErrorException("Error streaming media", ex); + + throw; } } } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 78ada1431..6cd46a310 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.ScheduledTasks; +using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Server.Implementations.Library.Resolvers; @@ -22,6 +23,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using SortOrder = MediaBrowser.Model.Entities.SortOrder; namespace MediaBrowser.Server.Implementations.Library { @@ -54,6 +56,12 @@ namespace MediaBrowser.Server.Implementations.Library /// <value>The entity resolvers enumerable.</value> private IEnumerable<IItemResolver> EntityResolvers { get; set; } + /// <summary> + /// Gets or sets the comparers. + /// </summary> + /// <value>The comparers.</value> + private IEnumerable<IBaseItemComparer> Comparers { get; set; } + #region LibraryChanged Event /// <summary> /// Fires whenever any validation routine adds or removes items. The added and removed items are properties of the args. @@ -124,12 +132,14 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="pluginFolders">The plugin folders.</param> /// <param name="resolvers">The resolvers.</param> /// <param name="introProviders">The intro providers.</param> - public void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders) + /// <param name="itemComparers">The item comparers.</param> + public void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers) { EntityResolutionIgnoreRules = rules; PluginFolderCreators = pluginFolders; EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); IntroProviders = introProviders; + Comparers = itemComparers; } /// <summary> @@ -702,5 +712,62 @@ namespace MediaBrowser.Server.Implementations.Library { return IntroProviders.SelectMany(i => i.GetIntros(item, user)); } + + /// <summary> + /// Sorts the specified sort by. + /// </summary> + /// <param name="items">The items.</param> + /// <param name="user">The user.</param> + /// <param name="sortBy">The sort by.</param> + /// <param name="sortOrder">The sort order.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, SortOrder sortOrder) + { + var isFirst = true; + + IOrderedEnumerable<BaseItem> orderedItems = null; + + foreach (var orderBy in sortBy.Select(o => GetComparer(o, user)).Where(c => c != null)) + { + if (isFirst) + { + orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, orderBy) : items.OrderBy(i => i, orderBy); + } + else + { + orderedItems = sortOrder == SortOrder.Descending ? orderedItems.ThenByDescending(i => i, orderBy) : orderedItems.ThenBy(i => i, orderBy); + } + + isFirst = false; + } + + return orderedItems ?? items; + } + + /// <summary> + /// Gets the comparer. + /// </summary> + /// <param name="name">The name.</param> + /// <param name="user">The user.</param> + /// <returns>IBaseItemComparer.</returns> + private IBaseItemComparer GetComparer(string name, User user) + { + var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase)); + + if (comparer != null) + { + // If it requires a user, create a new one, and assign the user + if (comparer is IUserBaseItemComparer) + { + var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType()); + + userComparer.User = user; + + return userComparer; + } + } + + return comparer; + } } } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 0b9f7c7e1..2bb75a18d 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -109,6 +109,7 @@ <Compile Include="HttpServer\HttpResultFactory.cs" /> <Compile Include="HttpServer\HttpServer.cs" /> <Compile Include="HttpServer\NativeWebSocket.cs" /> + <Compile Include="HttpServer\RangeRequestWriter.cs" /> <Compile Include="HttpServer\ServerFactory.cs" /> <Compile Include="HttpServer\StreamWriter.cs" /> <Compile Include="HttpServer\SwaggerService.cs" /> @@ -139,6 +140,17 @@ <Compile Include="ServerApplicationPaths.cs" /> <Compile Include="ServerManager\ServerManager.cs" /> <Compile Include="ServerManager\WebSocketConnection.cs" /> + <Compile Include="Sorting\AlbumArtistComparer.cs" /> + <Compile Include="Sorting\AlbumComparer.cs" /> + <Compile Include="Sorting\ArtistComparer.cs" /> + <Compile Include="Sorting\CommunityRatingComparer.cs" /> + <Compile Include="Sorting\DateCreatedComparer.cs" /> + <Compile Include="Sorting\DatePlayedComparer.cs" /> + <Compile Include="Sorting\PremiereDateComparer.cs" /> + <Compile Include="Sorting\ProductionYearComparer.cs" /> + <Compile Include="Sorting\RandomComparer.cs" /> + <Compile Include="Sorting\RuntimeComparer.cs" /> + <Compile Include="Sorting\SortNameComparer.cs" /> <Compile Include="Sqlite\SQLiteDisplayPreferencesRepository.cs" /> <Compile Include="Sqlite\SQLiteExtensions.cs" /> <Compile Include="Sqlite\SQLiteItemRepository.cs" /> diff --git a/MediaBrowser.Server.Implementations/Sorting/AlbumArtistComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AlbumArtistComparer.cs new file mode 100644 index 000000000..2493c0fc6 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -0,0 +1,46 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class AlbumArtistComparer + /// </summary> + public class AlbumArtistComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private string GetValue(BaseItem x) + { + var audio = x as Audio; + + return audio == null ? string.Empty : audio.AlbumArtist; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.AlbumArtist; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/AlbumComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AlbumComparer.cs new file mode 100644 index 000000000..f455d5c2b --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/AlbumComparer.cs @@ -0,0 +1,46 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class AlbumComparer + /// </summary> + public class AlbumComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private string GetValue(BaseItem x) + { + var audio = x as Audio; + + return audio == null ? string.Empty : audio.Album; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.Album; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/ArtistComparer.cs b/MediaBrowser.Server.Implementations/Sorting/ArtistComparer.cs new file mode 100644 index 000000000..c34f096a2 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/ArtistComparer.cs @@ -0,0 +1,46 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class ArtistComparer + /// </summary> + public class ArtistComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private string GetValue(BaseItem x) + { + var audio = x as Audio; + + return audio == null ? string.Empty : audio.Artist; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.Artist; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/CommunityRatingComparer.cs b/MediaBrowser.Server.Implementations/Sorting/CommunityRatingComparer.cs new file mode 100644 index 000000000..bdd18a648 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -0,0 +1,29 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class CommunityRatingComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return (x.CommunityRating ?? 0).CompareTo(y.CommunityRating ?? 0); + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.CommunityRating; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/DateCreatedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DateCreatedComparer.cs new file mode 100644 index 000000000..9862f0a8a --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -0,0 +1,33 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class DateCreatedComparer + /// </summary> + public class DateCreatedComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return DateTime.Compare(x.DateCreated, y.DateCreated); + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.DateCreated; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs new file mode 100644 index 000000000..db7e455c7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -0,0 +1,56 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class DatePlayedComparer + /// </summary> + public class DatePlayedComparer : IUserBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public User User { get; set; } + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private DateTime GetDate(BaseItem x) + { + var userdata = x.GetUserData(User, false); + + if (userdata != null && userdata.LastPlayedDate.HasValue) + { + return userdata.LastPlayedDate.Value; + } + + return DateTime.MaxValue; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.DatePlayed; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/PremiereDateComparer.cs b/MediaBrowser.Server.Implementations/Sorting/PremiereDateComparer.cs new file mode 100644 index 000000000..fcbc58987 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -0,0 +1,52 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class PremiereDateComparer + /// </summary> + public class PremiereDateComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private DateTime GetDate(BaseItem x) + { + if (x.PremiereDate.HasValue) + { + return x.PremiereDate.Value; + } + + if (x.ProductionYear.HasValue) + { + return new DateTime(x.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc); + } + return DateTime.MaxValue; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.PremiereDate; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/ProductionYearComparer.cs b/MediaBrowser.Server.Implementations/Sorting/ProductionYearComparer.cs new file mode 100644 index 000000000..16d531334 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -0,0 +1,52 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class ProductionYearComparer + /// </summary> + public class ProductionYearComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private int GetValue(BaseItem x) + { + if (x.ProductionYear.HasValue) + { + return x.ProductionYear.Value; + } + + if (x.PremiereDate.HasValue) + { + return x.PremiereDate.Value.Year; + } + + return 0; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.ProductionYear; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/RandomComparer.cs b/MediaBrowser.Server.Implementations/Sorting/RandomComparer.cs new file mode 100644 index 000000000..b1677331a --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/RandomComparer.cs @@ -0,0 +1,33 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class RandomComparer + /// </summary> + public class RandomComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return Guid.NewGuid().CompareTo(Guid.NewGuid()); + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.Random; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/RuntimeComparer.cs b/MediaBrowser.Server.Implementations/Sorting/RuntimeComparer.cs new file mode 100644 index 000000000..793cb265e --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/RuntimeComparer.cs @@ -0,0 +1,32 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class RuntimeComparer + /// </summary> + public class RuntimeComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return (x.RunTimeTicks ?? 0).CompareTo(y.RunTimeTicks ?? 0); + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.Runtime; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs new file mode 100644 index 000000000..873753a2b --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs @@ -0,0 +1,33 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + /// <summary> + /// Class SortNameComparer + /// </summary> + public class SortNameComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase); + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.SortName; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs index c6869c12d..8315b2000 100644 --- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs +++ b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs @@ -127,7 +127,10 @@ namespace MediaBrowser.Server.Implementations.Udp /// </summary> public void Stop() { - _udpClient.Close(); + if (_udpClient != null) + { + _udpClient.Close(); + } } /// <summary> diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index c822aae84..d5a505792 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -18,6 +18,7 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Updates; using MediaBrowser.Controller.Weather; using MediaBrowser.IsoMounter; @@ -126,23 +127,21 @@ namespace MediaBrowser.ServerApplication /// </summary> /// <value>The HTTP server.</value> private IHttpServer HttpServer { get; set; } - + /// <summary> - /// Inits this instance. + /// Runs the startup tasks. /// </summary> /// <returns>Task.</returns> - public override async Task Init() + protected override async Task RunStartupTasks() { - await base.Init().ConfigureAwait(false); + // Do these before allowing the base method to run, which will invoke startup scheduled tasks + await ServerKernel.LoadRepositories(ServerConfigurationManager).ConfigureAwait(false); - Task.Run(async () => - { - await ServerKernel.LoadRepositories(ServerConfigurationManager).ConfigureAwait(false); + await base.RunStartupTasks().ConfigureAwait(false); - DirectoryWatchers.Start(); + DirectoryWatchers.Start(); - Parallel.ForEach(GetExports<IServerEntryPoint>(), entryPoint => entryPoint.Run()); - }); + Parallel.ForEach(GetExports<IServerEntryPoint>(), entryPoint => entryPoint.Run()); } /// <summary> @@ -205,13 +204,15 @@ namespace MediaBrowser.ServerApplication ServerKernel.FFMpegManager = new FFMpegManager(ServerKernel, ZipClient, JsonSerializer, ProtobufSerializer, LogManager, ApplicationPaths); ServerKernel.ImageManager = new ImageManager(ServerKernel, ProtobufSerializer, LogManager.GetLogger("ImageManager"), ApplicationPaths); - ServerKernel.UserDataRepositories = GetExports<IUserDataRepository>(); - ServerKernel.UserRepositories = GetExports<IUserRepository>(); - ServerKernel.DisplayPreferencesRepositories = GetExports<IDisplayPreferencesRepository>(); - ServerKernel.ItemRepositories = GetExports<IItemRepository>(); - ServerKernel.WeatherProviders = GetExports<IWeatherProvider>(); - ServerKernel.ImageEnhancers = GetExports<IImageEnhancer>().OrderBy(e => e.Priority).ToArray(); - ServerKernel.StringFiles = GetExports<LocalizedStringData>(); + Parallel.Invoke( + () => ServerKernel.UserDataRepositories = GetExports<IUserDataRepository>(), + () => ServerKernel.UserRepositories = GetExports<IUserRepository>(), + () => ServerKernel.DisplayPreferencesRepositories = GetExports<IDisplayPreferencesRepository>(), + () => ServerKernel.ItemRepositories = GetExports<IItemRepository>(), + () => ServerKernel.WeatherProviders = GetExports<IWeatherProvider>(), + () => ServerKernel.ImageEnhancers = GetExports<IImageEnhancer>().OrderBy(e => e.Priority).ToArray(), + () => ServerKernel.StringFiles = GetExports<LocalizedStringData>() + ); } /// <summary> @@ -237,14 +238,20 @@ namespace MediaBrowser.ServerApplication { base.FindParts(); - HttpServer.Init(GetExports<IRestfulService>(false)); + Parallel.Invoke( + + () => + { + HttpServer.Init(GetExports<IRestfulService>(false)); - ServerManager.AddWebSocketListeners(GetExports<IWebSocketListener>(false)); - ServerManager.Start(); + ServerManager.AddWebSocketListeners(GetExports<IWebSocketListener>(false)); + ServerManager.Start(); + }, - LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(), GetExports<IVirtualFolderCreator>(), GetExports<IItemResolver>(), GetExports<IIntroProvider>()); + () => LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(), GetExports<IVirtualFolderCreator>(), GetExports<IItemResolver>(), GetExports<IIntroProvider>(), GetExports<IBaseItemComparer>()), - ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().OrderBy(e => e.Priority).ToArray()); + () => ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().OrderBy(e => e.Priority).ToArray()) + ); } /// <summary> diff --git a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs index d567a9fd8..1968bbc7d 100644 --- a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs +++ b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs @@ -4,8 +4,10 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; @@ -78,7 +80,8 @@ namespace MediaBrowser.ServerApplication Cursor = Cursors.Wait; await Task.Run(() => { - IEnumerable<BaseItem> children = CurrentUser.Name == "Physical" ? _libraryManager.RootFolder.Children.OrderBy(i => i.SortName) : _libraryManager.RootFolder.GetChildren(CurrentUser, sortBy: LocalizedStrings.Instance.GetString("NameDispPref")); + IEnumerable<BaseItem> children = CurrentUser.Name == "Physical" ? _libraryManager.RootFolder.Children : _libraryManager.RootFolder.GetChildren(CurrentUser); + children = OrderByName(children, CurrentUser); foreach (Folder folder in children) { @@ -86,9 +89,12 @@ namespace MediaBrowser.ServerApplication var currentFolder = folder; Task.Factory.StartNew(() => { - var prefs = ddlProfile.SelectedItem != null ? currentFolder.GetDisplayPrefs(ddlProfile.SelectedItem as User, false) ?? new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")} : new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")}; + var prefs = ddlProfile.SelectedItem != null ? currentFolder.GetDisplayPrefs(ddlProfile.SelectedItem as User, false) ?? new DisplayPreferences {SortBy = ItemSortBy.SortName} : new DisplayPreferences {SortBy = ItemSortBy.SortName}; var node = new TreeViewItem { Tag = currentFolder }; - AddChildren(node, currentFolder.GetChildren(CurrentUser, prefs.IndexBy, prefs.SortBy ?? LocalizedStrings.Instance.GetString("NameDispPref")), CurrentUser); + + var subChildren = currentFolder.GetChildren(CurrentUser, prefs.IndexBy); + subChildren = OrderByName(subChildren, CurrentUser); + AddChildren(node, subChildren, CurrentUser); node.Header = currentFolder.Name + " (" + node.Items.Count + ")"; tvwLibrary.Items.Add(node); @@ -101,6 +107,28 @@ namespace MediaBrowser.ServerApplication } /// <summary> + /// Orders the name of the by. + /// </summary> + /// <param name="items">The items.</param> + /// <param name="user">The user.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + private IEnumerable<BaseItem> OrderByName(IEnumerable<BaseItem> items, User user) + { + return OrderBy(items, user, ItemSortBy.SortName); + } + + /// <summary> + /// Orders the name of the by. + /// </summary> + /// <param name="items">The items.</param> + /// <param name="user">The user.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + private IEnumerable<BaseItem> OrderBy(IEnumerable<BaseItem> items, User user, string order) + { + return _libraryManager.Sort(items, user, new[] { order }, SortOrder.Ascending); + } + + /// <summary> /// Adds the children. /// </summary> /// <param name="parent">The parent.</param> @@ -115,7 +143,7 @@ namespace MediaBrowser.ServerApplication if (subFolder != null) { var prefs = subFolder.GetDisplayPrefs(user, false) ?? new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")}; - AddChildren(node, subFolder.GetChildren(user, sortBy: prefs.SortBy), user); + AddChildren(node, OrderBy(subFolder.GetChildren(user), user, prefs.SortBy), user); node.Header = item.Name + " (" + node.Items.Count + ")"; } else @@ -152,14 +180,29 @@ namespace MediaBrowser.ServerApplication { lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Visible; ddlIndexBy.ItemsSource = folder.IndexByOptionStrings; - ddlSortBy.ItemsSource = folder.SortByOptionStrings; + + ddlSortBy.ItemsSource = new [] + { + ItemSortBy.SortName, + ItemSortBy.Album, + ItemSortBy.AlbumArtist, + ItemSortBy.Artist, + ItemSortBy.CommunityRating, + ItemSortBy.DateCreated, + ItemSortBy.DatePlayed, + ItemSortBy.PremiereDate, + ItemSortBy.ProductionYear, + ItemSortBy.Random, + ItemSortBy.Runtime + }; + var prefs = folder.GetDisplayPrefs(ddlProfile.SelectedItem as User, false); ddlIndexBy.SelectedItem = prefs != null ? prefs.IndexBy ?? LocalizedStrings.Instance.GetString("NoneDispPref") : LocalizedStrings.Instance.GetString("NoneDispPref"); ddlSortBy.SelectedItem = prefs != null - ? prefs.SortBy ?? LocalizedStrings.Instance.GetString("NameDispPref") - : LocalizedStrings.Instance.GetString("NameDispPref"); + ? prefs.SortBy ?? ItemSortBy.SortName + : ItemSortBy.SortName; } else { @@ -311,7 +354,7 @@ namespace MediaBrowser.ServerApplication var folder = treeItem != null ? treeItem.Tag as Folder : null; - var prefs = folder != null ? folder.GetDisplayPrefs(CurrentUser, true) : new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")}; + var prefs = folder != null ? folder.GetDisplayPrefs(CurrentUser, true) : new DisplayPreferences {SortBy = ItemSortBy.SortName}; if (folder != null && prefs.IndexBy != ddlIndexBy.SelectedItem as string) { //grab UI context so we can update within the below task @@ -326,7 +369,7 @@ namespace MediaBrowser.ServerApplication //re-build the current item's children as an index prefs.IndexBy = ddlIndexBy.SelectedItem as string; treeItem.Items.Clear(); - AddChildren(treeItem,folder.GetChildren(CurrentUser, prefs.IndexBy, prefs.SortBy), CurrentUser); + AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, prefs.IndexBy), CurrentUser, prefs.SortBy), CurrentUser); treeItem.Header = folder.Name + "(" + treeItem.Items.Count + ")"; Cursor = Cursors.Arrow; @@ -367,7 +410,7 @@ namespace MediaBrowser.ServerApplication //re-sort prefs.SortBy = ddlSortBy.SelectedItem as string; treeItem.Items.Clear(); - AddChildren(treeItem,folder.GetChildren(CurrentUser,prefs.IndexBy, prefs.SortBy ?? LocalizedStrings.Instance.GetString("NameDispPref")), CurrentUser); + AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, prefs.IndexBy), CurrentUser, prefs.SortBy ?? ItemSortBy.SortName), CurrentUser); treeItem.Header = folder.Name + "(" + treeItem.Items.Count + ")"; Cursor = Cursors.Arrow; diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index ec3137d83..88ef97950 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -76,7 +76,7 @@ </ManifestKeyFile> </PropertyGroup> <PropertyGroup> - <GenerateManifests>true</GenerateManifests> + <GenerateManifests>false</GenerateManifests> </PropertyGroup> <PropertyGroup> <SignManifests>false</SignManifests> @@ -112,6 +112,9 @@ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <Prefer32Bit>true</Prefer32Bit> </PropertyGroup> + <PropertyGroup> + <TargetZone>LocalIntranet</TargetZone> + </PropertyGroup> <ItemGroup> <Reference Include="Hardcodet.Wpf.TaskbarNotification"> <HintPath>..\packages\Hardcodet.Wpf.TaskbarNotification.1.0.4.0\lib\net40\Hardcodet.Wpf.TaskbarNotification.dll</HintPath> diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication_TemporaryKey.pfx b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication_TemporaryKey.pfx Binary files differdeleted file mode 100644 index 64676b054..000000000 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication_TemporaryKey.pfx +++ /dev/null diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 86e4c10ea..4c861c61b 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Extensions; +using System.Reflection; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller; @@ -391,7 +392,6 @@ namespace MediaBrowser.WebDashboard.Api { "http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js", "http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.js", - "../jsapiclient.js" + versionString, "scripts/all.js" + versionString }; @@ -447,20 +447,27 @@ namespace MediaBrowser.WebDashboard.Api var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine); + await AppendResource(assembly, memoryStream, "MediaBrowser.WebDashboard.ApiClient.js", newLineBytes).ConfigureAwait(false); + foreach (var file in scriptFiles) { - using (var stream = assembly.GetManifestResourceStream(resourcePrefix + file)) - { - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - - await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); - } + await AppendResource(assembly, memoryStream, resourcePrefix + file, newLineBytes).ConfigureAwait(false); } memoryStream.Position = 0; return memoryStream; } + private async Task AppendResource(Assembly assembly, Stream outputStream, string path, byte[] newLineBytes) + { + using (var stream = assembly.GetManifestResourceStream(path)) + { + await stream.CopyToAsync(outputStream).ConfigureAwait(false); + + await outputStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); + } + } + } } diff --git a/MediaBrowser.Api/Javascript/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 31022de38..31022de38 100644 --- a/MediaBrowser.Api/Javascript/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js diff --git a/MediaBrowser.WebDashboard/Html/advancedMetadata.html b/MediaBrowser.WebDashboard/Html/advancedMetadata.html index 60e02ba3c..6ea3d7e74 100644 --- a/MediaBrowser.WebDashboard/Html/advancedMetadata.html +++ b/MediaBrowser.WebDashboard/Html/advancedMetadata.html @@ -21,13 +21,10 @@ <label>Disable internet providers for:</label> <div id="divItemTypes"></div> </li> - <li> - <button type="submit" data-theme="b"> + <li style="display: none;"> + <button class="btnSubmit" type="submit" data-theme="b"> Save </button> - <button type="button" onclick="Dashboard.navigate('dashboard.html');"> - Cancel - </button> </li> </ul> </form> diff --git a/MediaBrowser.WebDashboard/Html/metadata.html b/MediaBrowser.WebDashboard/Html/metadata.html index 4618dd4bd..28fd7dedd 100644 --- a/MediaBrowser.WebDashboard/Html/metadata.html +++ b/MediaBrowser.WebDashboard/Html/metadata.html @@ -18,32 +18,29 @@ <form id="metadataConfigurationForm"> <ul data-role="listview" class="ulForm"> <li> - <input type="checkbox" id="chkEnableInternetProviders" name="chkEnableInternetProviders" /> + <input type="checkbox" id="chkEnableInternetProviders" name="chkEnableInternetProviders" onchange="MetadataConfigurationPage.submit();" /> <label for="chkEnableInternetProviders">Download metadata from the internet </label> </li> <li> - <input type="checkbox" id="chkSaveLocal" name="chkSaveLocal" /> + <input type="checkbox" id="chkSaveLocal" name="chkSaveLocal onchange="MetadataConfigurationPage.submit();" /> <label for="chkSaveLocal">Save metadata within media folders </label> </li> <li> <label for="txtRefreshDays">Metadata refresh period (days): </label> - <input type="number" id="txtRefreshDays" name="txtRefreshDays" pattern="[0-9]*" required="required" min="1" /> + <input type="number" id="txtRefreshDays" name="txtRefreshDays" pattern="[0-9]*" required="required" min="1" onchange="MetadataConfigurationPage.submit();" /> </li> <li> <label for="selectLanguage">Preferred language: </label> - <select name="selectLanguage" id="selectLanguage"></select> + <select name="selectLanguage" id="selectLanguage" onchange="MetadataConfigurationPage.submit();"></select> </li> <li> <label for="selectCountry">Country: </label> - <select name="selectCountry" id="selectCountry"></select> + <select name="selectCountry" id="selectCountry" onchange="MetadataConfigurationPage.submit();"></select> </li> - <li> - <button type="submit" data-theme="b"> + <li style="display: none;"> + <button class="btnSubmit" type="submit" data-theme="b"> Save </button> - <button type="button" onclick="Dashboard.navigate('dashboard.html');"> - Cancel - </button> </li> </ul> </form> diff --git a/MediaBrowser.WebDashboard/Html/metadataImages.html b/MediaBrowser.WebDashboard/Html/metadataImages.html index b06a3205b..0197af43d 100644 --- a/MediaBrowser.WebDashboard/Html/metadataImages.html +++ b/MediaBrowser.WebDashboard/Html/metadataImages.html @@ -18,7 +18,7 @@ <form id="metadataImagesConfigurationForm"> <ul data-role="listview" class="ulForm"> <li> - <input type="checkbox" id="chkRefreshItemImages" name="chkRefreshItemImages" /> + <input type="checkbox" id="chkRefreshItemImages" name="chkRefreshItemImages" onchange="MetadataImagesPage.submit();" /> <label for="chkRefreshItemImages">Refresh existing images </label> <div class="fieldDescription"> When enabled, images will be refreshed periodically @@ -26,100 +26,100 @@ </li> <li> <label for="txtNumbackdrops">Max number of backdrops per item: </label> - <input type="number" id="txtNumbackdrops" name="txtNumbackdrops" pattern="[0-9]*" required="required" min="1" /> + <input type="number" id="txtNumbackdrops" name="txtNumbackdrops" pattern="[0-9]*" required="required" min="1" onchange="MetadataImagesPage.submit();" /> </li> <li> <label>Enable additional image downloading:</label> - - <div data-role="collapsible"> - <h3>Movies</h3> - <div data-role="controlgroup"> - <input type="checkbox" data-mini="true" id="chkDownloadMovieArt" name="chkDownloadMovieArt" /> - <label for="chkDownloadMovieArt">Movie Art</label> - <input type="checkbox" data-mini="true" id="chkDownloadMovieBanner" name="chkDownloadMovieBanner" /> - <label for="chkDownloadMovieBanner">Movie Banner</label> + <div data-role="collapsible"> + <h3>Movies</h3> + <div data-role="controlgroup"> + <input type="checkbox" data-mini="true" id="chkDownloadMovieArt" name="chkDownloadMovieArt" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadMovieArt">Movie Art</label> - <input type="checkbox" data-mini="true" id="chkDownloadMovieDisc" name="chkDownloadMovieDisc" /> - <label for="chkDownloadMovieDisc">Movie Disc</label> + <input type="checkbox" data-mini="true" id="chkDownloadMovieBanner" name="chkDownloadMovieBanner" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadMovieBanner">Movie Banner</label> - <input type="checkbox" data-mini="true" id="chkDownloadMovieLogo" name="chkDownloadMovieLogo" /> - <label for="chkDownloadMovieLogo">Movie Logo</label> + <input type="checkbox" data-mini="true" id="chkDownloadMovieDisc" name="chkDownloadMovieDisc" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadMovieDisc">Movie Disc</label> + + <input type="checkbox" data-mini="true" id="chkDownloadMovieLogo" name="chkDownloadMovieLogo" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadMovieLogo">Movie Logo</label> + + <input type="checkbox" data-mini="true" id="chkDownloadMovieThumb" name="chkDownloadMovieThumb" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadMovieThumb">Movie Thumb</label> - <input type="checkbox" data-mini="true" id="chkDownloadMovieThumb" name="chkDownloadMovieThumb" /> - <label for="chkDownloadMovieThumb">Movie Thumb</label> - - </div> </div> + </div> - <div data-role="collapsible"> - <h3>TV Series</h3> - <div data-role="controlgroup"> - <input type="checkbox" data-mini="true" id="chKDownloadTVArt" name="chKDownloadTVArt" /> - <label for="chKDownloadTVArt">TV Series Art</label> + <div data-role="collapsible"> + <h3>TV Series</h3> + <div data-role="controlgroup"> + <input type="checkbox" data-mini="true" id="chKDownloadTVArt" name="chKDownloadTVArt" onchange="MetadataImagesPage.submit();" /> + <label for="chKDownloadTVArt">TV Series Art</label> - <input type="checkbox" data-mini="true" id="chkDownloadTVBanner" name="chkDownloadTVBanner" /> - <label for="chkDownloadTVBanner">TV Series Banner</label> + <input type="checkbox" data-mini="true" id="chkDownloadTVBanner" name="chkDownloadTVBanner" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadTVBanner">TV Series Banner</label> - <input type="checkbox" data-mini="true" id="chkDownloadTVLogo" name="chkDownloadTVLogo" /> - <label for="chkDownloadTVLogo">TV Series Logo</label> + <input type="checkbox" data-mini="true" id="chkDownloadTVLogo" name="chkDownloadTVLogo" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadTVLogo">TV Series Logo</label> - <input type="checkbox" data-mini="true" id="chkDownloadTVThumb" name="chkDownloadTVThumb" /> - <label for="chkDownloadTVThumb">TV Series Thumb</label> - </div> + <input type="checkbox" data-mini="true" id="chkDownloadTVThumb" name="chkDownloadTVThumb" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadTVThumb">TV Series Thumb</label> </div> + </div> - <div data-role="collapsible"> - <h3>TV Seasons</h3> - <div data-role="controlgroup"> - <input type="checkbox" data-mini="true" id="chkDownloadSeasonBackdrops" name="chkDownloadSeasonBackdrops" /> - <label for="chkDownloadSeasonBackdrops">TV Season Backdrops</label> - - <input type="checkbox" data-mini="true" id="chkDownloadSeasonBanner" name="chkDownloadSeasonBanner" /> - <label for="chkDownloadSeasonBanner">TV Season Banner</label> + <div data-role="collapsible"> + <h3>TV Seasons</h3> + <div data-role="controlgroup"> + <input type="checkbox" data-mini="true" id="chkDownloadSeasonBackdrops" name="chkDownloadSeasonBackdrops" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadSeasonBackdrops">TV Season Backdrops</label> - <input type="checkbox" data-mini="true" id="chkDownloadSeasonThumb" name="chkDownloadSeasonThumb" /> - <label for="chkDownloadSeasonThumb">TV Season Thumb</label> - </div> + <input type="checkbox" data-mini="true" id="chkDownloadSeasonBanner" name="chkDownloadSeasonBanner" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadSeasonBanner">TV Season Banner</label> + <input type="checkbox" data-mini="true" id="chkDownloadSeasonThumb" name="chkDownloadSeasonThumb" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadSeasonThumb">TV Season Thumb</label> </div> - <div data-role="collapsible"> - <h3>Music Artists</h3> - <div data-role="controlgroup"> - <input type="checkbox" data-mini="true" id="chkDownloadArtistThumb" name="chkDownloadArtistThumb" /> - <label for="chkDownloadArtistThumb">Music Artist Thumb (primary image)</label> + </div> - <input type="checkbox" data-mini="true" id="chkDownloadArtistBackdrops" name="chkDownloadArtistBackdrops" /> - <label for="chkDownloadArtistBackdrops">Music Artist Backdrops</label> + <div data-role="collapsible"> + <h3>Music Artists</h3> + <div data-role="controlgroup"> + <input type="checkbox" data-mini="true" id="chkDownloadArtistThumb" name="chkDownloadArtistThumb" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadArtistThumb">Music Artist Thumb (primary image)</label> - <input type="checkbox" data-mini="true" id="chkDownloadArtistLogo" name="chkDownloadArtistLogo" /> - <label for="chkDownloadArtistLogo">Music Artist Logo</label> + <input type="checkbox" data-mini="true" id="chkDownloadArtistBackdrops" name="chkDownloadArtistBackdrops" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadArtistBackdrops">Music Artist Backdrops</label> - <input type="checkbox" data-mini="true" id="chkDownloadArtistBanner" name="chkDownloadArtistBanner" /> - <label for="chkDownloadArtistBanner">Music Artist Banner</label> - </div> + <input type="checkbox" data-mini="true" id="chkDownloadArtistLogo" name="chkDownloadArtistLogo" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadArtistLogo">Music Artist Logo</label> + <input type="checkbox" data-mini="true" id="chkDownloadArtistBanner" name="chkDownloadArtistBanner" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadArtistBanner">Music Artist Banner</label> </div> - - <div data-role="collapsible"> - <h3>Music Albums</h3> - <div data-role="controlgroup"> - <input type="checkbox" data-mini="true" id="chkDownloadAlbumPrimary" name="chkDownloadAlbumPrimary" /> - <label for="chkDownloadAlbumPrimary">Music Album Cover</label> - <input type="checkbox" data-mini="true" id="chkDownloadAlbumBackdrops" name="chkDownloadAlbumBackdrops" /> - <label for="chkDownloadAlbumBackdrops">Music Album Backdrops</label> - </div> + </div> + + <div data-role="collapsible"> + <h3>Music Albums</h3> + <div data-role="controlgroup"> + <input type="checkbox" data-mini="true" id="chkDownloadAlbumPrimary" name="chkDownloadAlbumPrimary" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadAlbumPrimary">Music Album Cover</label> + <input type="checkbox" data-mini="true" id="chkDownloadAlbumBackdrops" name="chkDownloadAlbumBackdrops" onchange="MetadataImagesPage.submit();" /> + <label for="chkDownloadAlbumBackdrops">Music Album Backdrops</label> </div> + </div> + </li> <li> <label for="selectTmdbPosterDownloadSize">Tmdb poster download size: </label> - <select id="selectTmdbPosterDownloadSize" name="selectTmdbPosterDownloadSize"> + <select id="selectTmdbPosterDownloadSize" name="selectTmdbPosterDownloadSize" onchange="MetadataImagesPage.submit();"> <option value="original">original</option> <option value="w92">w92</option> <option value="w154">w154</option> @@ -130,7 +130,7 @@ </li> <li> <label for="selectTmdbBackdropDownloadSize">Tmdb backdrop download size: </label> - <select id="selectTmdbBackdropDownloadSize" name="selectTmdbBackdropDownloadSize"> + <select id="selectTmdbBackdropDownloadSize" name="selectTmdbBackdropDownloadSize" onchange="MetadataImagesPage.submit();"> <option value="original">original</option> <option value="w380">w380</option> <option value="w780">w780</option> @@ -139,20 +139,17 @@ </li> <li> <label for="selectTmdbPersonImageDownloadSize">Tmdb person image download size: </label> - <select id="selectTmdbPersonImageDownloadSize" name="selectTmdbPersonImageDownloadSize"> + <select id="selectTmdbPersonImageDownloadSize" name="selectTmdbPersonImageDownloadSize" onchange="MetadataImagesPage.submit();"> <option value="original">original</option> <option value="w45">w45</option> <option value="w185">w185</option> <option value="h632">h632</option> </select> </li> - <li> - <button type="submit" data-theme="b"> + <li style="display: none;"> + <button class="btnSubmit" type="submit" data-theme="b"> Save </button> - <button type="button" onclick="Dashboard.navigate('dashboard.html');"> - Cancel - </button> </li> </ul> </form> diff --git a/MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js b/MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js index 73ed3f431..8b89add10 100644 --- a/MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js +++ b/MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js @@ -33,7 +33,7 @@ var checkedAttribute = configuration.InternetProviderExcludeTypes.indexOf(type) != -1 ? ' checked="checked"' : ''; - html += '<input' + checkedAttribute + ' class="chkItemType" data-itemtype="' + type + '" type="checkbox" name="' + id + '" id="' + id + '" />'; + html += '<input' + checkedAttribute + ' class="chkItemType" data-itemtype="' + type + '" type="checkbox" name="' + id + '" id="' + id + '" onchange="AdvancedMetadataConfigurationPage.submit();" />'; html += '<label for="' + id + '">' + type + '</label>'; } @@ -42,6 +42,12 @@ $('#divItemTypes', page).html(html).trigger("create"); }, + submit: function () { + + $('.btnSubmit', $.mobile.activePage)[0].click(); + + }, + onSubmit: function () { Dashboard.showLoadingMsg(); @@ -54,7 +60,7 @@ return currentCheckbox.getAttribute('data-itemtype'); }); - ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult); + ApiClient.updateServerConfiguration(config); }); // Disable default form submission diff --git a/MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js b/MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js index e68940b83..f7a9ba07b 100644 --- a/MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js +++ b/MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js @@ -79,6 +79,12 @@ $('#selectLanguage', '#metadataConfigurationPage').html(html).selectmenu("refresh"); }, + submit: function () { + + $('.btnSubmit', $.mobile.activePage)[0].click(); + + }, + onSubmit: function () { Dashboard.showLoadingMsg(); @@ -92,7 +98,7 @@ config.PreferredMetadataLanguage = $('#selectLanguage', form).val(); config.MetadataCountryCode = $('#selectCountry', form).val(); - ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult); + ApiClient.updateServerConfiguration(config); }); // Disable default form submission diff --git a/MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js b/MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js index 0dff46c39..e09172d36 100644 --- a/MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js +++ b/MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js @@ -42,6 +42,12 @@ Dashboard.hideLoadingMsg(); }, + submit: function () { + + $('.btnSubmit', $.mobile.activePage)[0].click(); + + }, + onSubmit: function () { Dashboard.showLoadingMsg(); @@ -75,7 +81,7 @@ config.DownloadMusicAlbumImages.Primary = $('#chkDownloadAlbumPrimary', form).checked(); config.DownloadMusicAlbumImages.Backdrops = $('#chkDownloadAlbumBackdrops', form).checked(); - ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult); + ApiClient.updateServerConfiguration(config); }); // Disable default form submission diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 188c18722..07992b1e3 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -403,6 +403,7 @@ <EmbeddedResource Include="Html\css\images\mblogotextblack.png" />
<EmbeddedResource Include="Html\css\images\mblogotextwhite.png" />
<EmbeddedResource Include="Html\css\images\clients\dlna.png" />
+ <EmbeddedResource Include="ApiClient.js" />
<Content Include="Html\css\images\stars.png" />
<EmbeddedResource Include="Html\scripts\MediaPlayer.js" />
</ItemGroup>
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 629487c79..fb7a49ef6 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.40" targetFramework="net45" /> <package id="ServiceStack" version="3.9.38" targetFramework="net45" /> <package id="ServiceStack.Common" version="3.9.38" targetFramework="net45" /> <package id="ServiceStack.OrmLite.SqlServer" version="3.9.39" targetFramework="net45" /> diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index f1ac09af5..4ba290fac 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.40</version> + <version>3.0.43</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.40" /> + <dependency id="MediaBrowser.Common" version="3.0.43" /> <dependency id="NLog" version="2.0.0.2000" /> <dependency id="ServiceStack.Text" version="3.9.38" /> <dependency id="protobuf-net" version="2.0.0.621" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 8b22d0886..36e8ce994 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.40</version> + <version>3.0.43</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index a97d2fc03..5193e4480 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.40</version> + <version>3.0.43</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.40" /> + <dependency id="MediaBrowser.Common" version="3.0.43" /> </dependencies> </metadata> <files> |
