mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-24 02:02:29 -04:00
Backport pull request #12934 from jellyfin/release-10.10.z
Fix playlists Original-merge: 8bee67f1f8dab604d745b3d077330085f7f111d4 Merged-by: crobibero <cody@robibe.ro> Backported-by: Joshua M. Boniface <joshua@boniface.me>
This commit is contained in:
parent
27b044493a
commit
6e7118eff1
@ -17,7 +17,6 @@ namespace Emby.Server.Implementations
|
|||||||
{ DefaultRedirectKey, "web/" },
|
{ DefaultRedirectKey, "web/" },
|
||||||
{ FfmpegProbeSizeKey, "1G" },
|
{ FfmpegProbeSizeKey, "1G" },
|
||||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||||
{ PlaylistsAllowDuplicatesKey, bool.FalseString },
|
|
||||||
{ BindToUnixSocketKey, bool.FalseString },
|
{ BindToUnixSocketKey, bool.FalseString },
|
||||||
{ SqliteCacheSizeKey, "20000" },
|
{ SqliteCacheSizeKey, "20000" },
|
||||||
{ FfmpegSkipValidationKey, bool.FalseString },
|
{ FfmpegSkipValidationKey, bool.FalseString },
|
||||||
|
@ -216,14 +216,11 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
var newItems = GetPlaylistItems(newItemIds, user, options)
|
var newItems = GetPlaylistItems(newItemIds, user, options)
|
||||||
.Where(i => i.SupportsAddingToPlaylist);
|
.Where(i => i.SupportsAddingToPlaylist);
|
||||||
|
|
||||||
// Filter out duplicate items, if necessary
|
// Filter out duplicate items
|
||||||
if (!_appConfig.DoPlaylistsAllowDuplicates())
|
|
||||||
{
|
|
||||||
var existingIds = playlist.LinkedChildren.Select(c => c.ItemId).ToHashSet();
|
var existingIds = playlist.LinkedChildren.Select(c => c.ItemId).ToHashSet();
|
||||||
newItems = newItems
|
newItems = newItems
|
||||||
.Where(i => !existingIds.Contains(i.Id))
|
.Where(i => !existingIds.Contains(i.Id))
|
||||||
.Distinct();
|
.Distinct();
|
||||||
}
|
|
||||||
|
|
||||||
// Create a list of the new linked children to add to the playlist
|
// Create a list of the new linked children to add to the playlist
|
||||||
var childrenToAdd = newItems
|
var childrenToAdd = newItems
|
||||||
@ -269,7 +266,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
|
|
||||||
var idList = entryIds.ToList();
|
var idList = entryIds.ToList();
|
||||||
|
|
||||||
var removals = children.Where(i => idList.Contains(i.Item1.Id));
|
var removals = children.Where(i => idList.Contains(i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture)));
|
||||||
|
|
||||||
playlist.LinkedChildren = children.Except(removals)
|
playlist.LinkedChildren = children.Except(removals)
|
||||||
.Select(i => i.Item1)
|
.Select(i => i.Item1)
|
||||||
@ -286,26 +283,39 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
RefreshPriority.High);
|
RefreshPriority.High);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MoveItemAsync(string playlistId, string entryId, int newIndex)
|
public async Task MoveItemAsync(string playlistId, string entryId, int newIndex, Guid callingUserId)
|
||||||
{
|
{
|
||||||
if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
|
if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("No Playlist exists with the supplied Id");
|
throw new ArgumentException("No Playlist exists with the supplied Id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var user = _userManager.GetUserById(callingUserId);
|
||||||
var children = playlist.GetManageableItems().ToList();
|
var children = playlist.GetManageableItems().ToList();
|
||||||
|
var accessibleChildren = children.Where(c => c.Item2.IsVisible(user)).ToArray();
|
||||||
|
|
||||||
var oldIndex = children.FindIndex(i => string.Equals(entryId, i.Item1.Id, StringComparison.OrdinalIgnoreCase));
|
var oldIndexAll = children.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase));
|
||||||
|
var oldIndexAccessible = accessibleChildren.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (oldIndex == newIndex)
|
if (oldIndexAccessible == newIndex)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = playlist.LinkedChildren[oldIndex];
|
var newPriorItemIndex = newIndex > oldIndexAccessible ? newIndex : newIndex - 1 < 0 ? 0 : newIndex - 1;
|
||||||
|
var newPriorItemId = accessibleChildren[newPriorItemIndex].Item1.ItemId;
|
||||||
|
var newPriorItemIndexOnAllChildren = children.FindIndex(c => c.Item1.ItemId.Equals(newPriorItemId));
|
||||||
|
var adjustedNewIndex = newPriorItemIndexOnAllChildren + 1;
|
||||||
|
|
||||||
|
var item = playlist.LinkedChildren.FirstOrDefault(i => string.Equals(entryId, i.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Modified item not found in playlist. ItemId: {ItemId}, PlaylistId: {PlaylistId}", item.ItemId, playlistId);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var newList = playlist.LinkedChildren.ToList();
|
var newList = playlist.LinkedChildren.ToList();
|
||||||
|
|
||||||
newList.Remove(item);
|
newList.Remove(item);
|
||||||
|
|
||||||
if (newIndex >= newList.Count)
|
if (newIndex >= newList.Count)
|
||||||
@ -314,7 +324,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
newList.Insert(newIndex, item);
|
newList.Insert(adjustedNewIndex, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist.LinkedChildren = [.. newList];
|
playlist.LinkedChildren = [.. newList];
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Api.Attributes;
|
using Jellyfin.Api.Attributes;
|
||||||
@ -426,7 +427,7 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false);
|
await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex, callingUserId).ConfigureAwait(false);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,7 +515,8 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = playlist.GetManageableItems().ToArray();
|
var user = _userManager.GetUserById(callingUserId);
|
||||||
|
var items = playlist.GetManageableItems().Where(i => i.Item2.IsVisible(user)).ToArray();
|
||||||
var count = items.Length;
|
var count = items.Length;
|
||||||
if (startIndex.HasValue)
|
if (startIndex.HasValue)
|
||||||
{
|
{
|
||||||
@ -529,11 +531,11 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
.AddClientFields(User)
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
var user = _userManager.GetUserById(callingUserId);
|
|
||||||
var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
|
var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
|
||||||
for (int index = 0; index < dtos.Count; index++)
|
for (int index = 0; index < dtos.Count; index++)
|
||||||
{
|
{
|
||||||
dtos[index].PlaylistItemId = items[index].Item1.Id;
|
dtos[index].PlaylistItemId = items[index].Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new QueryResult<BaseItemDto>(
|
var result = new QueryResult<BaseItemDto>(
|
||||||
|
@ -47,7 +47,8 @@ namespace Jellyfin.Server.Migrations
|
|||||||
typeof(Routines.AddDefaultCastReceivers),
|
typeof(Routines.AddDefaultCastReceivers),
|
||||||
typeof(Routines.UpdateDefaultPluginRepository),
|
typeof(Routines.UpdateDefaultPluginRepository),
|
||||||
typeof(Routines.FixAudioData),
|
typeof(Routines.FixAudioData),
|
||||||
typeof(Routines.MoveTrickplayFiles)
|
typeof(Routines.MoveTrickplayFiles),
|
||||||
|
typeof(Routines.RemoveDuplicatePlaylistChildren)
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -15,12 +15,12 @@ namespace Jellyfin.Server.Migrations.Routines;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class FixPlaylistOwner : IMigrationRoutine
|
internal class FixPlaylistOwner : IMigrationRoutine
|
||||||
{
|
{
|
||||||
private readonly ILogger<RemoveDuplicateExtras> _logger;
|
private readonly ILogger<FixPlaylistOwner> _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IPlaylistManager _playlistManager;
|
private readonly IPlaylistManager _playlistManager;
|
||||||
|
|
||||||
public FixPlaylistOwner(
|
public FixPlaylistOwner(
|
||||||
ILogger<RemoveDuplicateExtras> logger,
|
ILogger<FixPlaylistOwner> logger,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IPlaylistManager playlistManager)
|
IPlaylistManager playlistManager)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Playlists;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Migrations.Routines;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove duplicate playlist entries.
|
||||||
|
/// </summary>
|
||||||
|
internal class RemoveDuplicatePlaylistChildren : IMigrationRoutine
|
||||||
|
{
|
||||||
|
private readonly ILogger<RemoveDuplicatePlaylistChildren> _logger;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IPlaylistManager _playlistManager;
|
||||||
|
|
||||||
|
public RemoveDuplicatePlaylistChildren(
|
||||||
|
ILogger<RemoveDuplicatePlaylistChildren> logger,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
IPlaylistManager playlistManager)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_playlistManager = playlistManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Guid Id => Guid.Parse("{96C156A2-7A13-4B3B-A8B8-FB80C94D20C0}");
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string Name => "RemoveDuplicatePlaylistChildren";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool PerformOnNewInstall => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Perform()
|
||||||
|
{
|
||||||
|
var playlists = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
|
{
|
||||||
|
IncludeItemTypes = [BaseItemKind.Playlist]
|
||||||
|
})
|
||||||
|
.Cast<Playlist>()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (playlists.Length > 0)
|
||||||
|
{
|
||||||
|
foreach (var playlist in playlists)
|
||||||
|
{
|
||||||
|
var linkedChildren = playlist.LinkedChildren;
|
||||||
|
if (linkedChildren.Length > 0)
|
||||||
|
{
|
||||||
|
var nullItemChildren = linkedChildren.Where(c => c.ItemId is null);
|
||||||
|
var deduplicatedChildren = linkedChildren.DistinctBy(c => c.ItemId);
|
||||||
|
var newLinkedChildren = nullItemChildren.Concat(deduplicatedChildren);
|
||||||
|
playlist.LinkedChildren = linkedChildren;
|
||||||
|
playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
_playlistManager.SavePlaylistFile(playlist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
@ -12,7 +11,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
public LinkedChild()
|
public LinkedChild()
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
@ -21,9 +19,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
public string LibraryItemId { get; set; }
|
public string LibraryItemId { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the linked item id.
|
/// Gets or sets the linked item id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -31,6 +26,8 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
public static LinkedChild Create(BaseItem item)
|
public static LinkedChild Create(BaseItem item)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
var child = new LinkedChild
|
var child = new LinkedChild
|
||||||
{
|
{
|
||||||
Path = item.Path,
|
Path = item.Path,
|
||||||
|
@ -49,11 +49,6 @@ namespace MediaBrowser.Controller.Extensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string FfmpegPathKey = "ffmpeg";
|
public const string FfmpegPathKey = "ffmpeg";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key for a setting that indicates whether playlists should allow duplicate entries.
|
|
||||||
/// </summary>
|
|
||||||
public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The key for a setting that indicates whether kestrel should bind to a unix socket.
|
/// The key for a setting that indicates whether kestrel should bind to a unix socket.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -120,14 +115,6 @@ namespace MediaBrowser.Controller.Extensions
|
|||||||
public static bool GetFFmpegImgExtractPerfTradeoff(this IConfiguration configuration)
|
public static bool GetFFmpegImgExtractPerfTradeoff(this IConfiguration configuration)
|
||||||
=> configuration.GetValue<bool>(FfmpegImgExtractPerfTradeoffKey);
|
=> configuration.GetValue<bool>(FfmpegImgExtractPerfTradeoffKey);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether playlists should allow duplicate entries from the <see cref="IConfiguration"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configuration">The configuration to read the setting from.</param>
|
|
||||||
/// <returns>True if playlists should allow duplicates, otherwise false.</returns>
|
|
||||||
public static bool DoPlaylistsAllowDuplicates(this IConfiguration configuration)
|
|
||||||
=> configuration.GetValue<bool>(PlaylistsAllowDuplicatesKey);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether kestrel should bind to a unix socket from the <see cref="IConfiguration" />.
|
/// Gets a value indicating whether kestrel should bind to a unix socket from the <see cref="IConfiguration" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -92,8 +92,9 @@ namespace MediaBrowser.Controller.Playlists
|
|||||||
/// <param name="playlistId">The playlist identifier.</param>
|
/// <param name="playlistId">The playlist identifier.</param>
|
||||||
/// <param name="entryId">The entry identifier.</param>
|
/// <param name="entryId">The entry identifier.</param>
|
||||||
/// <param name="newIndex">The new index.</param>
|
/// <param name="newIndex">The new index.</param>
|
||||||
|
/// <param name="callingUserId">The calling user.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task MoveItemAsync(string playlistId, string entryId, int newIndex);
|
Task MoveItemAsync(string playlistId, string entryId, int newIndex, Guid callingUserId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removed all playlists of a user.
|
/// Removed all playlists of a user.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user