diff options
Diffstat (limited to 'Emby.Server.Implementations')
45 files changed, 360 insertions, 252 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 745753440..6add7e0b3 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -109,13 +109,13 @@ namespace Emby.Server.Implementations /// <summary> /// The disposable parts. /// </summary> - private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new(); + private readonly ConcurrentBag<IDisposable> _disposableParts = new(); private readonly DeviceId _deviceId; private readonly IConfiguration _startupConfig; private readonly IXmlSerializer _xmlSerializer; private readonly IStartupOptions _startupOptions; - private readonly IPluginManager _pluginManager; + private readonly PluginManager _pluginManager; private List<Type> _creatingInstances; @@ -146,7 +146,7 @@ namespace Emby.Server.Implementations _startupConfig = startupConfig; Logger = LoggerFactory.CreateLogger<ApplicationHost>(); - _deviceId = new DeviceId(ApplicationPaths, LoggerFactory); + _deviceId = new DeviceId(ApplicationPaths, LoggerFactory.CreateLogger<DeviceId>()); ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; ApplicationVersionString = ApplicationVersion.ToString(3); @@ -161,7 +161,7 @@ namespace Emby.Server.Implementations ApplicationPaths.PluginsPath, ApplicationVersion); - _disposableParts.TryAdd((PluginManager)_pluginManager, byte.MinValue); + _disposableParts.Add(_pluginManager); } /// <summary> @@ -360,7 +360,7 @@ namespace Emby.Server.Implementations { foreach (var part in parts.OfType<IDisposable>()) { - _disposableParts.TryAdd(part, byte.MinValue); + _disposableParts.Add(part); } } @@ -381,7 +381,7 @@ namespace Emby.Server.Implementations { foreach (var part in parts.OfType<IDisposable>()) { - _disposableParts.TryAdd(part, byte.MinValue); + _disposableParts.Add(part); } } @@ -457,7 +457,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager); serviceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager); serviceCollection.AddSingleton<IApplicationHost>(this); - serviceCollection.AddSingleton(_pluginManager); + serviceCollection.AddSingleton<IPluginManager>(_pluginManager); serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths); serviceCollection.AddSingleton<IFileSystem, ManagedFileSystem>(); @@ -965,7 +965,7 @@ namespace Emby.Server.Implementations Logger.LogInformation("Disposing {Type}", type.Name); - foreach (var (part, _) in _disposableParts) + foreach (var part in _disposableParts.ToArray()) { var partType = part.GetType(); if (partType == type) diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index b34d0f21e..e414792ba 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Collections var name = _localizationManager.GetLocalizedString("Collections"); - await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.BoxSets, libraryOptions, true).ConfigureAwait(false); + await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.boxsets, libraryOptions, true).ConfigureAwait(false); return FindFolders(path).First(); } diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index bf079d90c..b1c99227c 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -186,10 +186,7 @@ namespace Emby.Server.Implementations.Data protected void CheckDisposed() { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name, "Object has been disposed and cannot be accessed."); - } + ObjectDisposedException.ThrowIf(_disposed, this); } /// <inheritdoc /> diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index a6336f145..59e4ff1a9 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -205,7 +205,7 @@ namespace Emby.Server.Implementations.Data private static readonly string _mediaAttachmentSaveColumnsSelectQuery = $"select {string.Join(',', _mediaAttachmentSaveColumns)} from mediaattachments where ItemId=@ItemId"; - private static readonly string _mediaAttachmentInsertPrefix; + private static readonly string _mediaAttachmentInsertPrefix = BuildMediaAttachmentInsertPrefix(); private static readonly BaseItemKind[] _programTypes = new[] { @@ -296,21 +296,6 @@ namespace Emby.Server.Implementations.Data { BaseItemKind.Year, typeof(Year).FullName } }; - static SqliteItemRepository() - { - var queryPrefixText = new StringBuilder(); - queryPrefixText.Append("insert into mediaattachments ("); - foreach (var column in _mediaAttachmentSaveColumns) - { - queryPrefixText.Append(column) - .Append(','); - } - - queryPrefixText.Length -= 1; - queryPrefixText.Append(") values "); - _mediaAttachmentInsertPrefix = queryPrefixText.ToString(); - } - /// <summary> /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class. /// </summary> @@ -5879,6 +5864,21 @@ AND Type = @InternalPersonType)"); return item; } + private static string BuildMediaAttachmentInsertPrefix() + { + var queryPrefixText = new StringBuilder(); + queryPrefixText.Append("insert into mediaattachments ("); + foreach (var column in _mediaAttachmentSaveColumns) + { + queryPrefixText.Append(column) + .Append(','); + } + + queryPrefixText.Length -= 1; + queryPrefixText.Append(") values "); + return queryPrefixText.ToString(); + } + #nullable enable private readonly struct QueryTimeLogger : IDisposable diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index a5edcc58c..20359e4ad 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -58,7 +58,8 @@ namespace Emby.Server.Implementations.Data "create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)", "create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)", "create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)", - "create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)")); + "create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)", + "create index if not exists UserDatasIndex5 on UserDatas (key, userId, lastPlayedDate)")); if (!userDataTableExists) { diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index b3f5549bc..2459178d8 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -17,19 +15,19 @@ namespace Emby.Server.Implementations.Devices private readonly ILogger<DeviceId> _logger; private readonly object _syncLock = new object(); - private string _id; + private string? _id; - public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory) + public DeviceId(IApplicationPaths appPaths, ILogger<DeviceId> logger) { _appPaths = appPaths; - _logger = loggerFactory.CreateLogger<DeviceId>(); + _logger = logger; } - public string Value => _id ?? (_id = GetDeviceId()); + public string Value => _id ??= GetDeviceId(); private string CachePath => Path.Combine(_appPaths.DataPath, "device.txt"); - private string GetCachedId() + private string? GetCachedId() { try { @@ -65,7 +63,7 @@ namespace Emby.Server.Implementations.Devices { var path = CachePath; - Directory.CreateDirectory(Path.GetDirectoryName(path)); + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Path can't be a root directory.")); lock (_syncLock) { diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 7812687ea..98eacb52b 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -668,12 +668,13 @@ namespace Emby.Server.Implementations.Dto { dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>(); - if (!dto.ImageBlurHashes.ContainsKey(image.Type)) + if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value)) { - dto.ImageBlurHashes[image.Type] = new Dictionary<string, string>(); + value = new Dictionary<string, string>(); + dto.ImageBlurHashes[image.Type] = value; } - dto.ImageBlurHashes[image.Type][tag] = image.BlurHash; + value[tag] = image.BlurHash; } return tag; @@ -903,10 +904,7 @@ namespace Emby.Server.Implementations.Dto if (item is Audio audio) { dto.Album = audio.Album; - if (audio.ExtraType.HasValue) - { - dto.ExtraType = audio.ExtraType.Value.ToString(); - } + dto.ExtraType = audio.ExtraType; var albumParent = audio.AlbumEntity; @@ -1058,10 +1056,7 @@ namespace Emby.Server.Implementations.Dto dto.Trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult(); } - if (video.ExtraType.HasValue) - { - dto.ExtraType = video.ExtraType.Value.ToString(); - } + dto.ExtraType = video.ExtraType; } if (options.ContainsField(ItemFields.MediaStreams)) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 52f14b0b1..774d3563c 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.HttpServer WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); - using var connection = new WebSocketConnection( + var connection = new WebSocketConnection( _loggerFactory.CreateLogger<WebSocketConnection>(), webSocket, authorizationInfo, @@ -56,17 +56,19 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived }; - - var tasks = new Task[_webSocketListeners.Length]; - for (var i = 0; i < _webSocketListeners.Length; ++i) + await using (connection.ConfigureAwait(false)) { - tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context); - } + var tasks = new Task[_webSocketListeners.Length]; + for (var i = 0; i < _webSocketListeners.Length; ++i) + { + tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context); + } - await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); - await connection.ReceiveAsync().ConfigureAwait(false); - _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); + await connection.ReceiveAsync().ConfigureAwait(false); + _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); + } } catch (Exception ex) // Otherwise ASP.Net will ignore the exception { diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index c380d67db..67854a2a7 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Security; using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; @@ -643,7 +644,15 @@ namespace Emby.Server.Implementations.IO /// <inheritdoc /> public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false) { - return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive)); + try + { + return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive)); + } + catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException or SecurityException) + { + _logger.LogError(ex, "Failed to enumerate path {Path}", path); + return Enumerable.Empty<string>(); + } } /// <inheritdoc /> diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index a2abafd2a..bb5cc746e 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -338,7 +338,7 @@ namespace Emby.Server.Implementations.Library if (item is LiveTvProgram) { _logger.LogDebug( - "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + "Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -347,7 +347,7 @@ namespace Emby.Server.Implementations.Library else { _logger.LogInformation( - "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + "Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -366,7 +366,7 @@ namespace Emby.Server.Implementations.Library } _logger.LogDebug( - "Deleting metadata path, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + "Deleting metadata path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", item.GetType().Name, item.Name ?? "Unknown name", metadataPath, @@ -395,7 +395,7 @@ namespace Emby.Server.Implementations.Library try { _logger.LogInformation( - "Deleting item path, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + "Deleting item path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", item.GetType().Name, item.Name ?? "Unknown name", fileSystemInfo.FullName, @@ -410,6 +410,24 @@ namespace Emby.Server.Implementations.Library File.Delete(fileSystemInfo.FullName); } } + catch (DirectoryNotFoundException) + { + _logger.LogInformation( + "Directory not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + fileSystemInfo.FullName, + item.Id); + } + catch (FileNotFoundException) + { + _logger.LogInformation( + "File not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + fileSystemInfo.FullName, + item.Id); + } catch (IOException) { if (isRequiredForDelete) @@ -443,7 +461,7 @@ namespace Emby.Server.Implementations.Library ReportItemRemoved(item, parent); } - private static IEnumerable<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children) + private static List<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children) { var list = new List<string> { @@ -2677,7 +2695,12 @@ namespace Emby.Server.Implementations.Library extra = itemById; } - extra.ExtraType = extraType; + // Only update extra type if it is more specific then the currently known extra type + if (extra.ExtraType is null or ExtraType.Unknown || extraType != ExtraType.Unknown) + { + extra.ExtraType = extraType; + } + extra.ParentId = Guid.Empty; extra.OwnerId = owner.Id; return extra; diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index d4aeae41a..0ebfe3ae7 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -37,16 +35,16 @@ namespace Emby.Server.Implementations.Library _appPaths = appPaths; } - public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, CancellationToken cancellationToken) + public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string? cacheKey, bool addProbeDelay, CancellationToken cancellationToken) { var originalRuntime = mediaSource.RunTimeTicks; var now = DateTime.UtcNow; - MediaInfo mediaInfo = null; + MediaInfo? mediaInfo = null; var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json"); - if (!string.IsNullOrEmpty(cacheKey)) + if (cacheFilePath is not null) { try { @@ -91,7 +89,7 @@ namespace Emby.Server.Implementations.Library if (cacheFilePath is not null) { - Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath) ?? throw new InvalidOperationException("Path can't be a root directory.")); FileStream createStream = AsyncFile.OpenWrite(cacheFilePath); await using (createStream.ConfigureAwait(false)) { diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 18ada6aeb..9658bd566 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.Library if (user is not null) { - SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); + SetDefaultAudioAndSubtitleStreamIndices(item, source, user); if (item.MediaType == MediaType.Audio) { @@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.Library catch (Exception ex) { _logger.LogError(ex, "Error getting media sources"); - return Enumerable.Empty<MediaSourceInfo>(); + return []; } } @@ -339,7 +339,7 @@ namespace Emby.Server.Implementations.Library { foreach (var source in sources) { - SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); + SetDefaultAudioAndSubtitleStreamIndices(item, source, user); if (item.MediaType == MediaType.Audio) { @@ -360,7 +360,7 @@ namespace Emby.Server.Implementations.Library { if (string.IsNullOrEmpty(language)) { - return Array.Empty<string>(); + return []; } var culture = _localizationManager.FindLanguageInfo(language); @@ -369,14 +369,15 @@ namespace Emby.Server.Implementations.Library return culture.ThreeLetterISOLanguageNames; } - return new string[] { language }; + return [language]; } private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) { if (userData.SubtitleStreamIndex.HasValue && user.RememberSubtitleSelections - && user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) + && user.SubtitleMode != SubtitlePlaybackMode.None + && allowRememberingSelection) { var index = userData.SubtitleStreamIndex.Value; // Make sure the saved index is still valid @@ -390,7 +391,7 @@ namespace Emby.Server.Implementations.Library var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; - var audioLangage = defaultAudioIndex is null + var audioLanguage = defaultAudioIndex is null ? null : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); @@ -398,9 +399,9 @@ namespace Emby.Server.Implementations.Library source.MediaStreams, preferredSubs, user.SubtitleMode, - audioLangage); + audioLanguage); - MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage); + MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLanguage); } private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) @@ -421,7 +422,7 @@ namespace Emby.Server.Implementations.Library source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); } - public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) + public void SetDefaultAudioAndSubtitleStreamIndices(BaseItem item, MediaSourceInfo source, User user) { // Item would only be null if the app didn't supply ItemId as part of the live stream open request var mediaType = item?.MediaType ?? MediaType.Video; @@ -526,7 +527,7 @@ namespace Emby.Server.Implementations.Library var item = request.ItemId.IsEmpty() ? null : _libraryManager.GetItemById(request.ItemId); - SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); + SetDefaultAudioAndSubtitleStreamIndices(item, clone, user); } return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider); diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 6aef87c52..ea223e3ec 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -124,16 +124,16 @@ namespace Emby.Server.Implementations.Library } else if (mode == SubtitlePlaybackMode.Always) { - // always load the most suitable full subtitles + // Always load the most suitable full subtitles filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList(); } else if (mode == SubtitlePlaybackMode.OnlyForced) { - // always load the most suitable full subtitles + // Always load the most suitable full subtitles filteredStreams = sortedStreams.Where(s => s.IsForced).ToList(); } - // load forced subs if we have found no suitable full subtitles + // Load forced subs if we have found no suitable full subtitles var iterStreams = filteredStreams is null || filteredStreams.Count == 0 ? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) : filteredStreams; diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 078f4ad21..a69a0f33f 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -13,7 +11,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; -using MediaBrowser.Model.Querying; using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; namespace Emby.Server.Implementations.Library @@ -27,33 +24,35 @@ namespace Emby.Server.Implementations.Library _libraryManager = libraryManager; } - public List<BaseItem> GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromSong(Audio item, User? user, DtoOptions dtoOptions) { - var list = new List<Audio> + var list = new List<BaseItem> { item }; - return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList(); + list.AddRange(GetInstantMixFromGenres(item.Genres, user, dtoOptions)); + + return list; } /// <inheritdoc /> - public List<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User? user, DtoOptions dtoOptions) { return GetInstantMixFromGenres(artist.Genres, user, dtoOptions); } - public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User? user, DtoOptions dtoOptions) { return GetInstantMixFromGenres(item.Genres, user, dtoOptions); } - public List<BaseItem> GetInstantMixFromFolder(Folder item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromFolder(Folder item, User? user, DtoOptions dtoOptions) { var genres = item .GetRecursiveChildren(user, new InternalItemsQuery(user) { - IncludeItemTypes = new[] { BaseItemKind.Audio }, + IncludeItemTypes = [BaseItemKind.Audio], DtoOptions = dtoOptions }) .Cast<Audio>() @@ -64,12 +63,12 @@ namespace Emby.Server.Implementations.Library return GetInstantMixFromGenres(genres, user, dtoOptions); } - public List<BaseItem> GetInstantMixFromPlaylist(Playlist item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromPlaylist(Playlist item, User? user, DtoOptions dtoOptions) { return GetInstantMixFromGenres(item.Genres, user, dtoOptions); } - public List<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User? user, DtoOptions dtoOptions) { var genreIds = genres.DistinctNames().Select(i => { @@ -86,27 +85,23 @@ namespace Emby.Server.Implementations.Library return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); } - public List<BaseItem> GetInstantMixFromGenreIds(Guid[] genreIds, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromGenreIds(Guid[] genreIds, User? user, DtoOptions dtoOptions) { return _libraryManager.GetItemList(new InternalItemsQuery(user) { - IncludeItemTypes = new[] { BaseItemKind.Audio }, - - GenreIds = genreIds.ToArray(), - + IncludeItemTypes = [BaseItemKind.Audio], + GenreIds = genreIds, Limit = 200, - - OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, - + OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)], DtoOptions = dtoOptions }); } - public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromItem(BaseItem item, User? user, DtoOptions dtoOptions) { if (item is MusicGenre) { - return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions); + return GetInstantMixFromGenreIds([item.Id], user, dtoOptions); } if (item is Playlist playlist) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index c4b6b3756..21e7079d8 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -31,8 +31,9 @@ namespace Emby.Server.Implementations.Library var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase); - // Must be at least 3 characters after the attribute =, ], any character. - var maxIndex = str.Length - attribute.Length - 3; + // Must be at least 3 characters after the attribute =, ], any character, + // then we offset it by 1, because we want the index and not length. + var maxIndex = str.Length - attribute.Length - 2; while (attributeIndex > -1 && attributeIndex < maxIndex) { var attributeEnd = attributeIndex + attribute.Length; diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 7a61e2607..52be76217 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.Library item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); - item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || + item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) || item.GetParents().Any(i => i.IsLocked); // Make sure DateCreated and DateModified have values diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index 6cc04ea81..955055313 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - if (filename.IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1 || args.ContainsFileSystemEntryByName("collection.xml")) + if (filename.Contains("[boxset]", StringComparison.OrdinalIgnoreCase) || args.ContainsFileSystemEntryByName("collection.xml")) { return new BoxSet { diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index a50435ae6..a03c1214d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.IO; using System.Linq; @@ -11,7 +9,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Resolvers; using MediaBrowser.LocalMetadata.Savers; -using MediaBrowser.Model.Entities; namespace Emby.Server.Implementations.Library.Resolvers { @@ -20,11 +17,11 @@ namespace Emby.Server.Implementations.Library.Resolvers /// </summary> public class PlaylistResolver : GenericFolderResolver<Playlist> { - private CollectionType?[] _musicPlaylistCollectionTypes = - { + private readonly CollectionType?[] _musicPlaylistCollectionTypes = + [ null, CollectionType.music - }; + ]; /// <inheritdoc/> protected override Playlist Resolve(ItemResolveArgs args) diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 020cb517d..7f3f8615e 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -29,7 +27,7 @@ namespace Emby.Server.Implementations.Library public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query) { - User user = null; + User? user = null; if (!query.UserId.IsEmpty()) { user = _userManager.GetUserById(query.UserId); @@ -69,7 +67,7 @@ namespace Emby.Server.Implementations.Library /// <param name="user">The user.</param> /// <returns>IEnumerable{SearchHintResult}.</returns> /// <exception cref="ArgumentException"><c>query.SearchTerm</c> is <c>null</c> or empty.</exception> - private List<SearchHintInfo> GetSearchHints(SearchQuery query, User user) + private List<SearchHintInfo> GetSearchHints(SearchQuery query, User? user) { var searchTerm = query.SearchTerm; @@ -78,7 +76,7 @@ namespace Emby.Server.Implementations.Library searchTerm = searchTerm.Trim().RemoveDiacritics(); var excludeItemTypes = query.ExcludeItemTypes.ToList(); - var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<BaseItemKind>()).ToList(); + var includeItemTypes = query.IncludeItemTypes.ToList(); excludeItemTypes.Add(BaseItemKind.Year); excludeItemTypes.Add(BaseItemKind.Folder); @@ -179,7 +177,7 @@ namespace Emby.Server.Implementations.Library { if (!searchQuery.ParentId.IsEmpty()) { - searchQuery.AncestorIds = new[] { searchQuery.ParentId }; + searchQuery.AncestorIds = [searchQuery.ParentId]; searchQuery.ParentId = Guid.Empty; } diff --git a/Emby.Server.Implementations/Localization/Core/be.json b/Emby.Server.Implementations/Localization/Core/be.json index 05af8d8a5..77643505e 100644 --- a/Emby.Server.Implementations/Localization/Core/be.json +++ b/Emby.Server.Implementations/Localization/Core/be.json @@ -125,5 +125,7 @@ "TaskDownloadMissingSubtitles": "Спампаваць адсутныя субтытры", "TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных спісаў прайгравання HLS. Гэта задача можа працаваць у працягу доўгага часу.", "TaskRefreshTrickplayImages": "Стварыце выявы Trickplay", - "TaskRefreshTrickplayImagesDescription": "Стварае прагляд відэаролікаў для Trickplay у падключаных бібліятэках." + "TaskRefreshTrickplayImagesDescription": "Стварае прагляд відэаролікаў для Trickplay у падключаных бібліятэках.", + "TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і спісы прайгравання", + "TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і спісаў прайгравання, якія больш не існуюць." } diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index c4d8c6947..b7633f77c 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -126,5 +126,7 @@ "External": "Extern", "HearingImpaired": "Discapacitat auditiva", "TaskRefreshTrickplayImages": "Generar miniatures de línia de temps", - "TaskRefreshTrickplayImagesDescription": "Crear miniatures de línia de temps per vídeos en les biblioteques habilitades." + "TaskRefreshTrickplayImagesDescription": "Crear miniatures de línia de temps per vídeos en les biblioteques habilitades.", + "TaskCleanCollectionsAndPlaylistsDescription": "Esborra elements de col·leccions i llistes de reproducció que ja no existeixen.", + "TaskCleanCollectionsAndPlaylists": "Neteja col·leccions i llistes de reproducció" } diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 1c7bc75b5..2fa1c19e3 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -126,5 +126,7 @@ "External": "Externí", "HearingImpaired": "Sluchově postižení", "TaskRefreshTrickplayImages": "Generovat obrázky pro Trickplay", - "TaskRefreshTrickplayImagesDescription": "Obrázky Trickplay se používají k zobrazení náhledů u videí v knihovnách, kde je to povoleno." + "TaskRefreshTrickplayImagesDescription": "Obrázky Trickplay se používají k zobrazení náhledů u videí v knihovnách, kde je to povoleno.", + "TaskCleanCollectionsAndPlaylists": "Pročistit kolekce a seznamy přehrávání", + "TaskCleanCollectionsAndPlaylistsDescription": "Odstraní neexistující položky z kolekcí a seznamů přehrávání." } diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 092af34b6..b5e2c9b6b 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -126,5 +126,7 @@ "External": "Ekstern", "HearingImpaired": "Hørehæmmet", "TaskRefreshTrickplayImages": "Generér Trickplay Billeder", - "TaskRefreshTrickplayImagesDescription": "Laver trickplay forhåndsvisninger for videoer i aktiverede biblioteker." + "TaskRefreshTrickplayImagesDescription": "Laver trickplay forhåndsvisninger for videoer i aktiverede biblioteker.", + "TaskCleanCollectionsAndPlaylists": "Ryd op i samlinger og afspilningslister", + "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner enheder fra samlinger og afspilningslister der ikke eksisterer længere." } diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 7a4c2067b..d8b2f828f 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -126,5 +126,7 @@ "External": "Extern", "HearingImpaired": "Hörgeschädigt", "TaskRefreshTrickplayImages": "Trickplay-Bilder generieren", - "TaskRefreshTrickplayImagesDescription": "Erstellt eine Trickplay-Vorschau für Videos in aktivierten Bibliotheken." + "TaskRefreshTrickplayImagesDescription": "Erstellt eine Trickplay-Vorschau für Videos in aktivierten Bibliotheken.", + "TaskCleanCollectionsAndPlaylists": "Sammlungen und Playlisten aufräumen", + "TaskCleanCollectionsAndPlaylistsDescription": "Lösche nicht mehr vorhandene Einträge aus den Sammlungen und Playlisten." } diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 32bf89310..ff0c3d23d 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -126,5 +126,7 @@ "External": "External", "HearingImpaired": "Hearing Impaired", "TaskRefreshTrickplayImages": "Generate Trickplay Images", - "TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries." + "TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries.", + "TaskCleanCollectionsAndPlaylists": "Clean up collections and playlists", + "TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist." } diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index 496ecabd3..4ba31bee0 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -125,5 +125,7 @@ "TaskOptimizeDatabase": "Optimize database", "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.", "TaskKeyframeExtractor": "Keyframe Extractor", - "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time." + "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.", + "TaskCleanCollectionsAndPlaylists": "Clean up collections and playlists", + "TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist." } diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index fe10be308..91e29d926 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -30,7 +30,7 @@ "ItemAddedWithName": "{0} se ha añadido a la biblioteca", "ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca", "LabelIpAddressValue": "Dirección IP: {0}", - "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", + "LabelRunningTimeValue": "Duración: {0}", "Latest": "Últimas", "MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin", "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}", diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index e0aff7954..db83d4b47 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -1,6 +1,6 @@ { "Albums": "Albums", - "AppDeviceValues": "Application : {0}, Appareil : {1}", + "AppDeviceValues": "Application : {0}, Appareil : {1}", "Application": "Application", "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", @@ -29,7 +29,7 @@ "Inherit": "Hériter", "ItemAddedWithName": "{0} a été ajouté à la médiathèque", "ItemRemovedWithName": "{0} a été supprimé de la médiathèque", - "LabelIpAddressValue": "Adresse IP : {0}", + "LabelIpAddressValue": "Adresse IP : {0}", "LabelRunningTimeValue": "Durée : {0}", "Latest": "Derniers", "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour", @@ -126,5 +126,7 @@ "External": "Externe", "HearingImpaired": "Malentendants", "TaskRefreshTrickplayImages": "Générer des images Trickplay", - "TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées." + "TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées.", + "TaskCleanCollectionsAndPlaylists": "Nettoyer les collections et les listes de lecture", + "TaskCleanCollectionsAndPlaylistsDescription": "Supprime les éléments des collections et des listes de lecture qui n'existent plus." } diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json index 3f4dea523..a28352219 100644 --- a/Emby.Server.Implementations/Localization/Core/hi.json +++ b/Emby.Server.Implementations/Localization/Core/hi.json @@ -4,27 +4,27 @@ "HeaderNextUp": "इसके बाद", "HeaderLiveTV": "लाइव टीवी", "HeaderFavoriteSongs": "पसंदीदा गीत", - "HeaderFavoriteShows": "पसंदीदा शोज", - "HeaderFavoriteEpisodes": "पसंदीदा एपिसोड्स", - "HeaderFavoriteArtists": "पसंदीदा कलाकारसमूह", + "HeaderFavoriteShows": "पसंदीदा शो", + "HeaderFavoriteEpisodes": "पसंदीदा प्रकरण", + "HeaderFavoriteArtists": "पसंदीदा कलाकार", "HeaderFavoriteAlbums": "पसंदीदा एलबम्स", - "HeaderContinueWatching": "देखते रहिए", + "HeaderContinueWatching": "देखना जारी रखें", "HeaderAlbumArtists": "एल्बम कलाकार", - "Genres": "शैली", + "Genres": "शैलियां", "Forced": "बलपूर्वक", - "Folders": "फ़ोल्डरें", + "Folders": "फ़ोल्डर", "Favorites": "पसंदीदा", "FailedLoginAttemptWithUserName": "{0} से लॉगिन असफल हुआ", - "DeviceOnlineWithName": "{0} से संयोग हो गया है", - "DeviceOfflineWithName": "{0} से संयोग विच्छिन्न हो गया है", + "DeviceOnlineWithName": "{0} कनेक्ट हो गया है", + "DeviceOfflineWithName": "{0} डिस्कनेक्ट हो गया है", "Default": "प्राथमिक", - "Collections": "संग्रहों", - "ChapterNameValue": "अध्याय", + "Collections": "संग्रह", + "ChapterNameValue": "अध्याय {0}", "Channels": "चैनल", - "CameraImageUploadedFrom": "{0} से एक नया कैमरावाला चित्र अपलोड किया गया है", - "Books": "पुस्तकों", - "AuthenticationSucceededWithUserName": "सफलता से प्रमाणीकृत", - "Artists": "कलाकारों", + "CameraImageUploadedFrom": "{0} से एक नया कैमरा छवि अपलोड की गई है", + "Books": "पुस्तकें", + "AuthenticationSucceededWithUserName": "{0} सफलतापूर्वक प्रमाणित किया गया", + "Artists": "कलाकार", "Application": "एप्लिकेशन", "AppDeviceValues": "एप: {0}, उपकरण: {1}", "NotificationOptionPluginUninstalled": "प्लगइन अनइंस्टाल हो गया", diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index a34bcc490..8d8311557 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -126,5 +126,7 @@ "External": "Esterno", "HearingImpaired": "con problemi di udito", "TaskRefreshTrickplayImages": "Genera immagini Trickplay", - "TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate." + "TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate.", + "TaskCleanCollectionsAndPlaylists": "Ripulire le raccolte e le playlist", + "TaskCleanCollectionsAndPlaylistsDescription": "Rimuove gli elementi dalle raccolte e dalle playlist che non esistono più." } diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index e7279994b..004ce68f5 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -126,5 +126,7 @@ "External": "Išorinis", "HearingImpaired": "Su klausos sutrikimais", "TaskRefreshTrickplayImages": "Generuoti Trickplay atvaizdus", - "TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose." + "TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose.", + "TaskCleanCollectionsAndPlaylists": "Sutvarko duomenis jūsų kolekcijose ir grojaraščiuose.", + "TaskCleanCollectionsAndPlaylistsDescription": "Pašalina nebeegzistuojančius elementus iš kolekcijų ir grojaraščių." } diff --git a/Emby.Server.Implementations/Localization/Core/my.json b/Emby.Server.Implementations/Localization/Core/my.json index 198f7540c..4cb4cdc75 100644 --- a/Emby.Server.Implementations/Localization/Core/my.json +++ b/Emby.Server.Implementations/Localization/Core/my.json @@ -48,7 +48,7 @@ "Undefined": "သတ်မှတ်မထားသော", "TvShows": "တီဗီ ဇာတ်လမ်းတွဲများ", "System": "စနစ်", - "Sync": "ထပ်တူကျသည်။", + "Sync": "ချိန်ကိုက်မည်", "SubtitleDownloadFailureFromForItem": "{1} အတွက် {0} မှ စာတန်းထိုးများ ဒေါင်းလုဒ်လုပ်ခြင်း မအောင်မြင်ပါ", "StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို အသင့်ပြင်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။", "Songs": "သီချင်းများ", @@ -104,7 +104,7 @@ "HeaderFavoriteSongs": "အကြိုက်ဆုံးသီချင်းများ", "HeaderFavoriteShows": "အကြိုက်ဆုံး ဇာတ်လမ်းတွဲများ", "HeaderFavoriteEpisodes": "အကြိုက်ဆုံး ဇာတ်လမ်းအပိုင်းများ", - "HeaderFavoriteArtists": "အကြိုက်ဆုံးအနုပညာရှင်များ", + "HeaderFavoriteArtists": "အကြိုက်ဆုံး အနုပညာရှင်များ", "HeaderFavoriteAlbums": "အကြိုက်ဆုံး အယ်လ်ဘမ်များ", "HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ", "HeaderAlbumArtists": "အယ်လ်ဘမ်အနုပညာရှင်များ", @@ -120,5 +120,11 @@ "AuthenticationSucceededWithUserName": "{0} အောင်မြင်စွာ စစ်မှန်ကြောင်း အတည်ပြုပြီးပါပြီ", "Application": "အပလီကေးရှင်း", "AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}", - "External": "ပြင်ပ" + "External": "ပြင်ပ", + "TaskKeyframeExtractorDescription": "ပိုမိုတိကျသည့် အိတ်ချ်အယ်လ်အက်စ် အစဉ်လိုက်ပြသမှုများ ဖန်တီးနိုင်ရန်အတွက် ဗီဒီယိုဖိုင်များမှ ကီးဖရိန်များကို ထုတ်နှုတ်ယူမည် ဖြစ်သည်။ ဤလုပ်ဆောင်မှုသည် အချိန်ကြာရှည်နိုင်သည်။", + "TaskCleanCollectionsAndPlaylistsDescription": "စုစည်းမှုများနှင့် အစဉ်လိုက်ပြသမှုများမှ မရှိတော့သည်များကို ဖယ်ရှားမည်။", + "TaskRefreshTrickplayImages": "ထရစ်ခ်ပလေး ပုံများကို ထုတ်မည်", + "TaskKeyframeExtractor": "ကီးဖရိန်များကို ထုတ်နုတ်ခြင်း", + "TaskCleanCollectionsAndPlaylists": "စုစည်းမှုများနှင့် အစဉ်လိုက်ပြသမှုများကို ရှင်းလင်းမည်", + "HearingImpaired": "အကြားအာရုံ ချို့တဲ့သူ" } diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index be397f1b8..894d4b8ea 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -3,7 +3,7 @@ "AppDeviceValues": "App: {0}, Apparaat: {1}", "Application": "Applicatie", "Artists": "Artiesten", - "AuthenticationSucceededWithUserName": "{0} succesvol geauthenticeerd", + "AuthenticationSucceededWithUserName": "{0} is succesvol geauthenticeerd", "Books": "Boeken", "CameraImageUploadedFrom": "Nieuwe camera-afbeelding toegevoegd vanaf {0}", "Channels": "Kanalen", @@ -107,7 +107,7 @@ "TaskRefreshLibraryDescription": "Scant de mediabibliotheek op nieuwe bestanden en vernieuwt de metadata.", "TaskRefreshLibrary": "Mediabibliotheek scannen", "TaskRefreshChapterImagesDescription": "Maakt voorbeeldafbeedingen aan voor video's met hoofdstukken.", - "TaskRefreshChapterImages": "Hoofdstukafbeeldingen extraheren", + "TaskRefreshChapterImages": "Hoofdstukafbeeldingen uitpakken", "TaskCleanCacheDescription": "Verwijdert gecachte bestanden die het systeem niet langer nodig heeft.", "TaskCleanCache": "Cache-map opschonen", "TasksChannelsCategory": "Internetkanalen", @@ -122,9 +122,11 @@ "TaskOptimizeDatabaseDescription": "Comprimeert de database en trimt vrije ruimte. Het uitvoeren van deze taak kan de prestaties verbeteren, na het scannen van de bibliotheek of andere aanpassingen die invloed hebben op de database.", "TaskOptimizeDatabase": "Database optimaliseren", "TaskKeyframeExtractorDescription": "Haalt keyframes uit videobestanden om preciezere HLS-afspeellijsten te maken. Deze taak kan lang duren.", - "TaskKeyframeExtractor": "Keyframe-uitpakker", + "TaskKeyframeExtractor": "Keyframes uitpakken", "External": "Extern", "HearingImpaired": "Slechthorend", "TaskRefreshTrickplayImages": "Trickplay-afbeeldingen genereren", - "TaskRefreshTrickplayImagesDescription": "Genereert trickplay-afbeeldingen voor video's in bibliotheken waarvoor dit is ingeschakeld." + "TaskRefreshTrickplayImagesDescription": "Creëert trickplay-voorvertoningen voor video's in bibliotheken waarvoor dit is ingeschakeld.", + "TaskCleanCollectionsAndPlaylists": "Collecties en afspeellijsten opruimen", + "TaskCleanCollectionsAndPlaylistsDescription": "Verwijdert niet langer bestaande items uit collecties en afspeellijsten." } diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index bd572b744..64427b459 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -126,5 +126,7 @@ "TaskKeyframeExtractor": "Ekstraktor klatek kluczowych", "HearingImpaired": "Niedosłyszący", "TaskRefreshTrickplayImages": "Generuj obrazy trickplay", - "TaskRefreshTrickplayImagesDescription": "Tworzy podglądy trickplay dla filmów we włączonych bibliotekach." + "TaskRefreshTrickplayImagesDescription": "Tworzy podglądy trickplay dla filmów we włączonych bibliotekach.", + "TaskCleanCollectionsAndPlaylistsDescription": "Usuwa elementy z kolekcji i list odtwarzania, które już nie istnieją.", + "TaskCleanCollectionsAndPlaylists": "Oczyść kolekcje i listy odtwarzania" } diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index 92ac2681e..dc96088ff 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -126,5 +126,7 @@ "External": "Externo", "HearingImpaired": "Surdo", "TaskRefreshTrickplayImages": "Gerar imagens de truques", - "TaskRefreshTrickplayImagesDescription": "Cria vizualizações de truques para videos nas librarias ativas." + "TaskRefreshTrickplayImagesDescription": "Cria vizualizações de truques para videos nas librarias ativas.", + "TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.", + "TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução" } diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index 103393a1e..de487488e 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -125,5 +125,7 @@ "TaskKeyframeExtractor": "Extrator de quadro-chave", "TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo.", "TaskRefreshTrickplayImages": "Gerar miniaturas de vídeo", - "TaskRefreshTrickplayImagesDescription": "Cria miniaturas de vídeo para vídeos nas bibliotecas definidas." + "TaskRefreshTrickplayImagesDescription": "Cria miniaturas de vídeo para vídeos nas bibliotecas definidas.", + "TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.", + "TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução" } diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 26d678a0c..3d3f88709 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -126,5 +126,7 @@ "External": "Внешние", "HearingImpaired": "Для слабослышащих", "TaskRefreshTrickplayImages": "Сгенерировать изображения для Trickplay", - "TaskRefreshTrickplayImagesDescription": "Создает предпросмотры для Trickplay для видео в библиотеках, где эта функция включена." + "TaskRefreshTrickplayImagesDescription": "Создает предпросмотры для Trickplay для видео в библиотеках, где эта функция включена.", + "TaskCleanCollectionsAndPlaylists": "Очистка коллекций и списков воспроизведения", + "TaskCleanCollectionsAndPlaylistsDescription": "Удаляет элементы из коллекций и списков воспроизведения, которые больше не существуют." } diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index 43594a42e..905dba5ab 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -126,5 +126,7 @@ "External": "Externé", "HearingImpaired": "Sluchovo postihnutí", "TaskRefreshTrickplayImages": "Generovanie obrázkov Trickplay", - "TaskRefreshTrickplayImagesDescription": "Vytvára trickplay náhľady pre videá v povolených knižniciach." + "TaskRefreshTrickplayImagesDescription": "Vytvára trickplay náhľady pre videá v povolených knižniciach.", + "TaskCleanCollectionsAndPlaylists": "Vyčistiť kolekcie a playlisty", + "TaskCleanCollectionsAndPlaylistsDescription": "Odstráni položky z kolekcií a playlistov, ktoré už neexistujú." } diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index d7a627d12..059753957 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -126,5 +126,7 @@ "External": "Harici", "HearingImpaired": "Duyma Engelli", "TaskRefreshTrickplayImages": "Trickplay Görselleri Oluştur", - "TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için trickplay önizlemeleri oluşturur." + "TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için trickplay önizlemeleri oluşturur.", + "TaskCleanCollectionsAndPlaylistsDescription": "Artık var olmayan koleksiyon ve çalma listelerindeki ögeleri kaldırır.", + "TaskCleanCollectionsAndPlaylists": "Koleksiyonları ve çalma listelerini temizleyin" } diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json index 6f0dcfbe3..5f97d1ef9 100644 --- a/Emby.Server.Implementations/Localization/Core/uk.json +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -83,7 +83,7 @@ "SubtitleDownloadFailureFromForItem": "Не вдалося завантажити субтитри з {0} для {1}", "StartupEmbyServerIsLoading": "Jellyfin Server завантажується. Будь ласка, спробуйте трішки пізніше.", "Songs": "Пісні", - "Shows": "Телепередачі", + "Shows": "Серіали", "ServerNameNeedsToBeRestarted": "{0} потрібно перезапустити", "ScheduledTaskStartedWithName": "{0} розпочато", "ScheduledTaskFailedWithName": "{0} незавершено, збій", @@ -125,5 +125,7 @@ "External": "Зовнішній", "HearingImpaired": "З порушеннями слуху", "TaskRefreshTrickplayImagesDescription": "Створює trickplay-зображення для відео у ввімкнених медіатеках.", - "TaskRefreshTrickplayImages": "Створення Trickplay-зображень" + "TaskRefreshTrickplayImages": "Створити Trickplay-зображення", + "TaskCleanCollectionsAndPlaylists": "Очистити колекції і списки відтворення", + "TaskCleanCollectionsAndPlaylistsDescription": "Видаляє елементи з колекцій і списків відтворення, які більше не існують." } diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index e92752c5f..af9b54ad1 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -125,5 +125,7 @@ "External": "Bên ngoài", "HearingImpaired": "Khiếm Thính", "TaskRefreshTrickplayImages": "Tạo Ảnh Xem Trước Trickplay", - "TaskRefreshTrickplayImagesDescription": "Tạo bản xem trước trịckplay cho video trong thư viện đã bật." + "TaskRefreshTrickplayImagesDescription": "Tạo bản xem trước trịckplay cho video trong thư viện đã bật.", + "TaskCleanCollectionsAndPlaylists": "Dọn dẹp bộ sưu tập và danh sách phát", + "TaskCleanCollectionsAndPlaylistsDescription": "Xóa các mục khỏi bộ sưu tập và danh sách phát không còn tồn tại." } diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index b88d4eeaf..1f1458b6c 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -126,5 +126,7 @@ "External": "外部", "HearingImpaired": "听力障碍", "TaskRefreshTrickplayImages": "生成时间轴缩略图", - "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成时间轴缩略图。" + "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成时间轴缩略图。", + "TaskCleanCollectionsAndPlaylists": "清理合集和播放列表", + "TaskCleanCollectionsAndPlaylistsDescription": "清理合集和播放列表中已不存在的项目。" } diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index aea8d6532..7a6cf9eff 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -22,6 +22,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Playlists; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using PlaylistsNET.Content; @@ -59,6 +60,11 @@ namespace Emby.Server.Implementations.Playlists _appConfig = appConfig; } + public Playlist GetPlaylistForUser(Guid playlistId, Guid userId) + { + return GetPlaylists(userId).Where(p => p.Id.Equals(playlistId)).FirstOrDefault(); + } + public IEnumerable<Playlist> GetPlaylists(Guid userId) { var user = _userManager.GetUserById(userId); @@ -66,61 +72,56 @@ namespace Emby.Server.Implementations.Playlists return GetPlaylistsFolder(userId).GetChildren(user, true).OfType<Playlist>(); } - public async Task<PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest options) + public async Task<PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest request) { - var name = options.Name; + var name = request.Name; var folderName = _fileSystem.GetValidFilename(name); - var parentFolder = GetPlaylistsFolder(options.UserId); + var parentFolder = GetPlaylistsFolder(request.UserId); if (parentFolder is null) { throw new ArgumentException(nameof(parentFolder)); } - if (options.MediaType is null || options.MediaType == MediaType.Unknown) + if (request.MediaType is null || request.MediaType == MediaType.Unknown) { - foreach (var itemId in options.ItemIdList) + foreach (var itemId in request.ItemIdList) { - var item = _libraryManager.GetItemById(itemId); - if (item is null) - { - throw new ArgumentException("No item exists with the supplied Id"); - } - + var item = _libraryManager.GetItemById(itemId) ?? throw new ArgumentException("No item exists with the supplied Id"); if (item.MediaType != MediaType.Unknown) { - options.MediaType = item.MediaType; + request.MediaType = item.MediaType; } else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre) { - options.MediaType = MediaType.Audio; + request.MediaType = MediaType.Audio; } else if (item is Genre) { - options.MediaType = MediaType.Video; + request.MediaType = MediaType.Video; } else { if (item is Folder folder) { - options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) + request.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) .Select(i => i.MediaType) .FirstOrDefault(i => i != MediaType.Unknown); } } - if (options.MediaType is not null && options.MediaType != MediaType.Unknown) + if (request.MediaType is not null && request.MediaType != MediaType.Unknown) { break; } } } - if (options.MediaType is null || options.MediaType == MediaType.Unknown) + if (request.MediaType is null || request.MediaType == MediaType.Unknown) { - options.MediaType = MediaType.Audio; + request.MediaType = MediaType.Audio; } - var user = _userManager.GetUserById(options.UserId); + var user = _userManager.GetUserById(request.UserId); var path = Path.Combine(parentFolder.Path, folderName); path = GetTargetPath(path); @@ -133,19 +134,20 @@ namespace Emby.Server.Implementations.Playlists { Name = name, Path = path, - OwnerUserId = options.UserId, - Shares = options.Shares ?? Array.Empty<Share>() + OwnerUserId = request.UserId, + Shares = request.Users ?? [], + OpenAccess = request.Public ?? false }; - playlist.SetMediaType(options.MediaType); + playlist.SetMediaType(request.MediaType); parentFolder.AddChild(playlist); await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None) .ConfigureAwait(false); - if (options.ItemIdList.Count > 0) + if (request.ItemIdList.Count > 0) { - await AddToPlaylistInternal(playlist.Id, options.ItemIdList, user, new DtoOptions(false) + await AddToPlaylistInternal(playlist.Id, request.ItemIdList, user, new DtoOptions(false) { EnableImages = true }).ConfigureAwait(false); @@ -160,7 +162,7 @@ namespace Emby.Server.Implementations.Playlists } } - private string GetTargetPath(string path) + private static string GetTargetPath(string path) { while (Directory.Exists(path)) { @@ -170,14 +172,14 @@ namespace Emby.Server.Implementations.Playlists return path; } - private List<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, MediaType playlistMediaType, User user, DtoOptions options) + private IReadOnlyList<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, MediaType playlistMediaType, User user, DtoOptions options) { - var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i is not null); + var items = itemIds.Select(_libraryManager.GetItemById).Where(i => i is not null); return Playlist.GetPlaylistItems(playlistMediaType, items, user, options); } - public Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId) + public Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId) { var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); @@ -231,13 +233,8 @@ namespace Emby.Server.Implementations.Playlists // Update the playlist in the repository playlist.LinkedChildren = newLinkedChildren; - await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - // Update the playlist on disk - if (playlist.IsFile) - { - SavePlaylistFile(playlist); - } + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); // Refresh playlist metadata _providerManager.QueueRefresh( @@ -249,7 +246,7 @@ namespace Emby.Server.Implementations.Playlists RefreshPriority.High); } - public async Task RemoveFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds) + public async Task RemoveItemFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds) { if (_libraryManager.GetItemById(playlistId) is not Playlist playlist) { @@ -266,12 +263,7 @@ namespace Emby.Server.Implementations.Playlists .Select(i => i.Item1) .ToArray(); - await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - - if (playlist.IsFile) - { - SavePlaylistFile(playlist); - } + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); _providerManager.QueueRefresh( playlist.Id, @@ -313,14 +305,9 @@ namespace Emby.Server.Implementations.Playlists newList.Insert(newIndex, item); } - playlist.LinkedChildren = newList.ToArray(); + playlist.LinkedChildren = [.. newList]; - await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - - if (playlist.IsFile) - { - SavePlaylistFile(playlist); - } + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); } /// <inheritdoc /> @@ -430,8 +417,11 @@ namespace Emby.Server.Implementations.Playlists } else if (extension.Equals(".m3u8", StringComparison.OrdinalIgnoreCase)) { - var playlist = new M3uPlaylist(); - playlist.IsExtended = true; + var playlist = new M3uPlaylist + { + IsExtended = true + }; + foreach (var child in item.GetLinkedChildren()) { var entry = new M3uPlaylistEntry() @@ -481,7 +471,7 @@ namespace Emby.Server.Implementations.Playlists } } - private string NormalizeItemPath(string playlistPath, string itemPath) + private static string NormalizeItemPath(string playlistPath, string itemPath) { return MakeRelativePath(Path.GetDirectoryName(playlistPath), itemPath); } @@ -537,16 +527,11 @@ namespace Emby.Server.Implementations.Playlists { // Update owner if shared var rankedShares = playlist.Shares.OrderByDescending(x => x.CanEdit).ToArray(); - if (rankedShares.Length > 0 && Guid.TryParse(rankedShares[0].UserId, out var guid)) + if (rankedShares.Length > 0) { - playlist.OwnerUserId = guid; + playlist.OwnerUserId = rankedShares[0].UserId; playlist.Shares = rankedShares.Skip(1).ToArray(); - await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - - if (playlist.IsFile) - { - SavePlaylistFile(playlist); - } + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); } else if (!playlist.OpenAccess) { @@ -563,5 +548,76 @@ namespace Emby.Server.Implementations.Playlists } } } + + public async Task UpdatePlaylist(PlaylistUpdateRequest request) + { + var playlist = GetPlaylistForUser(request.Id, request.UserId); + + if (request.Ids is not null) + { + playlist.LinkedChildren = []; + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + + var user = _userManager.GetUserById(request.UserId); + await AddToPlaylistInternal(request.Id, request.Ids, user, new DtoOptions(false) + { + EnableImages = true + }).ConfigureAwait(false); + + playlist = GetPlaylistForUser(request.Id, request.UserId); + } + + if (request.Name is not null) + { + playlist.Name = request.Name; + } + + if (request.Users is not null) + { + playlist.Shares = request.Users; + } + + if (request.Public is not null) + { + playlist.OpenAccess = request.Public.Value; + } + + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + } + + public async Task AddUserToShares(PlaylistUserUpdateRequest request) + { + var userId = request.UserId; + var playlist = GetPlaylistForUser(request.Id, userId); + var shares = playlist.Shares.ToList(); + var existingUserShare = shares.FirstOrDefault(s => s.UserId.Equals(userId)); + if (existingUserShare is not null) + { + shares.Remove(existingUserShare); + } + + shares.Add(new PlaylistUserPermissions(userId, request.CanEdit ?? false)); + playlist.Shares = shares; + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + } + + public async Task RemoveUserFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share) + { + var playlist = GetPlaylistForUser(playlistId, userId); + var shares = playlist.Shares.ToList(); + shares.Remove(share); + playlist.Shares = shares; + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + } + + private async Task UpdatePlaylistInternal(Playlist playlist) + { + await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + if (playlist.IsFile) + { + SavePlaylistFile(playlist); + } + } } } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 40b3b0339..06798628f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -159,10 +159,7 @@ namespace Emby.Server.Implementations.Session private void CheckDisposed() { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } + ObjectDisposedException.ThrowIf(_disposed, this); } private void OnSessionStarted(SessionInfo info) @@ -456,8 +453,8 @@ namespace Emby.Server.Implementations.Session if (!_activeConnections.TryGetValue(key, out var sessionInfo)) { - _activeConnections[key] = await CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); - sessionInfo = _activeConnections[key]; + sessionInfo = await CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); + _activeConnections[key] = sessionInfo; } sessionInfo.UserId = user?.Id ?? Guid.Empty; @@ -614,9 +611,6 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug(ex, "Error calling OnPlaybackStopped"); } } - - playingSessions = Sessions.Where(i => i.NowPlayingItem is not null) - .ToList(); } else { diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 65c8599e7..59185cdb7 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -9,37 +8,35 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class AlbumArtistComparer. + /// Allows comparing artists of albums. Only the first artist of each album is considered. /// </summary> public class AlbumArtistComparer : IBaseItemComparer { /// <summary> - /// Gets the name. + /// Gets the item type this comparer compares. /// </summary> - /// <value>The name.</value> public ItemSortBy Type => ItemSortBy.AlbumArtist; /// <summary> - /// Compares the specified x. + /// Compares the specified arguments on their primary artist. /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> + /// <param name="x">First item to compare.</param> + /// <param name="y">Second item to compare.</param> + /// <returns>Zero if equal, else negative or positive number to indicate order.</returns> public int Compare(BaseItem? x, BaseItem? y) { - return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase); + return string.Compare(GetFirstAlbumArtist(x), GetFirstAlbumArtist(y), StringComparison.OrdinalIgnoreCase); } - /// <summary> - /// Gets the value. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>System.String.</returns> - private static string? GetValue(BaseItem? x) + private static string? GetFirstAlbumArtist(BaseItem? x) { - var audio = x as IHasAlbumArtist; + if (x is IHasAlbumArtist audio + && audio.AlbumArtists.Count != 0) + { + return audio.AlbumArtists[0]; + } - return audio?.AlbumArtists.FirstOrDefault(); + return null; } } } |
