From 95eaf88abd6a979b19f4ee998936ba6f2f4eef4b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 9 Nov 2014 23:20:11 -0500 Subject: [PATCH] fixes #943 - Add web client filtering by genres, parental ratings, tags and years --- .../UserLibrary/BaseItemsByNameService.cs | 56 +++++++++++++++--- .../UserLibrary/BaseItemsRequest.cs | 40 +++++++++++++ MediaBrowser.Api/UserLibrary/ItemsService.cs | 48 ++++++++-------- .../IO/CommonFileSystem.cs | 16 +++--- .../Entities/InternalItemsQuery.cs | 4 ++ .../Entities/UserViewBuilder.cs | 31 ++++++++++ .../ServerNotifyIcon.cs | 57 +++++++++---------- 7 files changed, 182 insertions(+), 70 deletions(-) diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 458cf8bae3..3ae53daf88 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -193,13 +193,6 @@ namespace MediaBrowser.Api.UserLibrary var filters = request.GetFilters().ToList(); - if (filters.Count == 0) - { - return items; - } - - items = items.AsParallel(); - if (filters.Contains(ItemFilter.Dislikes)) { items = items.Where(i => @@ -243,9 +236,56 @@ namespace MediaBrowser.Api.UserLibrary }); } - return items.AsEnumerable(); + // Avoid implicitly captured closure + var currentRequest = request; + return items.Where(i => ApplyAdditionalFilters(currentRequest, i, user, false)); } + private bool ApplyAdditionalFilters(BaseItemsRequest request, BaseItem i, User user, bool isPreFiltered) + { + if (!isPreFiltered) + { + // Apply tag filter + var tags = request.GetTags(); + if (tags.Length > 0) + { + var hasTags = i as IHasTags; + if (hasTags == null) + { + return false; + } + if (!(tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))) + { + return false; + } + } + + // Apply official rating filter + var officialRatings = request.GetOfficialRatings(); + if (officialRatings.Length > 0 && !officialRatings.Contains(i.OfficialRating ?? string.Empty)) + { + return false; + } + + // Apply genre filter + var genres = request.GetGenres(); + if (genres.Length > 0 && !(genres.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))) + { + return false; + } + + // Apply year filter + var years = request.GetYears(); + if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value))) + { + return false; + } + } + + return true; + } + + /// /// Filters the items. /// diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 3dcd4efbda..6b0c64b797 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -96,6 +96,41 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "IsPlayed", Description = "Optional filter by items that are played, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsPlayed { get; set; } + /// + /// Limit results to items containing specific genres + /// + /// The genres. + [ApiMember(Name = "Genres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Genres { get; set; } + + [ApiMember(Name = "OfficialRatings", Description = "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string OfficialRatings { get; set; } + + [ApiMember(Name = "Tags", Description = "Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Tags { get; set; } + + /// + /// Limit results to items containing specific years + /// + /// The years. + [ApiMember(Name = "Years", Description = "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Years { get; set; } + + public string[] GetGenres() + { + return (Genres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + + public string[] GetTags() + { + return (Tags ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + + public string[] GetOfficialRatings() + { + return (OfficialRatings ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + public string[] GetMediaTypes() { return (MediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); @@ -110,6 +145,11 @@ namespace MediaBrowser.Api.UserLibrary { return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } + + public int[] GetYears() + { + return (Years ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); + } /// /// Gets the filters. diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index f2da135c28..18996e0d9f 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -46,13 +46,6 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string PersonTypes { get; set; } - /// - /// Limit results to items containing specific genres - /// - /// The genres. - [ApiMember(Name = "Genres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Genres { get; set; } - [ApiMember(Name = "AllGenres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string AllGenres { get; set; } @@ -73,13 +66,6 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Albums { get; set; } - /// - /// Limit results to items containing specific years - /// - /// The years. - [ApiMember(Name = "Years", Description = "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Years { get; set; } - /// /// Gets or sets the item ids. /// @@ -247,11 +233,6 @@ namespace MediaBrowser.Api.UserLibrary return (AllGenres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } - public string[] GetGenres() - { - return (Genres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - public string[] GetStudios() { return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); @@ -262,11 +243,6 @@ namespace MediaBrowser.Api.UserLibrary return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } - public int[] GetYears() - { - return (Years ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); - } - public IEnumerable GetVideoTypes() { var val = VideoTypes; @@ -520,6 +496,8 @@ namespace MediaBrowser.Api.UserLibrary HasThemeSong = request.HasThemeSong, HasThemeVideo = request.HasThemeVideo, HasTrailer = request.HasTrailer, + Tags = request.GetTags(), + OfficialRatings = request.GetOfficialRatings(), Genres = request.GetGenres(), AllGenres = request.GetAllGenres(), Studios = request.GetStudios(), @@ -951,6 +929,28 @@ namespace MediaBrowser.Api.UserLibrary } } + // Apply tag filter + var tags = request.GetTags(); + if (tags.Length > 0) + { + var hasTags = i as IHasTags; + if (hasTags == null) + { + return false; + } + if (!(tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))) + { + return false; + } + } + + // Apply official rating filter + var officialRatings = request.GetOfficialRatings(); + if (officialRatings.Length > 0 && !officialRatings.Contains(i.OfficialRating ?? string.Empty)) + { + return false; + } + // Apply genre filter var genres = request.GetGenres(); if (genres.Length > 0 && !(genres.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))) diff --git a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs index 131dea36e0..68df0e52ac 100644 --- a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs +++ b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs @@ -398,14 +398,16 @@ namespace MediaBrowser.Common.Implementations.IO throw new ArgumentNullException("path"); } - //if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 && - // !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) - //{ - // return false; - //} - //return true; + // Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\ - return Path.IsPathRooted(path); + if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 && + !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + return true; + + //return Path.IsPathRooted(path); } } } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 1faa6c3914..30043682dd 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -69,9 +69,13 @@ namespace MediaBrowser.Controller.Entities public ImageType[] ImageTypes { get; set; } public VideoType[] VideoTypes { get; set; } public int[] Years { get; set; } + public string[] Tags { get; set; } + public string[] OfficialRatings { get; set; } public InternalItemsQuery() { + Tags = new string[] { }; + OfficialRatings = new string[] { }; SortBy = new string[] { }; MediaTypes = new string[] { }; IncludeItemTypes = new string[] { }; diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index ff3b5920e9..cd579f7ad8 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -870,6 +870,16 @@ namespace MediaBrowser.Controller.Entities return false; } + if (request.Tags.Length > 0) + { + return false; + } + + if (request.OfficialRatings.Length > 0) + { + return false; + } + return true; } @@ -1405,6 +1415,12 @@ namespace MediaBrowser.Controller.Entities } } + // Apply official rating filter + if (query.OfficialRatings.Length > 0 && !query.OfficialRatings.Contains(item.OfficialRating ?? string.Empty)) + { + return false; + } + // Apply person filter if (!string.IsNullOrEmpty(query.Person)) { @@ -1431,6 +1447,21 @@ namespace MediaBrowser.Controller.Entities return false; } } + } + + // Apply tag filter + var tags = query.Tags; + if (tags.Length > 0) + { + var hasTags = item as IHasTags; + if (hasTags == null) + { + return false; + } + if (!(tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))) + { + return false; + } } return true; diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs index 9236738b3a..6e13bb791c 100644 --- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs +++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs @@ -1,14 +1,9 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; using MediaBrowser.Server.Startup.Common.Browser; -using MediaBrowser.ServerApplication.Native; using System; -using System.Diagnostics; using System.Windows.Forms; namespace MediaBrowser.ServerApplication @@ -17,18 +12,18 @@ namespace MediaBrowser.ServerApplication { bool IsDisposing = false; - private System.Windows.Forms.NotifyIcon notifyIcon1; - private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; - private System.Windows.Forms.ToolStripMenuItem cmdExit; - private System.Windows.Forms.ToolStripMenuItem cmdBrowse; - private System.Windows.Forms.ToolStripMenuItem cmdConfigure; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; - private System.Windows.Forms.ToolStripMenuItem cmdRestart; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; - private System.Windows.Forms.ToolStripMenuItem cmdCommunity; - private System.Windows.Forms.ToolStripMenuItem cmdApiDocs; - private System.Windows.Forms.ToolStripMenuItem cmdSwagger; - private System.Windows.Forms.ToolStripMenuItem cmdGtihub; + private NotifyIcon notifyIcon1; + private ContextMenuStrip contextMenuStrip1; + private ToolStripMenuItem cmdExit; + private ToolStripMenuItem cmdBrowse; + private ToolStripMenuItem cmdConfigure; + private ToolStripSeparator toolStripSeparator2; + private ToolStripMenuItem cmdRestart; + private ToolStripSeparator toolStripSeparator1; + private ToolStripMenuItem cmdCommunity; + private ToolStripMenuItem cmdApiDocs; + private ToolStripMenuItem cmdSwagger; + private ToolStripMenuItem cmdGtihub; private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; @@ -61,19 +56,19 @@ namespace MediaBrowser.ServerApplication var components = new System.ComponentModel.Container(); var resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); - contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(components); - notifyIcon1 = new System.Windows.Forms.NotifyIcon(components); + contextMenuStrip1 = new ContextMenuStrip(components); + notifyIcon1 = new NotifyIcon(components); - cmdExit = new System.Windows.Forms.ToolStripMenuItem(); - cmdCommunity = new System.Windows.Forms.ToolStripMenuItem(); - toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); - cmdRestart = new System.Windows.Forms.ToolStripMenuItem(); - toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); - cmdConfigure = new System.Windows.Forms.ToolStripMenuItem(); - cmdBrowse = new System.Windows.Forms.ToolStripMenuItem(); - cmdApiDocs = new System.Windows.Forms.ToolStripMenuItem(); - cmdSwagger = new System.Windows.Forms.ToolStripMenuItem(); - cmdGtihub = new System.Windows.Forms.ToolStripMenuItem(); + cmdExit = new ToolStripMenuItem(); + cmdCommunity = new ToolStripMenuItem(); + toolStripSeparator1 = new ToolStripSeparator(); + cmdRestart = new ToolStripMenuItem(); + toolStripSeparator2 = new ToolStripSeparator(); + cmdConfigure = new ToolStripMenuItem(); + cmdBrowse = new ToolStripMenuItem(); + cmdApiDocs = new ToolStripMenuItem(); + cmdSwagger = new ToolStripMenuItem(); + cmdGtihub = new ToolStripMenuItem(); // // notifyIcon1 @@ -85,7 +80,7 @@ namespace MediaBrowser.ServerApplication // // contextMenuStrip1 // - contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + contextMenuStrip1.Items.AddRange(new ToolStripItem[] { cmdBrowse, cmdConfigure, toolStripSeparator2, @@ -136,7 +131,7 @@ namespace MediaBrowser.ServerApplication // // cmdApiDocs // - cmdApiDocs.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + cmdApiDocs.DropDownItems.AddRange(new ToolStripItem[] { cmdSwagger, cmdGtihub}); cmdApiDocs.Name = "cmdApiDocs";