aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/Entities
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/Entities')
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs26
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs133
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs8
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs6
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs2
6 files changed, 132 insertions, 45 deletions
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 4989f0f3f..7586b99e7 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -24,6 +24,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaSegments;
using MediaBrowser.Controller.Persistence;
@@ -1127,6 +1128,15 @@ namespace MediaBrowser.Controller.Entities
var protocol = item.PathProtocol;
+ // Resolve the item path so everywhere we use the media source it will always point to
+ // the correct path even if symlinks are in use. Calling ResolveLinkTarget on a non-link
+ // path will return null, so it's safe to check for all paths.
+ var itemPath = item.Path;
+ if (protocol is MediaProtocol.File && FileSystemHelper.ResolveLinkTarget(itemPath, returnFinalTarget: true) is { Exists: true } linkInfo)
+ {
+ itemPath = linkInfo.FullName;
+ }
+
var info = new MediaSourceInfo
{
Id = item.Id.ToString("N", CultureInfo.InvariantCulture),
@@ -1134,7 +1144,7 @@ namespace MediaBrowser.Controller.Entities
MediaStreams = MediaSourceManager.GetMediaStreams(item.Id),
MediaAttachments = MediaSourceManager.GetMediaAttachments(item.Id),
Name = GetMediaSourceName(item),
- Path = enablePathSubstitution ? GetMappedPath(item, item.Path, protocol) : item.Path,
+ Path = enablePathSubstitution ? GetMappedPath(item, itemPath, protocol) : itemPath,
RunTimeTicks = item.RunTimeTicks,
Container = item.Container,
Size = item.Size,
@@ -1610,12 +1620,17 @@ namespace MediaBrowser.Controller.Entities
return isAllowed;
}
- if (maxAllowedSubRating is not null)
+ if (!maxAllowedRating.HasValue)
+ {
+ return true;
+ }
+
+ if (ratingScore.Score != maxAllowedRating.Value)
{
- return (ratingScore.SubScore ?? 0) <= maxAllowedSubRating && ratingScore.Score <= maxAllowedRating.Value;
+ return ratingScore.Score < maxAllowedRating.Value;
}
- return !maxAllowedRating.HasValue || ratingScore.Score <= maxAllowedRating.Value;
+ return !maxAllowedSubRating.HasValue || (ratingScore.SubScore ?? 0) <= maxAllowedSubRating.Value;
}
public ParentalRatingScore GetParentalRatingScore()
@@ -2038,6 +2053,9 @@ namespace MediaBrowser.Controller.Entities
public virtual async Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken)
=> await LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken).ConfigureAwait(false);
+ public async Task ReattachUserDataAsync(CancellationToken cancellationToken) =>
+ await LibraryManager.ReattachUserDataAsync(this, cancellationToken).ConfigureAwait(false);
+
/// <summary>
/// Validates that images within the item are still on the filesystem.
/// </summary>
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index e9a383690..2ecb6cbdf 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -452,6 +452,7 @@ namespace MediaBrowser.Controller.Entities
// That's all the new and changed ones - now see if any have been removed and need cleanup
var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
var shouldRemove = !IsRoot || allowRemoveRoot;
+ var actuallyRemoved = new List<BaseItem>();
// If it's an AggregateFolder, don't remove
if (shouldRemove && itemsRemoved.Count > 0)
{
@@ -467,6 +468,7 @@ namespace MediaBrowser.Controller.Entities
{
Logger.LogDebug("Removed item: {Path}", item.Path);
+ actuallyRemoved.Add(item);
item.SetParent(null);
LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false);
}
@@ -477,6 +479,20 @@ namespace MediaBrowser.Controller.Entities
{
LibraryManager.CreateItems(newItems, this, cancellationToken);
}
+
+ // After removing items, reattach any detached user data to remaining children
+ // that share the same user data keys (eg. same episode replaced with a new file).
+ if (actuallyRemoved.Count > 0)
+ {
+ var removedKeys = actuallyRemoved.SelectMany(i => i.GetUserDataKeys()).ToHashSet();
+ foreach (var child in validChildren)
+ {
+ if (child.GetUserDataKeys().Any(removedKeys.Contains))
+ {
+ await child.ReattachUserDataAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
}
else
{
@@ -715,14 +731,21 @@ namespace MediaBrowser.Controller.Entities
}
else
{
- items = GetRecursiveChildren(user, query, out totalCount);
+ // Save pagination params before clearing them to prevent pagination from happening
+ // before sorting. PostFilterAndSort will apply pagination after sorting.
+ var limit = query.Limit;
+ var startIndex = query.StartIndex;
query.Limit = null;
- query.StartIndex = null; // override these here as they have already been applied
+ query.StartIndex = null;
+
+ items = GetRecursiveChildren(user, query, out totalCount);
+
+ // Restore pagination params so PostFilterAndSort can apply them after sorting
+ query.Limit = limit;
+ query.StartIndex = startIndex;
}
- var result = PostFilterAndSort(items, query);
- result.TotalRecordCount = totalCount;
- return result;
+ return PostFilterAndSort(items, query);
}
if (this is not UserRootFolder
@@ -980,25 +1003,19 @@ namespace MediaBrowser.Controller.Entities
else
{
// need to pass this param to the children.
+ // Note: Don't pass Limit/StartIndex here as pagination should happen after sorting in PostFilterAndSort
var childQuery = new InternalItemsQuery
{
DisplayAlbumFolders = query.DisplayAlbumFolders,
- Limit = query.Limit,
- StartIndex = query.StartIndex,
NameStartsWith = query.NameStartsWith,
NameStartsWithOrGreater = query.NameStartsWithOrGreater,
NameLessThan = query.NameLessThan
};
items = GetChildren(user, true, out totalItemCount, childQuery).Where(filter);
-
- query.Limit = null;
- query.StartIndex = null;
}
- var result = PostFilterAndSort(items, query);
- result.TotalRecordCount = totalItemCount;
- return result;
+ return PostFilterAndSort(items, query);
}
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
@@ -1034,7 +1051,15 @@ namespace MediaBrowser.Controller.Entities
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
- return UserViewBuilder.SortAndPage(items, null, query, LibraryManager);
+ var filteredItems = items as IReadOnlyList<BaseItem> ?? items.ToList();
+ var result = UserViewBuilder.SortAndPage(filteredItems, null, query, LibraryManager);
+
+ if (query.EnableTotalRecordCount)
+ {
+ result.TotalRecordCount = filteredItems.Count;
+ }
+
+ return result;
}
private static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(
@@ -1047,12 +1072,49 @@ namespace MediaBrowser.Controller.Entities
{
ArgumentNullException.ThrowIfNull(items);
- if (CollapseBoxSetItems(query, queryParent, user, configurationManager))
+ if (!CollapseBoxSetItems(query, queryParent, user, configurationManager))
+ {
+ return items;
+ }
+
+ var config = configurationManager.Configuration;
+
+ bool collapseMovies = config.EnableGroupingMoviesIntoCollections;
+ bool collapseSeries = config.EnableGroupingShowsIntoCollections;
+
+ if (user is null || (collapseMovies && collapseSeries))
+ {
+ return collectionManager.CollapseItemsWithinBoxSets(items, user);
+ }
+
+ if (!collapseMovies && !collapseSeries)
{
- items = collectionManager.CollapseItemsWithinBoxSets(items, user);
+ return items;
}
- return items;
+ var collapsibleItems = new List<BaseItem>();
+ var remainingItems = new List<BaseItem>();
+
+ foreach (var item in items)
+ {
+ if ((collapseMovies && item is Movie) || (collapseSeries && item is Series))
+ {
+ collapsibleItems.Add(item);
+ }
+ else
+ {
+ remainingItems.Add(item);
+ }
+ }
+
+ if (collapsibleItems.Count == 0)
+ {
+ return remainingItems;
+ }
+
+ var collapsedItems = collectionManager.CollapseItemsWithinBoxSets(collapsibleItems, user);
+
+ return collapsedItems.Concat(remainingItems);
}
private static bool CollapseBoxSetItems(
@@ -1083,24 +1145,26 @@ namespace MediaBrowser.Controller.Entities
}
var param = query.CollapseBoxSetItems;
-
- if (!param.HasValue)
+ if (param.HasValue)
{
- if (user is not null && query.IncludeItemTypes.Any(type =>
- (type == BaseItemKind.Movie && !configurationManager.Configuration.EnableGroupingMoviesIntoCollections) ||
- (type == BaseItemKind.Series && !configurationManager.Configuration.EnableGroupingShowsIntoCollections)))
- {
- return false;
- }
+ return param.Value && AllowBoxSetCollapsing(query);
+ }
- if (query.IncludeItemTypes.Length == 0
- || query.IncludeItemTypes.Any(type => type == BaseItemKind.Movie || type == BaseItemKind.Series))
- {
- param = true;
- }
+ var config = configurationManager.Configuration;
+
+ bool queryHasMovies = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Movie);
+ bool queryHasSeries = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Series);
+
+ bool collapseMovies = config.EnableGroupingMoviesIntoCollections;
+ bool collapseSeries = config.EnableGroupingShowsIntoCollections;
+
+ if (user is not null)
+ {
+ bool canCollapse = (queryHasMovies && collapseMovies) || (queryHasSeries && collapseSeries);
+ return canCollapse && AllowBoxSetCollapsing(query);
}
- return param.HasValue && param.Value && AllowBoxSetCollapsing(query);
+ return (queryHasMovies || queryHasSeries) && AllowBoxSetCollapsing(query);
}
private static bool AllowBoxSetCollapsing(InternalItemsQuery request)
@@ -1358,13 +1422,6 @@ namespace MediaBrowser.Controller.Entities
.Where(e => query is null || UserViewBuilder.FilterItem(e, query))
.ToArray();
- if (this is BoxSet && (query.OrderBy is null || query.OrderBy.Count == 0))
- {
- realChildren = realChildren
- .OrderBy(e => e.ProductionYear ?? int.MaxValue)
- .ToArray();
- }
-
var childCount = realChildren.Length;
if (result.Count < limit)
{
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index b32b64f5d..076a59292 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -125,6 +125,8 @@ namespace MediaBrowser.Controller.Entities
public string? Name { get; set; }
+ public bool? UseRawName { get; set; }
+
public string? Person { get; set; }
public Guid[] PersonIds { get; set; }
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 1d1fb2c39..3999c3e07 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -124,7 +124,7 @@ namespace MediaBrowser.Controller.Entities.Movies
if (sortBy == ItemSortBy.Default)
{
- return items;
+ return items;
}
return LibraryManager.Sort(items, user, new[] { sortBy }, SortOrder.Ascending);
@@ -136,6 +136,12 @@ namespace MediaBrowser.Controller.Entities.Movies
return Sort(children, user).ToArray();
}
+ public override IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, out int totalItemCount, InternalItemsQuery query = null)
+ {
+ var children = base.GetChildren(user, includeLinkedChildren, out totalItemCount, query);
+ return Sort(children, user).ToArray();
+ }
+
public override IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query, out int totalCount)
{
var children = base.GetRecursiveChildren(user, query, out totalCount);
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 427c2995b..6396631f9 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -214,7 +214,7 @@ namespace MediaBrowser.Controller.Entities.TV
query.AncestorWithPresentationUniqueKey = null;
query.SeriesPresentationUniqueKey = seriesKey;
query.IncludeItemTypes = new[] { BaseItemKind.Season };
- query.OrderBy = new[] { (ItemSortBy.IndexNumber, SortOrder.Ascending) };
+ query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
if (user is not null && !user.DisplayMissingEpisodes)
{
@@ -247,6 +247,10 @@ namespace MediaBrowser.Controller.Entities.TV
query.AncestorWithPresentationUniqueKey = null;
query.SeriesPresentationUniqueKey = seriesKey;
+ if (query.OrderBy.Count == 0)
+ {
+ query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
+ }
if (query.IncludeItemTypes.Length == 0)
{
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 4f9e9261b..bed7554b1 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -455,7 +455,7 @@ namespace MediaBrowser.Controller.Entities
var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();
var totalCount = itemsArray.Length;
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue && query.Limit.Value > 0)
{
itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray();
}