aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--Emby.Dlna/DlnaManager.cs68
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs2
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs32
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj4
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs34
-rw-r--r--Emby.Server.Implementations/Localization/Core/af.json22
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/cs.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/el.json12
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-US.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json18
-rw-r--r--Emby.Server.Implementations/Localization/Core/es.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/hu.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/it.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/kk.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ml.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/pl.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/pr.json1
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt-BR.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/sk.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json8
-rw-r--r--Emby.Server.Implementations/Localization/Core/vi.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-CN.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-HK.json9
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json6
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs63
-rw-r--r--Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs5
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs9
-rw-r--r--Jellyfin.Api/Helpers/ProgressiveFileStream.cs11
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj2
-rw-r--r--Jellyfin.Data/Enums/BaseItemKind.cs17
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj8
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj12
-rw-r--r--MediaBrowser.Common/Net/IPHost.cs48
-rw-r--r--MediaBrowser.Common/Plugins/BasePluginOfT.cs4
-rw-r--r--MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs8
-rw-r--r--MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs4
-rw-r--r--MediaBrowser.Controller/Channels/ChannelItemResult.cs9
-rw-r--r--MediaBrowser.Controller/Collections/CollectionCreationOptions.cs2
-rw-r--r--MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs2
-rw-r--r--MediaBrowser.Controller/Collections/ICollectionManager.cs8
-rw-r--r--MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs2
-rw-r--r--MediaBrowser.Controller/Dlna/IDlnaManager.cs8
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs971
-rw-r--r--MediaBrowser.Controller/Entities/BaseItemExtensions.cs5
-rw-r--r--MediaBrowser.Controller/Entities/BasePluginFolder.cs12
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs40
-rw-r--r--MediaBrowser.Controller/Entities/Extensions.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs2
-rw-r--r--MediaBrowser.Controller/Entities/IHasMediaSources.cs2
-rw-r--r--MediaBrowser.Controller/Entities/IHasTrailers.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Person.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Studio.cs2
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs136
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs92
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs14
-rw-r--r--MediaBrowser.Controller/Entities/UserItemData.cs14
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs42
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs238
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs3
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs32
-rw-r--r--MediaBrowser.Controller/Persistence/IUserDataRepository.cs1
-rw-r--r--MediaBrowser.Controller/Playlists/Playlist.cs78
-rw-r--r--MediaBrowser.Controller/Resolvers/IItemResolver.cs20
-rw-r--r--MediaBrowser.Controller/Subtitles/ISubtitleManager.cs19
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs18
-rw-r--r--MediaBrowser.Model/Globalization/ILocalizationManager.cs11
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs102
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs92
-rw-r--r--README.md3
-rw-r--r--debian/jellyfin.service2
-rw-r--r--deployment/Dockerfile.ubuntu.amd642
-rw-r--r--deployment/Dockerfile.ubuntu.arm642
-rw-r--r--deployment/Dockerfile.ubuntu.armhf2
-rwxr-xr-xdeployment/build.portable2
-rw-r--r--fedora/jellyfin.service2
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj4
-rw-r--r--tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs10
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj4
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj2
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj2
-rw-r--r--tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj4
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj2
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj4
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj2
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj4
-rw-r--r--tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs180
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs63
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj4
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj4
-rw-r--r--tests/Jellyfin.Server.Tests/ParseNetworkTests.cs94
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj2
100 files changed, 1601 insertions, 1257 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index c2d2aff34..1fe255385 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -212,4 +212,5 @@
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh)
+ - [lbenini](https://github.com/lbenini)
- [gnuyent](https://github.com/gnuyent)
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index b08f7590d..af70793cc 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -1,7 +1,4 @@
-#nullable disable
-
#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -96,12 +93,14 @@ namespace Emby.Dlna
}
}
+ /// <inheritdoc />
public DeviceProfile GetDefaultProfile()
{
return new DefaultProfile();
}
- public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
+ /// <inheritdoc />
+ public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
{
if (deviceInfo == null)
{
@@ -111,13 +110,13 @@ namespace Emby.Dlna
var profile = GetProfiles()
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
- if (profile != null)
+ if (profile == null)
{
- _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
+ LogUnmatchedProfile(deviceInfo);
}
else
{
- LogUnmatchedProfile(deviceInfo);
+ _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
}
return profile;
@@ -187,7 +186,8 @@ namespace Emby.Dlna
}
}
- public DeviceProfile GetProfile(IHeaderDictionary headers)
+ /// <inheritdoc />
+ public DeviceProfile? GetProfile(IHeaderDictionary headers)
{
if (headers == null)
{
@@ -195,15 +195,13 @@ namespace Emby.Dlna
}
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
-
- if (profile != null)
+ if (profile == null)
{
- _logger.LogDebug("Found matching device profile: {0}", profile.Name);
+ _logger.LogDebug("No matching device profile found. {@Headers}", headers);
}
else
{
- var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value)));
- _logger.LogDebug("No matching device profile found. {0}", headerString);
+ _logger.LogDebug("Found matching device profile: {0}", profile.Name);
}
return profile;
@@ -253,19 +251,19 @@ namespace Emby.Dlna
return xmlFies
.Select(i => ParseProfileFile(i, type))
.Where(i => i != null)
- .ToList();
+ .ToList()!; // We just filtered out all the nulls
}
catch (IOException)
{
- return new List<DeviceProfile>();
+ return Array.Empty<DeviceProfile>();
}
}
- private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
+ private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
{
lock (_profiles)
{
- if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile> profileTuple))
+ if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile>? profileTuple))
{
return profileTuple.Item2;
}
@@ -293,7 +291,8 @@ namespace Emby.Dlna
}
}
- public DeviceProfile GetProfile(string id)
+ /// <inheritdoc />
+ public DeviceProfile? GetProfile(string id)
{
if (string.IsNullOrEmpty(id))
{
@@ -322,6 +321,7 @@ namespace Emby.Dlna
}
}
+ /// <inheritdoc />
public IEnumerable<DeviceProfileInfo> GetProfileInfos()
{
return GetProfileInfosInternal().Select(i => i.Info);
@@ -329,17 +329,14 @@ namespace Emby.Dlna
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
{
- return new InternalProfileInfo
- {
- Path = file.FullName,
-
- Info = new DeviceProfileInfo
+ return new InternalProfileInfo(
+ new DeviceProfileInfo
{
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
Name = _fileSystem.GetFileNameWithoutExtension(file),
Type = type
- }
- };
+ },
+ file.FullName);
}
private async Task ExtractSystemProfilesAsync()
@@ -359,7 +356,8 @@ namespace Emby.Dlna
systemProfilesPath,
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
- using (var stream = _assembly.GetManifestResourceStream(name))
+ // The stream should exist as we just got its name from GetManifestResourceNames
+ using (var stream = _assembly.GetManifestResourceStream(name)!)
{
var fileInfo = _fileSystem.GetFileInfo(path);
@@ -380,6 +378,7 @@ namespace Emby.Dlna
Directory.CreateDirectory(UserProfilesPath);
}
+ /// <inheritdoc />
public void DeleteProfile(string id)
{
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
@@ -397,6 +396,7 @@ namespace Emby.Dlna
}
}
+ /// <inheritdoc />
public void CreateProfile(DeviceProfile profile)
{
profile = ReserializeProfile(profile);
@@ -412,6 +412,7 @@ namespace Emby.Dlna
SaveProfile(profile, path, DeviceProfileType.User);
}
+ /// <inheritdoc />
public void UpdateProfile(DeviceProfile profile)
{
profile = ReserializeProfile(profile);
@@ -470,9 +471,11 @@ namespace Emby.Dlna
var json = JsonSerializer.Serialize(profile, _jsonOptions);
- return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions);
+ // Output can't be null if the input isn't null
+ return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions)!;
}
+ /// <inheritdoc />
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{
var profile = GetDefaultProfile();
@@ -482,6 +485,7 @@ namespace Emby.Dlna
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
}
+ /// <inheritdoc />
public ImageStream GetIcon(string filename)
{
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
@@ -499,9 +503,15 @@ namespace Emby.Dlna
private class InternalProfileInfo
{
- internal DeviceProfileInfo Info { get; set; }
+ internal InternalProfileInfo(DeviceProfileInfo info, string path)
+ {
+ Info = info;
+ Path = path;
+ }
+
+ internal DeviceProfileInfo Info { get; }
- internal string Path { get; set; }
+ internal string Path { get; }
}
}
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 093607dd5..aa54510a7 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -880,7 +880,7 @@ namespace Emby.Server.Implementations.Channels
}
}
- private async Task CacheResponse(object result, string path)
+ private async Task CacheResponse(ChannelItemResult result, string path)
{
try
{
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 08acd1767..8270c2e84 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.IO;
@@ -63,13 +61,13 @@ namespace Emby.Server.Implementations.Collections
}
/// <inheritdoc />
- public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
+ public event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
/// <inheritdoc />
- public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
+ public event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
/// <inheritdoc />
- public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
+ public event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
private IEnumerable<Folder> FindFolders(string path)
{
@@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Collections
.Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path));
}
- internal async Task<Folder> EnsureLibraryFolder(string path, bool createIfNeeded)
+ internal async Task<Folder?> EnsureLibraryFolder(string path, bool createIfNeeded)
{
var existingFolder = FindFolders(path).FirstOrDefault();
if (existingFolder != null)
@@ -114,7 +112,7 @@ namespace Emby.Server.Implementations.Collections
return Path.Combine(_appPaths.DataPath, "collections");
}
- private Task<Folder> GetCollectionsFolder(bool createIfNeeded)
+ private Task<Folder?> GetCollectionsFolder(bool createIfNeeded)
{
return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
}
@@ -203,8 +201,7 @@ namespace Emby.Server.Implementations.Collections
private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
{
- var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
- if (collection == null)
+ if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
{
throw new ArgumentException("No collection exists with the supplied Id");
}
@@ -256,9 +253,7 @@ namespace Emby.Server.Implementations.Collections
/// <inheritdoc />
public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
{
- var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
-
- if (collection == null)
+ if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
{
throw new ArgumentException("No collection exists with the supplied Id");
}
@@ -312,11 +307,7 @@ namespace Emby.Server.Implementations.Collections
foreach (var item in items)
{
- if (item is not ISupportsBoxSetGrouping)
- {
- results[item.Id] = item;
- }
- else
+ if (item is ISupportsBoxSetGrouping)
{
var itemId = item.Id;
@@ -340,6 +331,7 @@ namespace Emby.Server.Implementations.Collections
}
var alreadyInResults = false;
+
// this is kind of a performance hack because only Video has alternate versions that should be in a box set?
if (item is Video video)
{
@@ -355,11 +347,13 @@ namespace Emby.Server.Implementations.Collections
}
}
- if (!alreadyInResults)
+ if (alreadyInResults)
{
- results[itemId] = item;
+ continue;
}
}
+
+ results[item.Id] = item;
}
return results.Values;
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 4c9e05821..927aa0c20 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -28,9 +28,9 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.8" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
<PackageReference Include="Mono.Nat" Version="3.0.1" />
- <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
+ <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.0" />
<PackageReference Include="sharpcompress" Version="0.28.3" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index c9657f605..16ff98a7d 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -13,7 +13,6 @@ using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
@@ -44,22 +43,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
{
- if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ if (info == null)
{
- using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
- if (!string.IsNullOrEmpty(info.UserAgent))
- {
- requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
- }
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ return File.OpenRead(info.Url);
+ }
- var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .SendAsync(requestMessage, cancellationToken)
- .ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
- return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
+ if (!string.IsNullOrEmpty(info.UserAgent))
+ {
+ requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
}
- return File.OpenRead(info.Url);
+ // Set HttpCompletionOption.ResponseHeadersRead to prevent timeouts on larger files
+ var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
+ .ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+
+ return await response.Content.ReadAsStreamAsync(cancellationToken);
}
private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)
@@ -83,7 +89,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
{
extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim();
- _logger.LogInformation("Found m3u channel: {0}", extInf);
}
else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
{
@@ -99,6 +104,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
channel.Path = trimmedLine;
channels.Add(channel);
+ _logger.LogInformation("Parsed channel: {ChannelName}", channel.Name);
extInf = string.Empty;
}
}
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index 4f21c66bc..18f17dda9 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -2,24 +2,24 @@
"Artists": "Kunstenare",
"Channels": "Kanale",
"Folders": "Lêergidse",
- "Favorites": "Gunstellinge",
+ "Favorites": "Gunstelinge",
"HeaderFavoriteShows": "Gunsteling Vertonings",
"ValueSpecialEpisodeName": "Spesiale - {0}",
- "HeaderAlbumArtists": "Album Kunstenaars",
+ "HeaderAlbumArtists": "Kunstenaars se Album",
"Books": "Boeke",
"HeaderNextUp": "Volgende",
"Movies": "Flieks",
"Shows": "Televisie Reekse",
"HeaderContinueWatching": "Kyk Verder",
"HeaderFavoriteEpisodes": "Gunsteling Episodes",
- "Photos": "Fotos",
+ "Photos": "Foto's",
"Playlists": "Snitlyste",
"HeaderFavoriteArtists": "Gunsteling Kunstenaars",
"HeaderFavoriteAlbums": "Gunsteling Albums",
"Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies",
- "DeviceOnlineWithName": "{0} gekoppel is",
+ "DeviceOnlineWithName": "{0} is gekoppel",
"DeviceOfflineWithName": "{0} is ontkoppel",
"Collections": "Versamelings",
"Inherit": "Ontvang",
@@ -71,7 +71,7 @@
"NameSeasonUnknown": "Seisoen Onbekend",
"NameSeasonNumber": "Seisoen {0}",
"NameInstallFailed": "{0} installering het misluk",
- "MusicVideos": "Musiek videos",
+ "MusicVideos": "Musiek Videos",
"Music": "Musiek",
"MixedContent": "Gemengde inhoud",
"MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
@@ -79,15 +79,15 @@
"MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
"MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
"Latest": "Nuutste",
- "LabelRunningTimeValue": "Lopende tyd: {0}",
+ "LabelRunningTimeValue": "Werktyd: {0}",
"LabelIpAddressValue": "IP adres: {0}",
"ItemRemovedWithName": "{0} is uit versameling verwyder",
- "ItemAddedWithName": "{0} is in die versameling",
- "HomeVideos": "Tuis opnames",
+ "ItemAddedWithName": "{0} is by die versameling gevoeg",
+ "HomeVideos": "Tuis Videos",
"HeaderRecordingGroups": "Groep Opnames",
"Genres": "Genres",
"FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
- "ChapterNameValue": "Hoofstuk",
+ "ChapterNameValue": "Hoofstuk {0}",
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
"Albums": "Albums",
@@ -117,5 +117,7 @@
"Forced": "Geforseer",
"Default": "Oorspronklik",
"TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.",
- "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon"
+ "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon",
+ "TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.",
+ "TaskOptimizeDatabase": "Optimaliseer databasis"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index 1b612dc71..7715daa7c 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -5,7 +5,7 @@
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
"Books": "Llibres",
- "CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}",
+ "CameraImageUploadedFrom": "S'ha pujat una nova imatge des de la camera desde {0}",
"Channels": "Canals",
"ChapterNameValue": "Capítol {0}",
"Collections": "Col·leccions",
diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json
index 62b2b6328..4f1d231a4 100644
--- a/Emby.Server.Implementations/Localization/Core/cs.json
+++ b/Emby.Server.Implementations/Localization/Core/cs.json
@@ -15,7 +15,7 @@
"Favorites": "Oblíbené",
"Folders": "Složky",
"Genres": "Žánry",
- "HeaderAlbumArtists": "Umělci alba",
+ "HeaderAlbumArtists": "Album umělce",
"HeaderContinueWatching": "Pokračovat ve sledování",
"HeaderFavoriteAlbums": "Oblíbená alba",
"HeaderFavoriteArtists": "Oblíbení interpreti",
diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json
index 23d45b473..697063f26 100644
--- a/Emby.Server.Implementations/Localization/Core/el.json
+++ b/Emby.Server.Implementations/Localization/Core/el.json
@@ -1,5 +1,5 @@
{
- "Albums": "Άλμπουμς",
+ "Albums": "Άλμπουμ",
"AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}",
"Application": "Εφαρμογή",
"Artists": "Καλλιτέχνες",
@@ -15,7 +15,7 @@
"Favorites": "Αγαπημένα",
"Folders": "Φάκελοι",
"Genres": "Είδη",
- "HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ",
+ "HeaderAlbumArtists": "Άλμπουμ Καλλιτέχνη",
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
@@ -39,7 +39,7 @@
"MixedContent": "Ανάμεικτο Περιεχόμενο",
"Movies": "Ταινίες",
"Music": "Μουσική",
- "MusicVideos": "Μουσικά βίντεο",
+ "MusicVideos": "Μουσικά Βίντεο",
"NameInstallFailed": "{0} η εγκατάσταση απέτυχε",
"NameSeasonNumber": "Κύκλος {0}",
"NameSeasonUnknown": "Άγνωστος Κύκλος",
@@ -62,7 +62,7 @@
"NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε",
"Photos": "Φωτογραφίες",
"Playlists": "Λίστες αναπαραγωγής",
- "Plugin": "Plugin",
+ "Plugin": "Πρόσθετο",
"PluginInstalledWithName": "{0} εγκαταστήθηκε",
"PluginUninstalledWithName": "{0} έχει απεγκατασταθεί",
"PluginUpdatedWithName": "{0} έχει αναβαθμιστεί",
@@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων",
"Undefined": "Απροσδιόριστο",
"Forced": "Εξαναγκασμένο",
- "Default": "Προεπιλογή"
+ "Default": "Προεπιλογή",
+ "TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.",
+ "TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων"
}
diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json
index 65964f6d9..ca127cdb8 100644
--- a/Emby.Server.Implementations/Localization/Core/en-US.json
+++ b/Emby.Server.Implementations/Localization/Core/en-US.json
@@ -17,7 +17,7 @@
"Folders": "Folders",
"Forced": "Forced",
"Genres": "Genres",
- "HeaderAlbumArtists": "Album Artists",
+ "HeaderAlbumArtists": "Artist's Album",
"HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favorite Albums",
"HeaderFavoriteArtists": "Favorite Artists",
@@ -27,7 +27,7 @@
"HeaderLiveTV": "Live TV",
"HeaderNextUp": "Next Up",
"HeaderRecordingGroups": "Recording Groups",
- "HomeVideos": "Home videos",
+ "HomeVideos": "Home Videos",
"Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library",
@@ -41,7 +41,7 @@
"MixedContent": "Mixed content",
"Movies": "Movies",
"Music": "Music",
- "MusicVideos": "Music videos",
+ "MusicVideos": "Music Videos",
"NameInstallFailed": "{0} installation failed",
"NameSeasonNumber": "Season {0}",
"NameSeasonUnknown": "Season Unknown",
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 5d7ed243f..432814dac 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -15,7 +15,7 @@
"Favorites": "Favoritos",
"Folders": "Carpetas",
"Genres": "Géneros",
- "HeaderAlbumArtists": "Artistas del álbum",
+ "HeaderAlbumArtists": "Artistas del Álbum",
"HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "TV en vivo",
"HeaderNextUp": "A continuación",
"HeaderRecordingGroups": "Grupos de grabación",
- "HomeVideos": "Videos caseros",
+ "HomeVideos": "Videos Caseros",
"Inherit": "Heredar",
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
@@ -39,7 +39,7 @@
"MixedContent": "Contenido mezclado",
"Movies": "Películas",
"Music": "Música",
- "MusicVideos": "Videos musicales",
+ "MusicVideos": "Videos Musicales",
"NameInstallFailed": "Falló la instalación de {0}",
"NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Temporada desconocida",
@@ -49,7 +49,7 @@
"NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
"NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
"NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
- "NotificationOptionInstallationFailed": "Falla de instalación",
+ "NotificationOptionInstallationFailed": "Fallo en la instalación",
"NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
"NotificationOptionPluginError": "Falla de complemento",
"NotificationOptionPluginInstalled": "Complemento instalado",
@@ -69,7 +69,7 @@
"ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciado",
- "ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado",
+ "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Programas",
"Songs": "Canciones",
"StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.",
@@ -94,9 +94,9 @@
"VersionNumber": "Versión {0}",
"TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
"TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
- "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
+ "TaskRefreshChannelsDescription": "Actualiza la información de los canales de Internet.",
"TaskRefreshChannels": "Actualizar canales",
- "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.",
+ "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día de antigüedad.",
"TaskCleanTranscode": "Limpiar directorio de transcodificado",
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
"TaskUpdatePlugins": "Actualizar complementos",
@@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir",
"Forced": "Forzado",
- "Default": "Predeterminado"
+ "Default": "Predeterminado",
+ "TaskOptimizeDatabase": "Optimizar base de datos",
+ "TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos."
}
diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json
index 7d42182b0..d3d9d2703 100644
--- a/Emby.Server.Implementations/Localization/Core/es.json
+++ b/Emby.Server.Implementations/Localization/Core/es.json
@@ -15,7 +15,7 @@
"Favorites": "Favoritos",
"Folders": "Carpetas",
"Genres": "Géneros",
- "HeaderAlbumArtists": "Artistas del álbum",
+ "HeaderAlbumArtists": "Artista del álbum",
"HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index 255d5427a..85ab1511a 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -15,7 +15,7 @@
"Favorites": "Kedvencek",
"Folders": "Könyvtárak",
"Genres": "Műfajok",
- "HeaderAlbumArtists": "Album előadók",
+ "HeaderAlbumArtists": "Előadó albumai",
"HeaderContinueWatching": "Megtekintés folytatása",
"HeaderFavoriteAlbums": "Kedvenc albumok",
"HeaderFavoriteArtists": "Kedvenc előadók",
diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json
index 0bde76e74..5e28cf09f 100644
--- a/Emby.Server.Implementations/Localization/Core/it.json
+++ b/Emby.Server.Implementations/Localization/Core/it.json
@@ -15,7 +15,7 @@
"Favorites": "Preferiti",
"Folders": "Cartelle",
"Genres": "Generi",
- "HeaderAlbumArtists": "Artisti degli Album",
+ "HeaderAlbumArtists": "Artisti dell'Album",
"HeaderContinueWatching": "Continua a guardare",
"HeaderFavoriteAlbums": "Album Preferiti",
"HeaderFavoriteArtists": "Artisti Preferiti",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Diretta TV",
"HeaderNextUp": "Prossimo",
"HeaderRecordingGroups": "Gruppi di Registrazione",
- "HomeVideos": "Video personali",
+ "HomeVideos": "Video Personali",
"Inherit": "Eredita",
"ItemAddedWithName": "{0} è stato aggiunto alla libreria",
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
@@ -39,7 +39,7 @@
"MixedContent": "Contenuto misto",
"Movies": "Film",
"Music": "Musica",
- "MusicVideos": "Video musicali",
+ "MusicVideos": "Video Musicali",
"NameInstallFailed": "{0} installazione fallita",
"NameSeasonNumber": "Stagione {0}",
"NameSeasonUnknown": "Stagione sconosciuta",
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index 0d90ad31c..c689bc58a 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -15,7 +15,7 @@
"Favorites": "お気に入り",
"Folders": "フォルダー",
"Genres": "ジャンル",
- "HeaderAlbumArtists": "アルバムアーティスト",
+ "HeaderAlbumArtists": "アーティストのアルバム",
"HeaderContinueWatching": "視聴を続ける",
"HeaderFavoriteAlbums": "お気に入りのアルバム",
"HeaderFavoriteArtists": "お気に入りのアーティスト",
diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json
index 1b4a18deb..d28564a7c 100644
--- a/Emby.Server.Implementations/Localization/Core/kk.json
+++ b/Emby.Server.Implementations/Localization/Core/kk.json
@@ -15,7 +15,7 @@
"Favorites": "Tañdaulylar",
"Folders": "Qaltalar",
"Genres": "Janrlar",
- "HeaderAlbumArtists": "Älbom oryndauşylary",
+ "HeaderAlbumArtists": "Oryndauşynyñ älbomy",
"HeaderContinueWatching": "Qaraudy jalğastyru",
"HeaderFavoriteAlbums": "Tañdauly älbomdar",
"HeaderFavoriteArtists": "Tañdauly oryndauşylar",
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index 409b4d26b..a37de0748 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -15,7 +15,7 @@
"Favorites": "즐겨찾기",
"Folders": "폴더",
"Genres": "장르",
- "HeaderAlbumArtists": "앨범 아티스트",
+ "HeaderAlbumArtists": "아티스트의 앨범",
"HeaderContinueWatching": "계속 시청하기",
"HeaderFavoriteAlbums": "즐겨찾는 앨범",
"HeaderFavoriteArtists": "즐겨찾는 아티스트",
diff --git a/Emby.Server.Implementations/Localization/Core/ml.json b/Emby.Server.Implementations/Localization/Core/ml.json
index 435f9b630..09ef34913 100644
--- a/Emby.Server.Implementations/Localization/Core/ml.json
+++ b/Emby.Server.Implementations/Localization/Core/ml.json
@@ -103,7 +103,7 @@
"ValueSpecialEpisodeName": "പ്രത്യേക - {0}",
"Collections": "ശേഖരങ്ങൾ",
"Folders": "ഫോൾഡറുകൾ",
- "HeaderAlbumArtists": "ആൽബം ആർട്ടിസ്റ്റുകൾ",
+ "HeaderAlbumArtists": "കലാകാരന്റെ ആൽബം",
"Sync": "സമന്വയിപ്പിക്കുക",
"Movies": "സിനിമകൾ",
"Photos": "ഫോട്ടോകൾ",
diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json
index 275bdec6e..e8a32a13e 100644
--- a/Emby.Server.Implementations/Localization/Core/pl.json
+++ b/Emby.Server.Implementations/Localization/Core/pl.json
@@ -15,7 +15,7 @@
"Favorites": "Ulubione",
"Folders": "Foldery",
"Genres": "Gatunki",
- "HeaderAlbumArtists": "Wykonawcy albumów",
+ "HeaderAlbumArtists": "Album artysty",
"HeaderContinueWatching": "Kontynuuj odtwarzanie",
"HeaderFavoriteAlbums": "Ulubione albumy",
"HeaderFavoriteArtists": "Ulubieni wykonawcy",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Telewizja",
"HeaderNextUp": "Do obejrzenia",
"HeaderRecordingGroups": "Grupy nagrań",
- "HomeVideos": "Nagrania prywatne",
+ "HomeVideos": "Nagrania domowe",
"Inherit": "Dziedzicz",
"ItemAddedWithName": "{0} zostało dodane do biblioteki",
"ItemRemovedWithName": "{0} zostało usunięte z biblioteki",
@@ -119,5 +119,6 @@
"Undefined": "Nieustalony",
"Forced": "Wymuszony",
"Default": "Domyślne",
- "TaskOptimizeDatabase": "Optymalizuj bazę danych"
+ "TaskOptimizeDatabase": "Optymalizuj bazę danych",
+ "TaskOptimizeDatabaseDescription": "Kompaktuje bazę danych i obcina wolne miejsce. Uruchomienie tego zadania po przeskanowaniu biblioteki lub dokonaniu innych zmian, które pociągają za sobą modyfikacje bazy danych, może poprawić wydajność."
}
diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/pr.json
@@ -0,0 +1 @@
+{}
diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json
index 323dcced0..be71289b1 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-BR.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json
@@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Limpar Registro de Atividades",
"Undefined": "Indefinido",
"Forced": "Forçado",
- "Default": "Padrão"
+ "Default": "Padrão",
+ "TaskOptimizeDatabaseDescription": "Compactar base de dados e liberar espaço livre. Executar esta tarefa após realizar mudanças que impliquem em modificações da base de dados pode trazer melhorias de desempenho.",
+ "TaskOptimizeDatabase": "Otimizar base de dados"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index b435672ad..474dacd7c 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -117,5 +117,6 @@
"Undefined": "Indefinido",
"Forced": "Forçado",
"Default": "Predefinição",
- "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado."
+ "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado.",
+ "TaskOptimizeDatabase": "Otimizar base de dados"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index 248f06c4b..cd016b51b 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Эфир",
"HeaderNextUp": "Очередное",
"HeaderRecordingGroups": "Группы записей",
- "HomeVideos": "Домашнее видео",
+ "HomeVideos": "Домашние видео",
"Inherit": "Наследуемое",
"ItemAddedWithName": "{0} - добавлено в медиатеку",
"ItemRemovedWithName": "{0} - изъято из медиатеки",
diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json
index 37da7d5ab..ad90bd813 100644
--- a/Emby.Server.Implementations/Localization/Core/sk.json
+++ b/Emby.Server.Implementations/Localization/Core/sk.json
@@ -39,7 +39,7 @@
"MixedContent": "Zmiešaný obsah",
"Movies": "Filmy",
"Music": "Hudba",
- "MusicVideos": "Hudobné videoklipy",
+ "MusicVideos": "Hudobné videá",
"NameInstallFailed": "Inštalácia {0} zlyhala",
"NameSeasonNumber": "Séria {0}",
"NameSeasonUnknown": "Neznáma séria",
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index d992bf79b..6c772c6a2 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -15,7 +15,7 @@
"Favorites": "Favoriter",
"Folders": "Mappar",
"Genres": "Genrer",
- "HeaderAlbumArtists": "Albumartister",
+ "HeaderAlbumArtists": "Artistens album",
"HeaderContinueWatching": "Fortsätt kolla",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Live-TV",
"HeaderNextUp": "Nästa",
"HeaderRecordingGroups": "Inspelningsgrupper",
- "HomeVideos": "Hemvideor",
+ "HomeVideos": "Hemmavideor",
"Inherit": "Ärv",
"ItemAddedWithName": "{0} lades till i biblioteket",
"ItemRemovedWithName": "{0} togs bort från biblioteket",
@@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Rensa Aktivitets Logg",
"Undefined": "odefinierad",
"Forced": "Tvingad",
- "Default": "Standard"
+ "Default": "Standard",
+ "TaskOptimizeDatabase": "Optimera databasen",
+ "TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna task efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats."
}
diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json
index 20ab1dd7d..3d69e418b 100644
--- a/Emby.Server.Implementations/Localization/Core/vi.json
+++ b/Emby.Server.Implementations/Localization/Core/vi.json
@@ -3,7 +3,7 @@
"Favorites": "Yêu Thích",
"Folders": "Thư Mục",
"Genres": "Thể Loại",
- "HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ",
+ "HeaderAlbumArtists": "Album Nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim",
@@ -82,7 +82,7 @@
"NameSeasonUnknown": "Không Rõ Mùa",
"NameSeasonNumber": "Phần {0}",
"NameInstallFailed": "{0} cài đặt thất bại",
- "MusicVideos": "Video Nhạc",
+ "MusicVideos": "Videos Nhạc",
"Music": "Nhạc",
"MixedContent": "Nội dung hỗn hợp",
"MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json
index faa9c40e2..f9df62724 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-CN.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json
@@ -7,7 +7,7 @@
"Books": "书籍",
"CameraImageUploadedFrom": "新的相机图像已从 {0} 上传",
"Channels": "频道",
- "ChapterNameValue": "第 {0} 集",
+ "ChapterNameValue": "章节 {0}",
"Collections": "合集",
"DeviceOfflineWithName": "{0} 已断开",
"DeviceOnlineWithName": "{0} 已连接",
@@ -15,8 +15,8 @@
"Favorites": "我的最爱",
"Folders": "文件夹",
"Genres": "风格",
- "HeaderAlbumArtists": "专辑作家",
- "HeaderContinueWatching": "继续观影",
+ "HeaderAlbumArtists": "专辑艺术家",
+ "HeaderContinueWatching": "继续观看",
"HeaderFavoriteAlbums": "收藏的专辑",
"HeaderFavoriteArtists": "最爱的艺术家",
"HeaderFavoriteEpisodes": "最爱的剧集",
@@ -108,8 +108,8 @@
"TaskCleanLogs": "清理日志目录",
"TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。",
"TaskRefreshLibrary": "扫描媒体库",
- "TaskRefreshChapterImagesDescription": "为包含剧集的视频提取缩略图。",
- "TaskRefreshChapterImages": "提取剧集图片",
+ "TaskRefreshChapterImagesDescription": "为包含章节的视频提取缩略图。",
+ "TaskRefreshChapterImages": "提取章节图片",
"TaskCleanCacheDescription": "删除系统不再需要的缓存文件。",
"TaskCleanCache": "清理缓存目录",
"TasksApplicationCategory": "应用程序",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json
index 3dad21dcb..1cc97bc27 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-HK.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json
@@ -13,7 +13,7 @@
"DeviceOnlineWithName": "{0} 已經連接",
"FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗",
"Favorites": "我的最愛",
- "Folders": "檔案夾",
+ "Folders": "資料夾",
"Genres": "風格",
"HeaderAlbumArtists": "專輯藝人",
"HeaderContinueWatching": "繼續觀看",
@@ -39,7 +39,7 @@
"MixedContent": "混合內容",
"Movies": "電影",
"Music": "音樂",
- "MusicVideos": "音樂視頻",
+ "MusicVideos": "音樂影片",
"NameInstallFailed": "{0} 安裝失敗",
"NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知季數",
@@ -117,5 +117,8 @@
"TaskCleanActivityLog": "清理活動記錄",
"Undefined": "未定義",
"Forced": "強制",
- "Default": "預設"
+ "Default": "預設",
+ "TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。",
+ "TaskOptimizeDatabase": "最佳化數據庫",
+ "TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。"
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index c3b223f63..585d81450 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -24,7 +24,7 @@
"HeaderFavoriteSongs": "最愛歌曲",
"HeaderLiveTV": "電視直播",
"HeaderNextUp": "接下來",
- "HomeVideos": "自製影片",
+ "HomeVideos": "家庭影片",
"ItemAddedWithName": "{0} 已新增至媒體庫",
"ItemRemovedWithName": "{0} 已從媒體庫移除",
"LabelIpAddressValue": "IP 位址:{0}",
@@ -117,5 +117,7 @@
"TaskCleanActivityLog": "清除活動紀錄",
"Undefined": "未定義的",
"Forced": "強制",
- "Default": "原本"
+ "Default": "原本",
+ "TaskOptimizeDatabaseDescription": "縮小資料庫並釋放可用空間。在掃描資料庫或進行資料庫相關的更動後使用此功能會增加效能。",
+ "TaskOptimizeDatabase": "最佳化資料庫"
}
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 9808e47de..03919197e 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -38,10 +36,10 @@ namespace Emby.Server.Implementations.Localization
private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
- private List<CultureDto> _cultures;
-
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+ private List<CultureDto> _cultures = new List<CultureDto>();
+
/// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager" /> class.
/// </summary>
@@ -72,8 +70,8 @@ namespace Emby.Server.Implementations.Localization
string countryCode = resource.Substring(RatingsPath.Length, 2);
var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
- await using var str = _assembly.GetManifestResourceStream(resource);
- using var reader = new StreamReader(str);
+ await using var stream = _assembly.GetManifestResourceStream(resource);
+ using var reader = new StreamReader(stream!); // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames()
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
if (string.IsNullOrWhiteSpace(line))
@@ -113,7 +111,8 @@ namespace Emby.Server.Implementations.Localization
{
List<CultureDto> list = new List<CultureDto>();
- await using var stream = _assembly.GetManifestResourceStream(CulturesPath);
+ await using var stream = _assembly.GetManifestResourceStream(CulturesPath)
+ ?? throw new InvalidOperationException($"Invalid resource path: '{CulturesPath}'");
using var reader = new StreamReader(stream);
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
@@ -162,7 +161,7 @@ namespace Emby.Server.Implementations.Localization
}
/// <inheritdoc />
- public CultureDto FindLanguageInfo(string language)
+ public CultureDto? FindLanguageInfo(string language)
{
// TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
for (var i = 0; i < _cultures.Count; i++)
@@ -183,9 +182,10 @@ namespace Emby.Server.Implementations.Localization
/// <inheritdoc />
public IEnumerable<CountryInfo> GetCountries()
{
- using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream(CountriesPath));
-
- return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions);
+ using StreamReader reader = new StreamReader(
+ _assembly.GetManifestResourceStream(CountriesPath) ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"));
+ return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions)
+ ?? throw new InvalidOperationException($"Resource contains invalid data: '{CountriesPath}'");
}
/// <inheritdoc />
@@ -205,7 +205,9 @@ namespace Emby.Server.Implementations.Localization
countryCode = "us";
}
- return GetRatings(countryCode) ?? GetRatings("us");
+ return GetRatings(countryCode)
+ ?? GetRatings("us")
+ ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'");
}
/// <summary>
@@ -213,7 +215,7 @@ namespace Emby.Server.Implementations.Localization
/// </summary>
/// <param name="countryCode">The country code.</param>
/// <returns>The ratings.</returns>
- private Dictionary<string, ParentalRating> GetRatings(string countryCode)
+ private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
{
_allParentalRatings.TryGetValue(countryCode, out var value);
@@ -238,7 +240,7 @@ namespace Emby.Server.Implementations.Localization
var ratingsDictionary = GetParentalRatingsDictionary();
- if (ratingsDictionary.TryGetValue(rating, out ParentalRating value))
+ if (ratingsDictionary.TryGetValue(rating, out ParentalRating? value))
{
return value.Value;
}
@@ -269,20 +271,6 @@ namespace Emby.Server.Implementations.Localization
}
/// <inheritdoc />
- public bool HasUnicodeCategory(string value, UnicodeCategory category)
- {
- foreach (var chr in value)
- {
- if (char.GetUnicodeCategory(chr) == category)
- {
- return true;
- }
- }
-
- return false;
- }
-
- /// <inheritdoc />
public string GetLocalizedString(string phrase)
{
return GetLocalizedString(phrase, _configurationManager.Configuration.UICulture);
@@ -347,18 +335,21 @@ namespace Emby.Server.Implementations.Localization
{
await using var stream = _assembly.GetManifestResourceStream(resourcePath);
// If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
- if (stream != null)
+ if (stream == null)
{
- var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
+ _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
+ return;
+ }
- foreach (var key in dict.Keys)
- {
- dictionary[key] = dict[key];
- }
+ var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
+ if (dict == null)
+ {
+ throw new InvalidOperationException($"Resource contains invalid data: '{stream}'");
}
- else
+
+ foreach (var key in dict.Keys)
{
- _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
+ dictionary[key] = dict[key];
}
}
diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
index 358606b0d..4160f3a50 100644
--- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
+++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
@@ -49,5 +49,10 @@ namespace Emby.Server.Implementations.Playlists
query.Parent = null;
return LibraryManager.GetItemsResult(query);
}
+
+ public override string GetClientTypeName()
+ {
+ return "ManualPlaylistsFolder";
+ }
}
}
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 24ee833ef..47ebe9f57 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -1172,7 +1172,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesVideoFile]
- public async Task<ActionResult> GetLiveRecordingFile([FromRoute, Required] string recordingId)
+ public ActionResult GetLiveRecordingFile([FromRoute, Required] string recordingId)
{
var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
@@ -1181,11 +1181,8 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- await using var memoryStream = new MemoryStream();
- await new ProgressiveFileCopier(path, null, _transcodingJobHelper, CancellationToken.None)
- .WriteToAsync(memoryStream, CancellationToken.None)
- .ConfigureAwait(false);
- return File(memoryStream, MimeTypes.GetMimeType(path));
+ var stream = new ProgressiveFileStream(path, null, _transcodingJobHelper);
+ return new FileStreamResult(stream, MimeTypes.GetMimeType(path));
}
/// <summary>
diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs
index 824870c7e..499dbe84d 100644
--- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs
+++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
@@ -16,6 +17,7 @@ namespace Jellyfin.Api.Helpers
private readonly FileStream _fileStream;
private readonly TranscodingJobDto? _job;
private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly int _timeoutMs;
private readonly bool _allowAsyncFileRead;
private int _bytesWritten;
private bool _disposed;
@@ -26,10 +28,12 @@ namespace Jellyfin.Api.Helpers
/// <param name="filePath">The path to the transcoded file.</param>
/// <param name="job">The transcoding job information.</param>
/// <param name="transcodingJobHelper">The transcoding job helper.</param>
- public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper)
+ /// <param name="timeoutMs">The timeout duration in milliseconds.</param>
+ public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000)
{
_job = job;
_transcodingJobHelper = transcodingJobHelper;
+ _timeoutMs = timeoutMs;
_bytesWritten = 0;
var fileOptions = FileOptions.SequentialScan;
@@ -81,6 +85,7 @@ namespace Jellyfin.Api.Helpers
{
int totalBytesRead = 0;
int remainingBytesToRead = count;
+ var stopwatch = Stopwatch.StartNew();
int newOffset = offset;
while (remainingBytesToRead > 0)
@@ -111,8 +116,8 @@ namespace Jellyfin.Api.Helpers
}
else
{
- // If the job is null it's a live stream and will require user action to close
- if (_job?.HasExited ?? false)
+ // If the job is null it's a live stream and will require user action to close, but don't keep it open indefinitely
+ if (_job?.HasExited ?? stopwatch.ElapsedMilliseconds > _timeoutMs)
{
break;
}
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index a527282d1..669925198 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.8" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.5" />
diff --git a/Jellyfin.Data/Enums/BaseItemKind.cs b/Jellyfin.Data/Enums/BaseItemKind.cs
index aac30279e..875781746 100644
--- a/Jellyfin.Data/Enums/BaseItemKind.cs
+++ b/Jellyfin.Data/Enums/BaseItemKind.cs
@@ -79,6 +79,16 @@
Movie,
/// <summary>
+ /// Item is a live tv channel.
+ /// </summary>
+ LiveTvChannel,
+
+ /// <summary>
+ /// Item is a live tv program.
+ /// </summary>
+ LiveTvProgram,
+
+ /// <summary>
/// Item is music album.
/// </summary>
MusicAlbum,
@@ -119,6 +129,11 @@
Playlist,
/// <summary>
+ /// Item is playlist folder.
+ /// </summary>
+ PlaylistsFolder,
+
+ /// <summary>
/// Item is program
/// </summary>
Program,
@@ -187,4 +202,4 @@
/// </summary>
Year
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 728f9021d..a75b28593 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -19,13 +19,13 @@
<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.8" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.8" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.8">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.9" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 49529b794..a57666cd6 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -33,18 +33,18 @@
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.8" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.8" />
- <PackageReference Include="prometheus-net" Version="4.2.0" />
- <PackageReference Include="prometheus-net.AspNetCore" Version="4.2.0" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.9" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.9" />
+ <PackageReference Include="prometheus-net" Version="5.0.1" />
+ <PackageReference Include="prometheus-net.AspNetCore" Version="5.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
- <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
+ <PackageReference Include="Serilog.Settings.Configuration" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
- <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
+ <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.5" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs
index d78d7def2..1f125f2b1 100644
--- a/MediaBrowser.Common/Net/IPHost.cs
+++ b/MediaBrowser.Common/Net/IPHost.cs
@@ -4,7 +4,6 @@ using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
-using System.Threading.Tasks;
namespace MediaBrowser.Common.Net
{
@@ -196,7 +195,7 @@ namespace MediaBrowser.Common.Net
return res;
}
- throw new InvalidCastException("Host does not contain a valid value. {host}");
+ throw new InvalidCastException($"Host does not contain a valid value. {host}");
}
/// <summary>
@@ -221,7 +220,7 @@ namespace MediaBrowser.Common.Net
return res;
}
- throw new InvalidCastException("Host does not contain a valid value. {host}");
+ throw new InvalidCastException($"Host does not contain a valid value. {host}");
}
/// <summary>
@@ -349,7 +348,7 @@ namespace MediaBrowser.Common.Net
}
}
- output = output[0..^1];
+ output = output[..^1];
if (moreThanOne)
{
@@ -400,7 +399,7 @@ namespace MediaBrowser.Common.Net
if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout)))
{
_lastResolved = DateTime.UtcNow;
- ResolveHostInternal().GetAwaiter().GetResult();
+ ResolveHostInternal();
Resolved = true;
}
@@ -410,30 +409,31 @@ namespace MediaBrowser.Common.Net
/// <summary>
/// Task that looks up a Host name and returns its IP addresses.
/// </summary>
- /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
- private async Task ResolveHostInternal()
+ private void ResolveHostInternal()
{
- if (!string.IsNullOrEmpty(HostName))
+ var hostName = HostName;
+ if (string.IsNullOrEmpty(hostName))
{
- // Resolves the host name - so save a DNS lookup.
- if (string.Equals(HostName, "localhost", StringComparison.OrdinalIgnoreCase))
+ return;
+ }
+
+ // Resolves the host name - so save a DNS lookup.
+ if (string.Equals(hostName, "localhost", StringComparison.OrdinalIgnoreCase))
+ {
+ _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback };
+ return;
+ }
+
+ if (Uri.CheckHostName(hostName) == UriHostNameType.Dns)
+ {
+ try
{
- _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback };
- return;
+ _addresses = Dns.GetHostEntry(hostName).AddressList;
}
-
- if (Uri.CheckHostName(HostName).Equals(UriHostNameType.Dns))
+ catch (SocketException ex)
{
- try
- {
- IPHostEntry ip = await Dns.GetHostEntryAsync(HostName).ConfigureAwait(false);
- _addresses = ip.AddressList;
- }
- catch (SocketException ex)
- {
- // Log and then ignore socket errors, as the result value will just be an empty array.
- Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message);
- }
+ // Log and then ignore socket errors, as the result value will just be an empty array.
+ Debug.WriteLine("GetHostAddresses failed with {Message}.", ex.Message);
}
}
}
diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
index 8a6d28e0f..afda83a7c 100644
--- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs
+++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
@@ -47,10 +47,10 @@ namespace MediaBrowser.Common.Plugins
var assemblyFilePath = assembly.Location;
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
- if (!Directory.Exists(dataFolderPath) && Version != null)
+ if (Version != null && !Directory.Exists(dataFolderPath))
{
// Try again with the version number appended to the folder name.
- dataFolderPath = dataFolderPath + "_" + Version.ToString();
+ dataFolderPath += "_" + Version.ToString();
}
SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
index 97f40b537..abfdb41d8 100644
--- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
@@ -1,6 +1,5 @@
-#nullable disable
-
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Jellyfin.Extensions;
@@ -16,7 +15,7 @@ namespace MediaBrowser.Controller.BaseItemManager
{
private readonly IServerConfigurationManager _serverConfigurationManager;
- private int _metadataRefreshConcurrency = 0;
+ private int _metadataRefreshConcurrency;
/// <summary>
/// Initializes a new instance of the <see cref="BaseItemManager"/> class.
@@ -101,7 +100,7 @@ namespace MediaBrowser.Controller.BaseItemManager
/// Called when the configuration is updated.
/// It will refresh the metadata throttler if the relevant config changed.
/// </summary>
- private void OnConfigurationUpdated(object sender, EventArgs e)
+ private void OnConfigurationUpdated(object? sender, EventArgs e)
{
int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency();
if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency)
@@ -114,6 +113,7 @@ namespace MediaBrowser.Controller.BaseItemManager
/// <summary>
/// Creates the metadata refresh throttler.
/// </summary>
+ [MemberNotNull(nameof(MetadataRefreshThrottler))]
private void SetupMetadataThrottler()
{
MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency);
diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
index b2b36c040..e18994214 100644
--- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Threading;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
@@ -34,4 +32,4 @@ namespace MediaBrowser.Controller.BaseItemManager
/// <returns><c>true</c> if image fetcher is enabled, else false.</returns>
bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Channels/ChannelItemResult.cs b/MediaBrowser.Controller/Channels/ChannelItemResult.cs
index 7a0addd9f..ca7721991 100644
--- a/MediaBrowser.Controller/Channels/ChannelItemResult.cs
+++ b/MediaBrowser.Controller/Channels/ChannelItemResult.cs
@@ -1,7 +1,6 @@
-#nullable disable
-
-#pragma warning disable CA1002, CA2227, CS1591
+#pragma warning disable CS1591
+using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Channels
@@ -10,10 +9,10 @@ namespace MediaBrowser.Controller.Channels
{
public ChannelItemResult()
{
- Items = new List<ChannelItemInfo>();
+ Items = Array.Empty<ChannelItemInfo>();
}
- public List<ChannelItemInfo> Items { get; set; }
+ public IReadOnlyList<ChannelItemInfo> Items { get; set; }
public int? TotalRecordCount { get; set; }
}
diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
index 76ad335c5..30f5f4efa 100644
--- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
+++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CA2227, CS1591
+#pragma warning disable CS1591
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs
index 8155cf3db..e538fa4b3 100644
--- a/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs
+++ b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs
index 49cc39f04..b8c33ee5a 100644
--- a/MediaBrowser.Controller/Collections/ICollectionManager.cs
+++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -16,17 +14,17 @@ namespace MediaBrowser.Controller.Collections
/// <summary>
/// Occurs when [collection created].
/// </summary>
- event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
+ event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
/// <summary>
/// Occurs when [items added to collection].
/// </summary>
- event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
+ event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
/// <summary>
/// Occurs when [items removed from collection].
/// </summary>
- event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
+ event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
/// <summary>
/// Creates the collection.
diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
index 44e2c45dd..43ad04dba 100644
--- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
+++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Configuration;
diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs
index b51dc255c..a64919700 100644
--- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs
+++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System.Collections.Generic;
@@ -22,7 +20,7 @@ namespace MediaBrowser.Controller.Dlna
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>DeviceProfile.</returns>
- DeviceProfile GetProfile(IHeaderDictionary headers);
+ DeviceProfile? GetProfile(IHeaderDictionary headers);
/// <summary>
/// Gets the default profile.
@@ -53,14 +51,14 @@ namespace MediaBrowser.Controller.Dlna
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>DeviceProfile.</returns>
- DeviceProfile GetProfile(string id);
+ DeviceProfile? GetProfile(string id);
/// <summary>
/// Gets the profile.
/// </summary>
/// <param name="deviceInfo">The device information.</param>
/// <returns>DeviceProfile.</returns>
- DeviceProfile GetProfile(DeviceIdentification deviceInfo);
+ DeviceProfile? GetProfile(DeviceIdentification deviceInfo);
/// <summary>
/// Gets the server description XML.
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 23b97f70c..067fecd87 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1401
using System;
using System.Collections.Generic;
@@ -41,6 +41,22 @@ namespace MediaBrowser.Controller.Entities
public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem>
{
/// <summary>
+ /// The trailer folder name.
+ /// </summary>
+ public const string TrailerFolderName = "trailers";
+ public const string ThemeSongsFolderName = "theme-music";
+ public const string ThemeSongFilename = "theme";
+ public const string ThemeVideosFolderName = "backdrops";
+ public const string ExtrasFolderName = "extras";
+ public const string BehindTheScenesFolderName = "behind the scenes";
+ public const string DeletedScenesFolderName = "deleted scenes";
+ public const string InterviewFolderName = "interviews";
+ public const string SceneFolderName = "scenes";
+ public const string SampleFolderName = "samples";
+ public const string ShortsFolderName = "shorts";
+ public const string FeaturettesFolderName = "featurettes";
+
+ /// <summary>
/// The supported image extensions.
/// </summary>
public static readonly string[] SupportedImageExtensions
@@ -61,38 +77,21 @@ namespace MediaBrowser.Controller.Entities
".ttml"
};
- protected BaseItem()
- {
- Tags = Array.Empty<string>();
- Genres = Array.Empty<string>();
- Studios = Array.Empty<string>();
- ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- LockedFields = Array.Empty<MetadataField>();
- ImageInfos = Array.Empty<ItemImageInfo>();
- ProductionLocations = Array.Empty<string>();
- RemoteTrailers = Array.Empty<MediaUrl>();
- ExtraIds = Array.Empty<Guid>();
- }
-
- public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
- public static char SlugChar = '-';
-
/// <summary>
- /// The trailer folder name.
+ /// Extra types that should be counted and displayed as "Special Features" in the UI.
/// </summary>
- public const string TrailerFolderName = "trailers";
- public const string ThemeSongsFolderName = "theme-music";
- public const string ThemeSongFilename = "theme";
- public const string ThemeVideosFolderName = "backdrops";
- public const string ExtrasFolderName = "extras";
- public const string BehindTheScenesFolderName = "behind the scenes";
- public const string DeletedScenesFolderName = "deleted scenes";
- public const string InterviewFolderName = "interviews";
- public const string SceneFolderName = "scenes";
- public const string SampleFolderName = "samples";
- public const string ShortsFolderName = "shorts";
- public const string FeaturettesFolderName = "featurettes";
+ public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType>
+ {
+ Model.Entities.ExtraType.Unknown,
+ Model.Entities.ExtraType.BehindTheScenes,
+ Model.Entities.ExtraType.Clip,
+ Model.Entities.ExtraType.DeletedScene,
+ Model.Entities.ExtraType.Interview,
+ Model.Entities.ExtraType.Sample,
+ Model.Entities.ExtraType.Scene
+ };
+ public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
public static readonly string[] AllExtrasTypesFolderNames =
{
ExtrasFolderName,
@@ -105,6 +104,29 @@ namespace MediaBrowser.Controller.Entities
FeaturettesFolderName
};
+ private string _sortName;
+ private Guid[] _themeSongIds;
+ private Guid[] _themeVideoIds;
+
+ private string _forcedSortName;
+
+ private string _name;
+
+ public static char SlugChar = '-';
+
+ protected BaseItem()
+ {
+ Tags = Array.Empty<string>();
+ Genres = Array.Empty<string>();
+ Studios = Array.Empty<string>();
+ ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ LockedFields = Array.Empty<MetadataField>();
+ ImageInfos = Array.Empty<ItemImageInfo>();
+ ProductionLocations = Array.Empty<string>();
+ RemoteTrailers = Array.Empty<MediaUrl>();
+ ExtraIds = Array.Empty<Guid>();
+ }
+
[JsonIgnore]
public Guid[] ThemeSongIds
{
@@ -194,8 +216,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public virtual bool SupportsRemoteImageDownloading => true;
- private string _name;
-
/// <summary>
/// Gets or sets the name.
/// </summary>
@@ -328,12 +348,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public virtual bool IsHidden => false;
- public BaseItem GetOwner()
- {
- var ownerId = OwnerId;
- return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId);
- }
-
/// <summary>
/// Gets the type of the location.
/// </summary>
@@ -379,13 +393,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- public bool IsPathProtocol(MediaProtocol protocol)
- {
- var current = PathProtocol;
-
- return current.HasValue && current.Value == protocol;
- }
-
[JsonIgnore]
public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File);
@@ -423,35 +430,17 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public virtual bool EnableAlphaNumericSorting => true;
- private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1)
- {
- var list = new List<Tuple<StringBuilder, bool>>();
-
- int thisMarker = 0;
-
- while (thisMarker < s1.Length)
- {
- char thisCh = s1[thisMarker];
+ public virtual bool IsHD => Height >= 720;
- var thisChunk = new StringBuilder();
- bool isNumeric = char.IsDigit(thisCh);
+ public bool IsShortcut { get; set; }
- while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric)
- {
- thisChunk.Append(thisCh);
- thisMarker++;
+ public string ShortcutPath { get; set; }
- if (thisMarker < s1.Length)
- {
- thisCh = s1[thisMarker];
- }
- }
+ public int Width { get; set; }
- list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric));
- }
+ public int Height { get; set; }
- return list;
- }
+ public Guid[] ExtraIds { get; set; }
/// <summary>
/// Gets the primary image path.
@@ -463,72 +452,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public string PrimaryImagePath => this.GetImagePath(ImageType.Primary);
- public virtual bool CanDelete()
- {
- if (SourceType == SourceType.Channel)
- {
- return ChannelManager.CanDelete(this);
- }
-
- return IsFileProtocol;
- }
-
- public virtual bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders)
- {
- if (user.HasPermission(PermissionKind.EnableContentDeletion))
- {
- return true;
- }
-
- var allowed = user.GetPreferenceValues<Guid>(PreferenceKind.EnableContentDeletionFromFolders);
-
- if (SourceType == SourceType.Channel)
- {
- return allowed.Contains(ChannelId);
- }
- else
- {
- var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders);
-
- foreach (var folder in collectionFolders)
- {
- if (allowed.Contains(folder.Id))
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
- public bool CanDelete(User user, List<Folder> allCollectionFolders)
- {
- return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders);
- }
-
- public bool CanDelete(User user)
- {
- var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList();
-
- return CanDelete(user, allCollectionFolders);
- }
-
- public virtual bool CanDownload()
- {
- return false;
- }
-
- public virtual bool IsAuthorizedToDownload(User user)
- {
- return user.HasPermission(PermissionKind.EnableContentDownloading);
- }
-
- public bool CanDownload(User user)
- {
- return CanDownload() && IsAuthorizedToDownload(user);
- }
-
/// <summary>
/// Gets or sets the date created.
/// </summary>
@@ -548,38 +471,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public DateTime DateLastRefreshed { get; set; }
- /// <summary>
- /// Gets or sets the logger.
- /// </summary>
- public static ILogger<BaseItem> Logger { get; set; }
-
- public static ILibraryManager LibraryManager { get; set; }
-
- public static IServerConfigurationManager ConfigurationManager { get; set; }
-
- public static IProviderManager ProviderManager { get; set; }
-
- public static ILocalizationManager LocalizationManager { get; set; }
-
- public static IItemRepository ItemRepository { get; set; }
-
- public static IFileSystem FileSystem { get; set; }
-
- public static IUserDataManager UserDataManager { get; set; }
-
- public static IChannelManager ChannelManager { get; set; }
-
- public static IMediaSourceManager MediaSourceManager { get; set; }
-
- /// <summary>
- /// Returns a <see cref="string" /> that represents this instance.
- /// </summary>
- /// <returns>A <see cref="string" /> that represents this instance.</returns>
- public override string ToString()
- {
- return Name;
- }
-
[JsonIgnore]
public bool IsLocked { get; set; }
@@ -611,7 +502,45 @@ namespace MediaBrowser.Controller.Entities
}
}
- private string _forcedSortName;
+ [JsonIgnore]
+ public bool EnableMediaSourceDisplay
+ {
+ get
+ {
+ if (SourceType == SourceType.Channel)
+ {
+ return ChannelManager.EnableMediaSourceDisplay(this);
+ }
+
+ return true;
+ }
+ }
+
+ [JsonIgnore]
+ public Guid ParentId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the logger.
+ /// </summary>
+ public static ILogger<BaseItem> Logger { get; set; }
+
+ public static ILibraryManager LibraryManager { get; set; }
+
+ public static IServerConfigurationManager ConfigurationManager { get; set; }
+
+ public static IProviderManager ProviderManager { get; set; }
+
+ public static ILocalizationManager LocalizationManager { get; set; }
+
+ public static IItemRepository ItemRepository { get; set; }
+
+ public static IFileSystem FileSystem { get; set; }
+
+ public static IUserDataManager UserDataManager { get; set; }
+
+ public static IChannelManager ChannelManager { get; set; }
+
+ public static IMediaSourceManager MediaSourceManager { get; set; }
/// <summary>
/// Gets or sets the name of the forced sort.
@@ -628,10 +557,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- private string _sortName;
- private Guid[] _themeSongIds;
- private Guid[] _themeVideoIds;
-
/// <summary>
/// Gets or sets the name of the sort.
/// </summary>
@@ -660,164 +585,6 @@ namespace MediaBrowser.Controller.Entities
set => _sortName = value;
}
- public string GetInternalMetadataPath()
- {
- var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath;
-
- return GetInternalMetadataPath(basePath);
- }
-
- protected virtual string GetInternalMetadataPath(string basePath)
- {
- if (SourceType == SourceType.Channel)
- {
- return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture));
- }
-
- ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture);
-
- return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString);
- }
-
- /// <summary>
- /// Creates the name of the sort.
- /// </summary>
- /// <returns>System.String.</returns>
- protected virtual string CreateSortName()
- {
- if (Name == null)
- {
- return null; // some items may not have name filled in properly
- }
-
- if (!EnableAlphaNumericSorting)
- {
- return Name.TrimStart();
- }
-
- var sortable = Name.Trim().ToLowerInvariant();
-
- foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
- {
- sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal);
- }
-
- foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
- {
- sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal);
- }
-
- foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
- {
- // Remove from beginning if a space follows
- if (sortable.StartsWith(search + " ", StringComparison.Ordinal))
- {
- sortable = sortable.Remove(0, search.Length + 1);
- }
-
- // Remove from middle if surrounded by spaces
- sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal);
-
- // Remove from end if followed by a space
- if (sortable.EndsWith(" " + search, StringComparison.Ordinal))
- {
- sortable = sortable.Remove(sortable.Length - (search.Length + 1));
- }
- }
-
- return ModifySortChunks(sortable);
- }
-
- private string ModifySortChunks(string name)
- {
- var chunks = GetSortChunks(name);
-
- var builder = new StringBuilder();
-
- foreach (var chunk in chunks)
- {
- var chunkBuilder = chunk.Item1;
-
- // This chunk is numeric
- if (chunk.Item2)
- {
- while (chunkBuilder.Length < 10)
- {
- chunkBuilder.Insert(0, '0');
- }
- }
-
- builder.Append(chunkBuilder);
- }
-
- // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
- return builder.ToString().RemoveDiacritics();
- }
-
- [JsonIgnore]
- public bool EnableMediaSourceDisplay
- {
- get
- {
- if (SourceType == SourceType.Channel)
- {
- return ChannelManager.EnableMediaSourceDisplay(this);
- }
-
- return true;
- }
- }
-
- [JsonIgnore]
- public Guid ParentId { get; set; }
-
- public void SetParent(Folder parent)
- {
- ParentId = parent == null ? Guid.Empty : parent.Id;
- }
-
- public BaseItem GetParent()
- {
- var parentId = ParentId;
- if (!parentId.Equals(Guid.Empty))
- {
- return LibraryManager.GetItemById(parentId);
- }
-
- return null;
- }
-
- public IEnumerable<BaseItem> GetParents()
- {
- var parent = GetParent();
-
- while (parent != null)
- {
- yield return parent;
-
- parent = parent.GetParent();
- }
- }
-
- /// <summary>
- /// Finds a parent of a given type.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns>``0.</returns>
- public T FindParent<T>()
- where T : Folder
- {
- foreach (var parent in GetParents())
- {
- if (parent is T item)
- {
- return item;
- }
- }
-
- return null;
- }
-
[JsonIgnore]
public virtual Guid DisplayParentId
{
@@ -1001,6 +768,349 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
+ /// Gets or sets the provider ids.
+ /// </summary>
+ /// <value>The provider ids.</value>
+ [JsonIgnore]
+ public Dictionary<string, string> ProviderIds { get; set; }
+
+ [JsonIgnore]
+ public virtual Folder LatestItemsIndexContainer => null;
+
+ [JsonIgnore]
+ public string PresentationUniqueKey { get; set; }
+
+ [JsonIgnore]
+ public virtual bool EnableRememberingTrackSelections => true;
+
+ [JsonIgnore]
+ public virtual bool IsTopParent
+ {
+ get
+ {
+ if (this is BasePluginFolder || this is Channel)
+ {
+ return true;
+ }
+
+ if (this is IHasCollectionType view)
+ {
+ if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ if (GetParent() is AggregateFolder)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ [JsonIgnore]
+ public virtual bool SupportsAncestors => true;
+
+ [JsonIgnore]
+ public virtual bool StopRefreshIfLocalMetadataFound => true;
+
+ [JsonIgnore]
+ protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol;
+
+ [JsonIgnore]
+ public virtual bool SupportsPeople => false;
+
+ [JsonIgnore]
+ public virtual bool SupportsThemeMedia => false;
+
+ [JsonIgnore]
+ public virtual bool SupportsInheritedParentImages => false;
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is folder.
+ /// </summary>
+ /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
+ [JsonIgnore]
+ public virtual bool IsFolder => false;
+
+ [JsonIgnore]
+ public virtual bool IsDisplayedAsFolder => false;
+
+ /// <summary>
+ /// Gets or sets the remote trailers.
+ /// </summary>
+ /// <value>The remote trailers.</value>
+ public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
+
+ public virtual bool SupportsExternalTransfer => false;
+
+ public virtual double GetDefaultPrimaryImageAspectRatio()
+ {
+ return 0;
+ }
+
+ public virtual string CreatePresentationUniqueKey()
+ {
+ return Id.ToString("N", CultureInfo.InvariantCulture);
+ }
+
+ public bool IsPathProtocol(MediaProtocol protocol)
+ {
+ var current = PathProtocol;
+
+ return current.HasValue && current.Value == protocol;
+ }
+
+ private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1)
+ {
+ var list = new List<Tuple<StringBuilder, bool>>();
+
+ int thisMarker = 0;
+
+ while (thisMarker < s1.Length)
+ {
+ char thisCh = s1[thisMarker];
+
+ var thisChunk = new StringBuilder();
+ bool isNumeric = char.IsDigit(thisCh);
+
+ while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric)
+ {
+ thisChunk.Append(thisCh);
+ thisMarker++;
+
+ if (thisMarker < s1.Length)
+ {
+ thisCh = s1[thisMarker];
+ }
+ }
+
+ list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric));
+ }
+
+ return list;
+ }
+
+ public virtual bool CanDelete()
+ {
+ if (SourceType == SourceType.Channel)
+ {
+ return ChannelManager.CanDelete(this);
+ }
+
+ return IsFileProtocol;
+ }
+
+ public virtual bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders)
+ {
+ if (user.HasPermission(PermissionKind.EnableContentDeletion))
+ {
+ return true;
+ }
+
+ var allowed = user.GetPreferenceValues<Guid>(PreferenceKind.EnableContentDeletionFromFolders);
+
+ if (SourceType == SourceType.Channel)
+ {
+ return allowed.Contains(ChannelId);
+ }
+ else
+ {
+ var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders);
+
+ foreach (var folder in collectionFolders)
+ {
+ if (allowed.Contains(folder.Id))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public BaseItem GetOwner()
+ {
+ var ownerId = OwnerId;
+ return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId);
+ }
+
+ public bool CanDelete(User user, List<Folder> allCollectionFolders)
+ {
+ return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders);
+ }
+
+ public bool CanDelete(User user)
+ {
+ var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList();
+
+ return CanDelete(user, allCollectionFolders);
+ }
+
+ public virtual bool CanDownload()
+ {
+ return false;
+ }
+
+ public virtual bool IsAuthorizedToDownload(User user)
+ {
+ return user.HasPermission(PermissionKind.EnableContentDownloading);
+ }
+
+ public bool CanDownload(User user)
+ {
+ return CanDownload() && IsAuthorizedToDownload(user);
+ }
+
+ /// <summary>
+ /// Returns a <see cref="string" /> that represents this instance.
+ /// </summary>
+ /// <returns>A <see cref="string" /> that represents this instance.</returns>
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ public string GetInternalMetadataPath()
+ {
+ var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath;
+
+ return GetInternalMetadataPath(basePath);
+ }
+
+ protected virtual string GetInternalMetadataPath(string basePath)
+ {
+ if (SourceType == SourceType.Channel)
+ {
+ return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture));
+ }
+
+ ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture);
+
+ return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString);
+ }
+
+ /// <summary>
+ /// Creates the name of the sort.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ protected virtual string CreateSortName()
+ {
+ if (Name == null)
+ {
+ return null; // some items may not have name filled in properly
+ }
+
+ if (!EnableAlphaNumericSorting)
+ {
+ return Name.TrimStart();
+ }
+
+ var sortable = Name.Trim().ToLowerInvariant();
+
+ foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
+ {
+ sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal);
+ }
+
+ foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
+ {
+ sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal);
+ }
+
+ foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
+ {
+ // Remove from beginning if a space follows
+ if (sortable.StartsWith(search + " ", StringComparison.Ordinal))
+ {
+ sortable = sortable.Remove(0, search.Length + 1);
+ }
+
+ // Remove from middle if surrounded by spaces
+ sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal);
+
+ // Remove from end if followed by a space
+ if (sortable.EndsWith(" " + search, StringComparison.Ordinal))
+ {
+ sortable = sortable.Remove(sortable.Length - (search.Length + 1));
+ }
+ }
+
+ return ModifySortChunks(sortable);
+ }
+
+ private string ModifySortChunks(string name)
+ {
+ var chunks = GetSortChunks(name);
+
+ var builder = new StringBuilder();
+
+ foreach (var chunk in chunks)
+ {
+ var chunkBuilder = chunk.Item1;
+
+ // This chunk is numeric
+ if (chunk.Item2)
+ {
+ while (chunkBuilder.Length < 10)
+ {
+ chunkBuilder.Insert(0, '0');
+ }
+ }
+
+ builder.Append(chunkBuilder);
+ }
+
+ // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
+ return builder.ToString().RemoveDiacritics();
+ }
+
+ public BaseItem GetParent()
+ {
+ var parentId = ParentId;
+ if (!parentId.Equals(Guid.Empty))
+ {
+ return LibraryManager.GetItemById(parentId);
+ }
+
+ return null;
+ }
+
+ public IEnumerable<BaseItem> GetParents()
+ {
+ var parent = GetParent();
+
+ while (parent != null)
+ {
+ yield return parent;
+
+ parent = parent.GetParent();
+ }
+ }
+
+ /// <summary>
+ /// Finds a parent of a given type.
+ /// </summary>
+ /// <typeparam name="T">Type of parent.</typeparam>
+ /// <returns>``0.</returns>
+ public T FindParent<T>()
+ where T : Folder
+ {
+ foreach (var parent in GetParents())
+ {
+ if (parent is T item)
+ {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
/// Gets the play access.
/// </summary>
/// <param name="user">The user.</param>
@@ -1405,14 +1515,46 @@ namespace MediaBrowser.Controller.Entities
}
}
- [JsonIgnore]
- protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol;
+ protected bool IsVisibleStandaloneInternal(User user, bool checkFolders)
+ {
+ if (!IsVisible(user))
+ {
+ return false;
+ }
- [JsonIgnore]
- public virtual bool SupportsPeople => false;
+ if (GetParents().Any(i => !i.IsVisible(user)))
+ {
+ return false;
+ }
- [JsonIgnore]
- public virtual bool SupportsThemeMedia => false;
+ if (checkFolders)
+ {
+ var topParent = GetParents().LastOrDefault() ?? this;
+
+ if (string.IsNullOrEmpty(topParent.Path))
+ {
+ return true;
+ }
+
+ var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList();
+
+ if (itemCollectionFolders.Count > 0)
+ {
+ var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList();
+ if (!itemCollectionFolders.Any(userCollectionFolders.Contains))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public void SetParent(Folder parent)
+ {
+ ParentId = parent == null ? Guid.Empty : parent.Id;
+ }
/// <summary>
/// Refreshes owned items such as trailers, theme videos, special features, etc.
@@ -1609,29 +1751,6 @@ namespace MediaBrowser.Controller.Entities
return themeSongsChanged;
}
- /// <summary>
- /// Gets or sets the provider ids.
- /// </summary>
- /// <value>The provider ids.</value>
- [JsonIgnore]
- public Dictionary<string, string> ProviderIds { get; set; }
-
- [JsonIgnore]
- public virtual Folder LatestItemsIndexContainer => null;
-
- public virtual double GetDefaultPrimaryImageAspectRatio()
- {
- return 0;
- }
-
- public virtual string CreatePresentationUniqueKey()
- {
- return Id.ToString("N", CultureInfo.InvariantCulture);
- }
-
- [JsonIgnore]
- public string PresentationUniqueKey { get; set; }
-
public string GetPresentationUniqueKey()
{
return PresentationUniqueKey ?? CreatePresentationUniqueKey();
@@ -1929,55 +2048,6 @@ namespace MediaBrowser.Controller.Entities
return IsVisibleStandaloneInternal(user, true);
}
- [JsonIgnore]
- public virtual bool SupportsInheritedParentImages => false;
-
- protected bool IsVisibleStandaloneInternal(User user, bool checkFolders)
- {
- if (!IsVisible(user))
- {
- return false;
- }
-
- if (GetParents().Any(i => !i.IsVisible(user)))
- {
- return false;
- }
-
- if (checkFolders)
- {
- var topParent = GetParents().LastOrDefault() ?? this;
-
- if (string.IsNullOrEmpty(topParent.Path))
- {
- return true;
- }
-
- var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList();
-
- if (itemCollectionFolders.Count > 0)
- {
- var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList();
- if (!itemCollectionFolders.Any(userCollectionFolders.Contains))
- {
- return false;
- }
- }
- }
-
- return true;
- }
-
- /// <summary>
- /// Gets a value indicating whether this instance is folder.
- /// </summary>
- /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
- [JsonIgnore]
- public virtual bool IsFolder => false;
-
- [JsonIgnore]
- public virtual bool IsDisplayedAsFolder => false;
-
public virtual string GetClientTypeName()
{
if (IsFolder && SourceType == SourceType.Channel && !(this is Channel))
@@ -2066,14 +2136,11 @@ namespace MediaBrowser.Controller.Entities
return null;
}
- [JsonIgnore]
- public virtual bool EnableRememberingTrackSelections => true;
-
/// <summary>
/// Adds a studio to the item.
/// </summary>
/// <param name="name">The name.</param>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentNullException">Throws if name is null.</exception>
public void AddStudio(string name)
{
if (string.IsNullOrEmpty(name))
@@ -2109,7 +2176,7 @@ namespace MediaBrowser.Controller.Entities
/// Adds a genre to the item.
/// </summary>
/// <param name="name">The name.</param>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentNullException">Throwns if name is null.</exception>
public void AddGenre(string name)
{
if (string.IsNullOrEmpty(name))
@@ -2132,8 +2199,7 @@ namespace MediaBrowser.Controller.Entities
/// <param name="user">The user.</param>
/// <param name="datePlayed">The date played.</param>
/// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
- /// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentNullException">Throws if user is null.</exception>
public virtual void MarkPlayed(
User user,
DateTime? datePlayed,
@@ -2170,8 +2236,7 @@ namespace MediaBrowser.Controller.Entities
/// Marks the unplayed.
/// </summary>
/// <param name="user">The user.</param>
- /// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentNullException">Throws if user is null.</exception>
public virtual void MarkUnplayed(User user)
{
if (user == null)
@@ -2271,6 +2336,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <param name="type">The type.</param>
/// <param name="index">The index.</param>
+ /// <returns>A task.</returns>
public async Task DeleteImageAsync(ImageType type, int index)
{
var info = GetImageInfo(type, index);
@@ -2308,6 +2374,8 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Validates that images within the item are still on the filesystem.
/// </summary>
+ /// <param name="directoryService">The directory service to use.</param>
+ /// <returns><c>true</c> if the images validate, <c>false</c> if not.</returns>
public bool ValidateImages(IDirectoryService directoryService)
{
var allFiles = ImageInfos
@@ -2335,7 +2403,6 @@ namespace MediaBrowser.Controller.Entities
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>System.String.</returns>
- /// <exception cref="InvalidOperationException"> </exception>
/// <exception cref="ArgumentNullException">Item is null.</exception>
public string GetImagePath(ImageType imageType, int imageIndex)
=> GetImageInfo(imageType, imageIndex)?.Path;
@@ -2821,39 +2888,6 @@ namespace MediaBrowser.Controller.Entities
return GetParents().FirstOrDefault(parent => parent.IsTopParent);
}
- [JsonIgnore]
- public virtual bool IsTopParent
- {
- get
- {
- if (this is BasePluginFolder || this is Channel)
- {
- return true;
- }
-
- if (this is IHasCollectionType view)
- {
- if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
-
- if (GetParent() is AggregateFolder)
- {
- return true;
- }
-
- return false;
- }
- }
-
- [JsonIgnore]
- public virtual bool SupportsAncestors => true;
-
- [JsonIgnore]
- public virtual bool StopRefreshIfLocalMetadataFound => true;
-
public virtual IEnumerable<Guid> GetIdsForAncestorQuery()
{
return new[] { Id };
@@ -2888,6 +2922,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Updates the official rating based on content and returns true or false indicating if it changed.
/// </summary>
+ /// <param name="children">Media children.</param>
/// <returns><c>true</c> if the rating was updated; otherwise <c>false</c>.</returns>
public bool UpdateRatingToItems(IList<BaseItem> children)
{
@@ -2921,12 +2956,6 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Gets or sets the remote trailers.
- /// </summary>
- /// <value>The remote trailers.</value>
- public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
-
- /// <summary>
/// Get all extras associated with this item, sorted by <see cref="SortName"/>.
/// </summary>
/// <returns>An enumerable containing the items.</returns>
@@ -2963,39 +2992,11 @@ namespace MediaBrowser.Controller.Entities
}
}
- public virtual bool IsHD => Height >= 720;
-
- public bool IsShortcut { get; set; }
-
- public string ShortcutPath { get; set; }
-
- public int Width { get; set; }
-
- public int Height { get; set; }
-
- public Guid[] ExtraIds { get; set; }
-
public virtual long GetRunTimeTicksForPlayState()
{
return RunTimeTicks ?? 0;
}
- /// <summary>
- /// Extra types that should be counted and displayed as "Special Features" in the UI.
- /// </summary>
- public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType>
- {
- Model.Entities.ExtraType.Unknown,
- Model.Entities.ExtraType.BehindTheScenes,
- Model.Entities.ExtraType.Clip,
- Model.Entities.ExtraType.DeletedScene,
- Model.Entities.ExtraType.Interview,
- Model.Entities.ExtraType.Sample,
- Model.Entities.ExtraType.Scene
- };
-
- public virtual bool SupportsExternalTransfer => false;
-
/// <inheritdoc />
public override bool Equals(object obj)
{
diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
index 89ad392a4..e88121212 100644
--- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
+++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
@@ -64,6 +64,8 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <param name="source">The source object.</param>
/// <param name="dest">The destination object.</param>
+ /// <typeparam name="T">Source type.</typeparam>
+ /// <typeparam name="TU">Destination type.</typeparam>
public static void DeepCopy<T, TU>(this T source, TU dest)
where T : BaseItem
where TU : BaseItem
@@ -109,6 +111,9 @@ namespace MediaBrowser.Controller.Entities
/// Copies all properties on newly created object. Skips properties that do not exist.
/// </summary>
/// <param name="source">The source object.</param>
+ /// <typeparam name="T">Source type.</typeparam>
+ /// <typeparam name="TU">Destination type.</typeparam>
+ /// <returns>Destination object.</returns>
public static TU DeepCopy<T, TU>(this T source)
where T : BaseItem
where TU : BaseItem, new()
diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
index 1bd25042f..272a37df1 100644
--- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs
+++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
@@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public virtual string CollectionType => null;
+ [JsonIgnore]
+ public override bool SupportsInheritedParentImages => false;
+
+ [JsonIgnore]
+ public override bool SupportsPeople => false;
+
public override bool CanDelete()
{
return false;
@@ -24,11 +30,5 @@ namespace MediaBrowser.Controller.Entities
{
return true;
}
-
- [JsonIgnore]
- public override bool SupportsInheritedParentImages => false;
-
- [JsonIgnore]
- public override bool SupportsPeople => false;
}
}
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 4f367fe2b..0fb4771dd 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -41,6 +41,23 @@ namespace MediaBrowser.Controller.Entities
PhysicalFolderIds = Array.Empty<Guid>();
}
+ /// <summary>
+ /// Gets the display preferences id.
+ /// </summary>
+ /// <remarks>
+ /// Allow different display preferences for each collection folder.
+ /// </remarks>
+ /// <value>The display prefs id.</value>
+ [JsonIgnore]
+ public override Guid DisplayPreferencesId => Id;
+
+ [JsonIgnore]
+ public override string[] PhysicalLocations => PhysicalLocationsList;
+
+ public string[] PhysicalLocationsList { get; set; }
+
+ public Guid[] PhysicalFolderIds { get; set; }
+
public static IXmlSerializer XmlSerializer { get; set; }
public static IServerApplicationHost ApplicationHost { get; set; }
@@ -63,6 +80,9 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public override IEnumerable<BaseItem> Children => GetActualChildren();
+ [JsonIgnore]
+ public override bool SupportsPeople => false;
+
public override bool CanDelete()
{
return false;
@@ -160,23 +180,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- /// <summary>
- /// Gets the display preferences id.
- /// </summary>
- /// <remarks>
- /// Allow different display preferences for each collection folder.
- /// </remarks>
- /// <value>The display prefs id.</value>
- [JsonIgnore]
- public override Guid DisplayPreferencesId => Id;
-
- [JsonIgnore]
- public override string[] PhysicalLocations => PhysicalLocationsList;
-
- public string[] PhysicalLocationsList { get; set; }
-
- public Guid[] PhysicalFolderIds { get; set; }
-
public override bool IsSaveLocalMetadataEnabled()
{
return true;
@@ -373,8 +376,5 @@ namespace MediaBrowser.Controller.Entities
return result;
}
-
- [JsonIgnore]
- public override bool SupportsPeople => false;
}
}
diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs
index d8bc0069c..9ce8eebe3 100644
--- a/MediaBrowser.Controller/Entities/Extensions.cs
+++ b/MediaBrowser.Controller/Entities/Extensions.cs
@@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Adds the trailer URL.
/// </summary>
+ /// <param name="item">Media item.</param>
+ /// <param name="url">Trailer URL.</param>
public static void AddTrailerUrl(this BaseItem item, string url)
{
if (string.IsNullOrEmpty(url))
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 34ad5bbbb..d45a02cf2 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1669,7 +1669,6 @@ namespace MediaBrowser.Controller.Entities
/// <param name="user">The user.</param>
/// <param name="datePlayed">The date played.</param>
/// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
- /// <returns>Task.</returns>
public override void MarkPlayed(
User user,
DateTime? datePlayed,
@@ -1711,7 +1710,6 @@ namespace MediaBrowser.Controller.Entities
/// Marks the unplayed.
/// </summary>
/// <param name="user">The user.</param>
- /// <returns>Task.</returns>
public override void MarkUnplayed(User user)
{
var itemsResult = GetItemList(new InternalItemsQuery
diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
index 98c3b3edf..90d9bdd2d 100644
--- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs
+++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
@@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Gets the media sources.
/// </summary>
+ /// <param name="enablePathSubstitution"><c>true</c> to enable path substitution, <c>false</c> to not.</param>
+ /// <returns>A list of media sources.</returns>
List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution);
List<MediaStream> GetMediaStreams();
diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs
index 2bd9ded33..f4271678d 100644
--- a/MediaBrowser.Controller/Entities/IHasTrailers.cs
+++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs
@@ -39,6 +39,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Gets the trailer count.
/// </summary>
+ /// <param name="item">Media item.</param>
/// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
public static int GetTrailerCount(this IHasTrailers item)
=> item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count;
@@ -46,6 +47,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Gets the trailer ids.
/// </summary>
+ /// <param name="item">Media item.</param>
/// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item)
{
@@ -70,6 +72,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Gets the trailers.
/// </summary>
+ /// <param name="item">Media item.</param>
/// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns>
public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item)
{
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index b9e37269e..045c1b89f 100644
--- a/MediaBrowser.Controller/Entities/Person.cs
+++ b/MediaBrowser.Controller/Entities/Person.cs
@@ -129,6 +129,8 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
+ /// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param>
+ /// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs
index 556624e14..c8feb1c94 100644
--- a/MediaBrowser.Controller/Entities/Studio.cs
+++ b/MediaBrowser.Controller/Entities/Studio.cs
@@ -105,6 +105,8 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
+ /// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param>
+ /// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 31c179bca..27c3ff81b 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -49,12 +49,6 @@ namespace MediaBrowser.Controller.Entities.TV
/// <value>The index number.</value>
public int? IndexNumberEnd { get; set; }
- public string FindSeriesSortName()
- {
- var series = Series;
- return series == null ? SeriesName : series.SortName;
- }
-
[JsonIgnore]
protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1;
@@ -76,45 +70,6 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore]
protected override bool EnableDefaultVideoUserDataKeys => false;
- public override double GetDefaultPrimaryImageAspectRatio()
- {
- // hack for tv plugins
- if (SourceType == SourceType.Channel)
- {
- return 0;
- }
-
- return 16.0 / 9;
- }
-
- public override List<string> GetUserDataKeys()
- {
- var list = base.GetUserDataKeys();
-
- var series = Series;
- if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
- {
- var seriesUserDataKeys = series.GetUserDataKeys();
- var take = seriesUserDataKeys.Count;
- if (seriesUserDataKeys.Count > 1)
- {
- take--;
- }
-
- var newList = seriesUserDataKeys.GetRange(0, take);
- var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
- for (int i = 0; i < take; i++)
- {
- newList[i] = newList[i] + suffix;
- }
-
- newList.AddRange(list);
- list = newList;
- }
-
- return list;
- }
-
/// <summary>
/// Gets the Episode's Series Instance.
/// </summary>
@@ -161,6 +116,74 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore]
public string SeasonName { get; set; }
+ [JsonIgnore]
+ public override bool SupportsRemoteImageDownloading
+ {
+ get
+ {
+ if (IsMissingEpisode)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ [JsonIgnore]
+ public bool IsMissingEpisode => LocationType == LocationType.Virtual;
+
+ [JsonIgnore]
+ public Guid SeasonId { get; set; }
+
+ [JsonIgnore]
+ public Guid SeriesId { get; set; }
+
+ public string FindSeriesSortName()
+ {
+ var series = Series;
+ return series == null ? SeriesName : series.SortName;
+ }
+
+ public override double GetDefaultPrimaryImageAspectRatio()
+ {
+ // hack for tv plugins
+ if (SourceType == SourceType.Channel)
+ {
+ return 0;
+ }
+
+ return 16.0 / 9;
+ }
+
+ public override List<string> GetUserDataKeys()
+ {
+ var list = base.GetUserDataKeys();
+
+ var series = Series;
+ if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
+ {
+ var seriesUserDataKeys = series.GetUserDataKeys();
+ var take = seriesUserDataKeys.Count;
+ if (seriesUserDataKeys.Count > 1)
+ {
+ take--;
+ }
+
+ var newList = seriesUserDataKeys.GetRange(0, take);
+ var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
+ for (int i = 0; i < take; i++)
+ {
+ newList[i] = newList[i] + suffix;
+ }
+
+ newList.AddRange(list);
+ list = newList;
+ }
+
+ return list;
+ }
+
public string FindSeriesPresentationUniqueKey()
{
var series = Series;
@@ -242,29 +265,6 @@ namespace MediaBrowser.Controller.Entities.TV
return false;
}
- [JsonIgnore]
- public override bool SupportsRemoteImageDownloading
- {
- get
- {
- if (IsMissingEpisode)
- {
- return false;
- }
-
- return true;
- }
- }
-
- [JsonIgnore]
- public bool IsMissingEpisode => LocationType == LocationType.Virtual;
-
- [JsonIgnore]
- public Guid SeasonId { get; set; }
-
- [JsonIgnore]
- public Guid SeriesId { get; set; }
-
public Guid FindSeriesId()
{
var series = FindParent<Series>();
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index aa62bb35b..926c7b045 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -38,6 +38,50 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore]
public override Guid DisplayParentId => SeriesId;
+ /// <summary>
+ /// Gets this Episode's Series Instance.
+ /// </summary>
+ /// <value>The series.</value>
+ [JsonIgnore]
+ public Series Series
+ {
+ get
+ {
+ var seriesId = SeriesId;
+ if (seriesId == Guid.Empty)
+ {
+ seriesId = FindSeriesId();
+ }
+
+ return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series);
+ }
+ }
+
+ [JsonIgnore]
+ public string SeriesPath
+ {
+ get
+ {
+ var series = Series;
+
+ if (series != null)
+ {
+ return series.Path;
+ }
+
+ return System.IO.Path.GetDirectoryName(Path);
+ }
+ }
+
+ [JsonIgnore]
+ public string SeriesPresentationUniqueKey { get; set; }
+
+ [JsonIgnore]
+ public string SeriesName { get; set; }
+
+ [JsonIgnore]
+ public Guid SeriesId { get; set; }
+
public override double GetDefaultPrimaryImageAspectRatio()
{
double value = 2;
@@ -80,41 +124,6 @@ namespace MediaBrowser.Controller.Entities.TV
return result;
}
- /// <summary>
- /// Gets this Episode's Series Instance.
- /// </summary>
- /// <value>The series.</value>
- [JsonIgnore]
- public Series Series
- {
- get
- {
- var seriesId = SeriesId;
- if (seriesId == Guid.Empty)
- {
- seriesId = FindSeriesId();
- }
-
- return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series);
- }
- }
-
- [JsonIgnore]
- public string SeriesPath
- {
- get
- {
- var series = Series;
-
- if (series != null)
- {
- return series.Path;
- }
-
- return System.IO.Path.GetDirectoryName(Path);
- }
- }
-
public override string CreatePresentationUniqueKey()
{
if (IndexNumber.HasValue)
@@ -157,6 +166,9 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Gets the episodes.
/// </summary>
+ /// <param name="user">The user.</param>
+ /// <param name="options">The options to use.</param>
+ /// <returns>Set of episodes.</returns>
public List<BaseItem> GetEpisodes(User user, DtoOptions options)
{
return GetEpisodes(Series, user, options);
@@ -193,15 +205,6 @@ namespace MediaBrowser.Controller.Entities.TV
return UnratedItem.Series;
}
- [JsonIgnore]
- public string SeriesPresentationUniqueKey { get; set; }
-
- [JsonIgnore]
- public string SeriesName { get; set; }
-
- [JsonIgnore]
- public Guid SeriesId { get; set; }
-
public string FindSeriesPresentationUniqueKey()
{
var series = Series;
@@ -241,6 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
+ /// <param name="replaceAllMetadata"><c>true</c> to replace metdata, <c>false</c> to not.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 44d07b4a4..beda504b9 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -72,6 +72,9 @@ namespace MediaBrowser.Controller.Entities.TV
/// <value>The status.</value>
public SeriesStatus? Status { get; set; }
+ [JsonIgnore]
+ public override bool StopRefreshIfLocalMetadataFound => false;
+
public override double GetDefaultPrimaryImageAspectRatio()
{
double value = 2;
@@ -394,6 +397,10 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Filters the episodes by season.
/// </summary>
+ /// <param name="episodes">The episodes.</param>
+ /// <param name="parentSeason">The season.</param>
+ /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
+ /// <returns>The set of episodes.</returns>
public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, bool includeSpecials)
{
var seasonNumber = parentSeason.IndexNumber;
@@ -424,6 +431,10 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Filters the episodes by season.
/// </summary>
+ /// <param name="episodes">The episodes.</param>
+ /// <param name="seasonNumber">The season.</param>
+ /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
+ /// <returns>The set of episodes.</returns>
public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
{
if (!includeSpecials || seasonNumber < 1)
@@ -499,8 +510,5 @@ namespace MediaBrowser.Controller.Entities.TV
return list;
}
-
- [JsonIgnore]
- public override bool StopRefreshIfLocalMetadataFound => false;
}
}
diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs
index 6ab2116d7..50ba9ef30 100644
--- a/MediaBrowser.Controller/Entities/UserItemData.cs
+++ b/MediaBrowser.Controller/Entities/UserItemData.cs
@@ -12,6 +12,13 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class UserItemData
{
+ public const double MinLikeValue = 6.5;
+
+ /// <summary>
+ /// The _rating.
+ /// </summary>
+ private double? _rating;
+
/// <summary>
/// Gets or sets the user id.
/// </summary>
@@ -25,11 +32,6 @@ namespace MediaBrowser.Controller.Entities
public string Key { get; set; }
/// <summary>
- /// The _rating.
- /// </summary>
- private double? _rating;
-
- /// <summary>
/// Gets or sets the users 0-10 rating.
/// </summary>
/// <value>The rating.</value>
@@ -93,8 +95,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The index of the subtitle stream.</value>
public int? SubtitleStreamIndex { get; set; }
- public const double MinLikeValue = 6.5;
-
/// <summary>
/// Gets or sets a value indicating whether the item is liked or not.
/// This should never be serialized.
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index 2b15a52f0..f3bf4749d 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -21,8 +21,28 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class UserRootFolder : Folder
{
- private List<Guid> _childrenIds = null;
private readonly object _childIdsLock = new object();
+ private List<Guid> _childrenIds = null;
+
+ [JsonIgnore]
+ public override bool SupportsInheritedParentImages => false;
+
+ [JsonIgnore]
+ public override bool SupportsPlayedStatus => false;
+
+ [JsonIgnore]
+ protected override bool SupportsShortcutChildren => true;
+
+ [JsonIgnore]
+ public override bool IsPreSorted => true;
+
+ private void ClearCache()
+ {
+ lock (_childIdsLock)
+ {
+ _childrenIds = null;
+ }
+ }
protected override List<BaseItem> LoadChildren()
{
@@ -39,20 +59,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- [JsonIgnore]
- public override bool SupportsInheritedParentImages => false;
-
- [JsonIgnore]
- public override bool SupportsPlayedStatus => false;
-
- private void ClearCache()
- {
- lock (_childIdsLock)
- {
- _childrenIds = null;
- }
- }
-
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
{
if (query.Recursive)
@@ -74,12 +80,6 @@ namespace MediaBrowser.Controller.Entities
return GetChildren(user, true).Count;
}
- [JsonIgnore]
- protected override bool SupportsShortcutChildren => true;
-
- [JsonIgnore]
- public override bool IsPreSorted => true;
-
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
{
var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList();
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index d05b5df2f..7dd95b85c 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities
ISupportsPlaceHolders,
IHasMediaSources
{
+ public Video()
+ {
+ AdditionalParts = Array.Empty<string>();
+ LocalAlternateVersions = Array.Empty<string>();
+ SubtitleFiles = Array.Empty<string>();
+ LinkedAlternateVersions = Array.Empty<LinkedChild>();
+ }
+
[JsonIgnore]
public string PrimaryVersionId { get; set; }
@@ -74,30 +82,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- public void SetPrimaryVersionId(string id)
- {
- if (string.IsNullOrEmpty(id))
- {
- PrimaryVersionId = null;
- }
- else
- {
- PrimaryVersionId = id;
- }
-
- PresentationUniqueKey = CreatePresentationUniqueKey();
- }
-
- public override string CreatePresentationUniqueKey()
- {
- if (!string.IsNullOrEmpty(PrimaryVersionId))
- {
- return PrimaryVersionId;
- }
-
- return base.CreatePresentationUniqueKey();
- }
-
[JsonIgnore]
public override bool SupportsThemeMedia => true;
@@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The aspect ratio.</value>
public string AspectRatio { get; set; }
- public Video()
- {
- AdditionalParts = Array.Empty<string>();
- LocalAlternateVersions = Array.Empty<string>();
- SubtitleFiles = Array.Empty<string>();
- LinkedAlternateVersions = Array.Empty<LinkedChild>();
- }
-
- public override bool CanDownload()
- {
- if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
- {
- return false;
- }
-
- return IsFileProtocol;
- }
-
[JsonIgnore]
public override bool SupportsAddingToPlaylist => true;
@@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
- public IEnumerable<Guid> GetAdditionalPartIds()
- {
- return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
- }
-
- public IEnumerable<Guid> GetLocalAlternateVersionIds()
- {
- return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
- }
-
public static ILiveTvManager LiveTvManager { get; set; }
[JsonIgnore]
@@ -222,37 +178,77 @@ namespace MediaBrowser.Controller.Entities
}
}
- protected override bool IsActiveRecording()
+ [JsonIgnore]
+ public bool IsCompleteMedia
{
- return LiveTvManager.GetActiveRecordingInfo(Path) != null;
+ get
+ {
+ if (SourceType == SourceType.Channel)
+ {
+ return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase);
+ }
+
+ return !IsActiveRecording();
+ }
}
- public override bool CanDelete()
+ [JsonIgnore]
+ protected virtual bool EnableDefaultVideoUserDataKeys => true;
+
+ [JsonIgnore]
+ public override string ContainingFolderPath
{
- if (IsActiveRecording())
+ get
{
- return false;
- }
+ if (IsStacked)
+ {
+ return System.IO.Path.GetDirectoryName(Path);
+ }
- return base.CanDelete();
+ if (!IsPlaceHolder)
+ {
+ if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
+ {
+ return Path;
+ }
+ }
+
+ return base.ContainingFolderPath;
+ }
}
[JsonIgnore]
- public bool IsCompleteMedia
+ public override string FileNameWithoutExtension
{
get
{
- if (SourceType == SourceType.Channel)
+ if (IsFileProtocol)
{
- return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase);
+ if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
+ {
+ return System.IO.Path.GetFileName(Path);
+ }
+
+ return System.IO.Path.GetFileNameWithoutExtension(Path);
}
- return !IsActiveRecording();
+ return null;
}
}
+ /// <summary>
+ /// Gets a value indicating whether [is3 D].
+ /// </summary>
+ /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
[JsonIgnore]
- protected virtual bool EnableDefaultVideoUserDataKeys => true;
+ public bool Is3D => Video3DFormat.HasValue;
+
+ /// <summary>
+ /// Gets the type of the media.
+ /// </summary>
+ /// <value>The type of the media.</value>
+ [JsonIgnore]
+ public override string MediaType => Model.Entities.MediaType.Video;
public override List<string> GetUserDataKeys()
{
@@ -293,6 +289,65 @@ namespace MediaBrowser.Controller.Entities
return list;
}
+ public void SetPrimaryVersionId(string id)
+ {
+ if (string.IsNullOrEmpty(id))
+ {
+ PrimaryVersionId = null;
+ }
+ else
+ {
+ PrimaryVersionId = id;
+ }
+
+ PresentationUniqueKey = CreatePresentationUniqueKey();
+ }
+
+ public override string CreatePresentationUniqueKey()
+ {
+ if (!string.IsNullOrEmpty(PrimaryVersionId))
+ {
+ return PrimaryVersionId;
+ }
+
+ return base.CreatePresentationUniqueKey();
+ }
+
+ public override bool CanDownload()
+ {
+ if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
+ {
+ return false;
+ }
+
+ return IsFileProtocol;
+ }
+
+ protected override bool IsActiveRecording()
+ {
+ return LiveTvManager.GetActiveRecordingInfo(Path) != null;
+ }
+
+ public override bool CanDelete()
+ {
+ if (IsActiveRecording())
+ {
+ return false;
+ }
+
+ return base.CanDelete();
+ }
+
+ public IEnumerable<Guid> GetAdditionalPartIds()
+ {
+ return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
+ }
+
+ public IEnumerable<Guid> GetLocalAlternateVersionIds()
+ {
+ return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
+ }
+
private string GetUserDataKey(string providerId)
{
var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
@@ -328,47 +383,6 @@ namespace MediaBrowser.Controller.Entities
.OrderBy(i => i.SortName);
}
- [JsonIgnore]
- public override string ContainingFolderPath
- {
- get
- {
- if (IsStacked)
- {
- return System.IO.Path.GetDirectoryName(Path);
- }
-
- if (!IsPlaceHolder)
- {
- if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
- {
- return Path;
- }
- }
-
- return base.ContainingFolderPath;
- }
- }
-
- [JsonIgnore]
- public override string FileNameWithoutExtension
- {
- get
- {
- if (IsFileProtocol)
- {
- if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
- {
- return System.IO.Path.GetFileName(Path);
- }
-
- return System.IO.Path.GetFileNameWithoutExtension(Path);
- }
-
- return null;
- }
- }
-
internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
{
var updateType = base.UpdateFromResolvedItem(newItem);
@@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities
return updateType;
}
- /// <summary>
- /// Gets a value indicating whether [is3 D].
- /// </summary>
- /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
- [JsonIgnore]
- public bool Is3D => Video3DFormat.HasValue;
-
- /// <summary>
- /// Gets the type of the media.
- /// </summary>
- /// <value>The type of the media.</value>
- [JsonIgnore]
- public override string MediaType => Model.Entities.MediaType.Video;
-
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 9bae95a27..141bb91c5 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
@@ -16,9 +15,7 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index d8995ce74..0813a8e7d 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1306, SA1401
using System;
using System.Collections.Generic;
@@ -31,6 +31,21 @@ namespace MediaBrowser.Controller.Net
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
/// <summary>
+ /// The logger.
+ /// </summary>
+ protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger;
+
+ protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
+
+ Logger = logger;
+ }
+
+ /// <summary>
/// Gets the type used for the messages sent to the client.
/// </summary>
/// <value>The type.</value>
@@ -55,21 +70,6 @@ namespace MediaBrowser.Controller.Net
protected abstract Task<TReturnDataType> GetDataToSend();
/// <summary>
- /// The logger.
- /// </summary>
- protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger;
-
- protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger)
- {
- if (logger == null)
- {
- throw new ArgumentNullException(nameof(logger));
- }
-
- Logger = logger;
- }
-
- /// <summary>
/// Processes the message.
/// </summary>
/// <param name="message">The message.</param>
diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
index 5fa5834c8..c43acfb6d 100644
--- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
@@ -18,7 +18,6 @@ namespace MediaBrowser.Controller.Persistence
/// <param name="key">The key.</param>
/// <param name="userData">The user data.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken);
/// <summary>
diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs
index 3eaf23515..5e671a725 100644
--- a/MediaBrowser.Controller/Playlists/Playlist.cs
+++ b/MediaBrowser.Controller/Playlists/Playlist.cs
@@ -31,24 +31,18 @@ namespace MediaBrowser.Controller.Playlists
".zpl"
};
- public Guid OwnerUserId { get; set; }
-
- public Share[] Shares { get; set; }
-
public Playlist()
{
Shares = Array.Empty<Share>();
}
+ public Guid OwnerUserId { get; set; }
+
+ public Share[] Shares { get; set; }
+
[JsonIgnore]
public bool IsFile => IsPlaylistFile(Path);
- public static bool IsPlaylistFile(string path)
- {
- // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
- return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
- }
-
[JsonIgnore]
public override string ContainingFolderPath
{
@@ -80,6 +74,41 @@ namespace MediaBrowser.Controller.Playlists
[JsonIgnore]
public override bool SupportsCumulativeRunTimeTicks => true;
+ [JsonIgnore]
+ public override bool IsPreSorted => true;
+
+ public string PlaylistMediaType { get; set; }
+
+ [JsonIgnore]
+ public override string MediaType => PlaylistMediaType;
+
+ [JsonIgnore]
+ private bool IsSharedItem
+ {
+ get
+ {
+ var path = Path;
+
+ if (string.IsNullOrEmpty(path))
+ {
+ return false;
+ }
+
+ return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
+ }
+ }
+
+ public static bool IsPlaylistFile(string path)
+ {
+ // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
+ return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
+ }
+
+ public void SetMediaType(string value)
+ {
+ PlaylistMediaType = value;
+ }
+
public override double GetDefaultPrimaryImageAspectRatio()
{
return 1;
@@ -197,35 +226,6 @@ namespace MediaBrowser.Controller.Playlists
return new[] { item };
}
- [JsonIgnore]
- public override bool IsPreSorted => true;
-
- public string PlaylistMediaType { get; set; }
-
- [JsonIgnore]
- public override string MediaType => PlaylistMediaType;
-
- public void SetMediaType(string value)
- {
- PlaylistMediaType = value;
- }
-
- [JsonIgnore]
- private bool IsSharedItem
- {
- get
- {
- var path = Path;
-
- if (string.IsNullOrEmpty(path))
- {
- return false;
- }
-
- return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
- }
- }
-
public override bool IsVisible(User user)
{
if (!IsSharedItem)
diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs
index 75286eadc..b95d00aa3 100644
--- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs
@@ -14,17 +14,17 @@ namespace MediaBrowser.Controller.Resolvers
public interface IItemResolver
{
/// <summary>
+ /// Gets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ ResolverPriority Priority { get; }
+
+ /// <summary>
/// Resolves the path.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>BaseItem.</returns>
BaseItem ResolvePath(ItemResolveArgs args);
-
- /// <summary>
- /// Gets the priority.
- /// </summary>
- /// <value>The priority.</value>
- ResolverPriority Priority { get; }
}
public interface IMultiItemResolver
@@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Resolvers
public class MultiItemResolverResult
{
- public List<BaseItem> Items { get; set; }
-
- public List<FileSystemMetadata> ExtraFiles { get; set; }
-
public MultiItemResolverResult()
{
Items = new List<BaseItem>();
ExtraFiles = new List<FileSystemMetadata>();
}
+
+ public List<BaseItem> Items { get; set; }
+
+ public List<FileSystemMetadata> ExtraFiles { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
index 9e661cbe4..3330dd540 100644
--- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
+++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
@@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary>
/// Searches the subtitles.
/// </summary>
+ /// <param name="video">The video.</param>
+ /// <param name="language">Subtitle language.</param>
+ /// <param name="isPerfectMatch">Require perfect match.</param>
+ /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+ /// <returns>Subtitles, wrapped in task.</returns>
Task<RemoteSubtitleInfo[]> SearchSubtitles(
Video video,
string language,
@@ -47,11 +52,20 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary>
/// Downloads the subtitles.
/// </summary>
+ /// <param name="video">The video.</param>
+ /// <param name="subtitleId">Subtitle ID.</param>
+ /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+ /// <returns>A task.</returns>
Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken);
/// <summary>
/// Downloads the subtitles.
/// </summary>
+ /// <param name="video">The video.</param>
+ /// <param name="libraryOptions">Library options to use.</param>
+ /// <param name="subtitleId">Subtitle ID.</param>
+ /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+ /// <returns>A task.</returns>
Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken);
/// <summary>
@@ -73,11 +87,16 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary>
/// Deletes the subtitles.
/// </summary>
+ /// <param name="item">Media item.</param>
+ /// <param name="index">Subtitle index.</param>
+ /// <returns>A task.</returns>
Task DeleteSubtitles(BaseItem item, int index);
/// <summary>
/// Gets the providers.
/// </summary>
+ /// <param name="item">The media item.</param>
+ /// <returns>Subtitles providers.</returns>
SubtitleProviderInfo[] GetSupportedProviders(BaseItem item);
}
}
diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
index 0f7c47e76..767d87d46 100644
--- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
+++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
@@ -11,6 +11,15 @@ namespace MediaBrowser.Controller.Subtitles
{
public class SubtitleSearchRequest : IHasProviderIds
{
+ public SubtitleSearchRequest()
+ {
+ SearchAllProviders = true;
+ ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ DisabledSubtitleFetchers = Array.Empty<string>();
+ SubtitleFetcherOrder = Array.Empty<string>();
+ }
+
public string Language { get; set; }
public string TwoLetterISOLanguageName { get; set; }
@@ -42,14 +51,5 @@ namespace MediaBrowser.Controller.Subtitles
public string[] DisabledSubtitleFetchers { get; set; }
public string[] SubtitleFetcherOrder { get; set; }
-
- public SubtitleSearchRequest()
- {
- SearchAllProviders = true;
- ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- DisabledSubtitleFetchers = Array.Empty<string>();
- SubtitleFetcherOrder = Array.Empty<string>();
- }
}
}
diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
index baefeb39c..b213e7aa0 100644
--- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs
+++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
@@ -1,4 +1,3 @@
-#nullable disable
using System.Collections.Generic;
using System.Globalization;
using MediaBrowser.Model.Entities;
@@ -57,18 +56,10 @@ namespace MediaBrowser.Model.Globalization
IEnumerable<LocalizationOption> GetLocalizationOptions();
/// <summary>
- /// Checks if the string contains a character with the specified unicode category.
- /// </summary>
- /// <param name="value">The string.</param>
- /// <param name="category">The unicode category.</param>
- /// <returns>Wether or not the string contains a character with the specified unicode category.</returns>
- bool HasUnicodeCategory(string value, UnicodeCategory category);
-
- /// <summary>
/// Returns the correct <see cref="CultureInfo" /> for the given language.
/// </summary>
/// <param name="language">The language.</param>
/// <returns>The correct <see cref="CultureInfo" /> for the given language.</returns>
- CultureDto FindLanguageInfo(string language);
+ CultureDto? FindLanguageInfo(string language);
}
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index 0edb7f43c..f975278fb 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -148,80 +148,76 @@ namespace MediaBrowser.XbmcMetadata.Parsers
return;
}
- using (var fileStream = File.OpenRead(metadataFile))
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- item.ResetPeople();
-
- // Need to handle a url after the xml data
- // http://kodi.wiki/view/NFO_files/movies
+ item.ResetPeople();
- var xml = streamReader.ReadToEnd();
+ // Need to handle a url after the xml data
+ // http://kodi.wiki/view/NFO_files/movies
- // Find last closing Tag
- // Need to do this in two steps to account for random > characters after the closing xml
- var index = xml.LastIndexOf(@"</", StringComparison.Ordinal);
+ var xml = File.ReadAllText(metadataFile);
- // If closing tag exists, move to end of Tag
- if (index != -1)
- {
- index = xml.IndexOf('>', index);
- }
+ // Find last closing Tag
+ // Need to do this in two steps to account for random > characters after the closing xml
+ var index = xml.LastIndexOf(@"</", StringComparison.Ordinal);
- if (index != -1)
- {
- var endingXml = xml.Substring(index);
+ // If closing tag exists, move to end of Tag
+ if (index != -1)
+ {
+ index = xml.IndexOf('>', index);
+ }
- ParseProviderLinks(item.Item, endingXml);
+ if (index != -1)
+ {
+ var endingXml = xml.AsSpan().Slice(index);
- // If the file is just an imdb url, don't go any further
- if (index == 0)
- {
- return;
- }
+ ParseProviderLinks(item.Item, endingXml);
- xml = xml.Substring(0, index + 1);
- }
- else
+ // If the file is just an imdb url, don't go any further
+ if (index == 0)
{
- // If the file is just provider urls, handle that
- ParseProviderLinks(item.Item, xml);
-
return;
}
- // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
- try
+ xml = xml.Substring(0, index + 1);
+ }
+ else
+ {
+ // If the file is just provider urls, handle that
+ ParseProviderLinks(item.Item, xml);
+
+ return;
+ }
+
+ // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
+ try
+ {
+ using (var stringReader = new StringReader(xml))
+ using (var reader = XmlReader.Create(stringReader, settings))
{
- using (var stringReader = new StringReader(xml))
- using (var reader = XmlReader.Create(stringReader, settings))
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
- reader.MoveToContent();
- reader.Read();
+ cancellationToken.ThrowIfCancellationRequested();
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ if (reader.NodeType == XmlNodeType.Element)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- FetchDataFromXmlNode(reader, item);
- }
- else
- {
- reader.Read();
- }
+ FetchDataFromXmlNode(reader, item);
+ }
+ else
+ {
+ reader.Read();
}
}
}
- catch (XmlException)
- {
- }
+ }
+ catch (XmlException)
+ {
}
}
- protected void ParseProviderLinks(T item, string xml)
+ protected void ParseProviderLinks(T item, ReadOnlySpan<char> xml)
{
if (ProviderIdParsers.TryFindImdbId(xml, out var imdbId))
{
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index 6b1607530..ca3ec79b7 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -40,72 +40,68 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <inheritdoc />
protected override void Fetch(MetadataResult<Episode> item, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken)
{
- using (var fileStream = File.OpenRead(metadataFile))
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- item.ResetPeople();
+ item.ResetPeople();
- var xmlFile = streamReader.ReadToEnd();
+ var xmlFile = File.ReadAllText(metadataFile);
- var srch = "</episodedetails>";
- var index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
+ var srch = "</episodedetails>";
+ var index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
- var xml = xmlFile;
+ var xml = xmlFile;
- if (index != -1)
- {
- xml = xmlFile.Substring(0, index + srch.Length);
- xmlFile = xmlFile.Substring(index + srch.Length);
- }
+ if (index != -1)
+ {
+ xml = xmlFile.Substring(0, index + srch.Length);
+ xmlFile = xmlFile.Substring(index + srch.Length);
+ }
- // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
- try
+ // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
+ try
+ {
+ // Extract episode details from the first episodedetails block
+ using (var stringReader = new StringReader(xml))
+ using (var reader = XmlReader.Create(stringReader, settings))
{
- // Extract episode details from the first episodedetails block
- using (var stringReader = new StringReader(xml))
- using (var reader = XmlReader.Create(stringReader, settings))
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
- reader.MoveToContent();
- reader.Read();
+ cancellationToken.ThrowIfCancellationRequested();
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ if (reader.NodeType == XmlNodeType.Element)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- FetchDataFromXmlNode(reader, item);
- }
- else
- {
- reader.Read();
- }
+ FetchDataFromXmlNode(reader, item);
+ }
+ else
+ {
+ reader.Read();
}
}
+ }
- // Extract the last episode number from nfo
- // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag
- while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1)
+ // Extract the last episode number from nfo
+ // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag
+ while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1)
+ {
+ xml = xmlFile.Substring(0, index + srch.Length);
+ xmlFile = xmlFile.Substring(index + srch.Length);
+
+ using (var stringReader = new StringReader(xml))
+ using (var reader = XmlReader.Create(stringReader, settings))
{
- xml = xmlFile.Substring(0, index + srch.Length);
- xmlFile = xmlFile.Substring(index + srch.Length);
+ reader.MoveToContent();
- using (var stringReader = new StringReader(xml))
- using (var reader = XmlReader.Create(stringReader, settings))
+ if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num))
{
- reader.MoveToContent();
-
- if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num))
- {
- item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
- }
+ item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
}
}
}
- catch (XmlException)
- {
- }
+ }
+ catch (XmlException)
+ {
}
}
diff --git a/README.md b/README.md
index 3259777c8..3aef84b99 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,9 @@
<a href="https://github.com/jellyfin/jellyfin/commits/master.atom">
<img alt="Master Commits RSS Feed"" src="https://img.shields.io/badge/rss-commits-ffa500?logo=rss" />
</a>
+<a href="https://lgtm.com/projects/g/jellyfin/jellyfin/alerts/">
+<img alt="Total LGTM alerts" src="https://img.shields.io/lgtm/alerts/g/jellyfin/jellyfin.svg?logo=lgtm&logoWidth=18"/>
+</a>
</p>
---
diff --git a/debian/jellyfin.service b/debian/jellyfin.service
index f1a8f4652..c9d1a4d13 100644
--- a/debian/jellyfin.service
+++ b/debian/jellyfin.service
@@ -1,6 +1,6 @@
[Unit]
Description = Jellyfin Media Server
-After = network.target
+After = network-online.target
[Service]
Type = simple
diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64
index 97e3ff802..d88efcdc9 100644
--- a/deployment/Dockerfile.ubuntu.amd64
+++ b/deployment/Dockerfile.ubuntu.amd64
@@ -19,7 +19,7 @@ RUN apt-get update -yqq \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8468e541-a99a-4191-8470-654fa0747a9a/cb32548d2fd3d60ef3fe8fc80cd735ef/dotnet-sdk-5.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/13b9d84c-a35b-4ffe-8f62-447a01403d64/1f9ae31daa0f7d98513e7551246899f2/dotnet-sdk-5.0.400-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64
index c94ee91dd..4f41bba2d 100644
--- a/deployment/Dockerfile.ubuntu.arm64
+++ b/deployment/Dockerfile.ubuntu.arm64
@@ -18,7 +18,7 @@ RUN apt-get update -yqq \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8468e541-a99a-4191-8470-654fa0747a9a/cb32548d2fd3d60ef3fe8fc80cd735ef/dotnet-sdk-5.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/13b9d84c-a35b-4ffe-8f62-447a01403d64/1f9ae31daa0f7d98513e7551246899f2/dotnet-sdk-5.0.400-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf
index aaaedda82..01752d536 100644
--- a/deployment/Dockerfile.ubuntu.armhf
+++ b/deployment/Dockerfile.ubuntu.armhf
@@ -18,7 +18,7 @@ RUN apt-get update -yqq \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8468e541-a99a-4191-8470-654fa0747a9a/cb32548d2fd3d60ef3fe8fc80cd735ef/dotnet-sdk-5.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/13b9d84c-a35b-4ffe-8f62-447a01403d64/1f9ae31daa0f7d98513e7551246899f2/dotnet-sdk-5.0.400-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/build.portable b/deployment/build.portable
index ea40ade5d..a6c741881 100755
--- a/deployment/build.portable
+++ b/deployment/build.portable
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=false"
tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/fedora/jellyfin.service b/fedora/jellyfin.service
index b092ebf2f..f706b0ad3 100644
--- a/fedora/jellyfin.service
+++ b/fedora/jellyfin.service
@@ -1,5 +1,5 @@
[Unit]
-After=network.target
+After=network-online.target
Description=Jellyfin is a free software media system that puts you in control of managing and streaming your media.
[Service]
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 4edd84384..0c36e81cc 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -15,9 +15,9 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.8" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.9" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
diff --git a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs
index e6c325bac..18d3f9763 100644
--- a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs
+++ b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs
@@ -171,11 +171,11 @@ namespace Jellyfin.Common.Tests.Cryptography
[InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
[InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
[InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
- [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $
- [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment
- [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment
- [InlineData("$PBKDF2$iterations=$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt
- [InlineData("$PBKDF2$iterations=$69F420$invalid hash")] // Invalid hash
+ [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $
+ [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment
+ [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment
+ [InlineData("$PBKDF2$iterations=1000$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt
+ [InlineData("$PBKDF2$iterations=1000$69F420$invalid hash")] // Invalid hash
[InlineData("$PBKDF2$69F420$")] // Empty hash
public static void Parse_InvalidFormat_ThrowsFormatException(string passwordHash)
{
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index e4350c336..8e6b07716 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -12,11 +12,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
- <PackageReference Include="FsCheck.Xunit" Version="2.15.3" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 5b269a4b2..a5778b59c 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index 713f6423c..5a48631c2 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
index 9272d5eef..72cd9aa45 100644
--- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
+++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -17,7 +17,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
- <PackageReference Include="FsCheck.Xunit" Version="2.15.3" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index a6a948e2b..7ea503913 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 06ff22c7e..e9b7b1850 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -7,11 +7,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
- <PackageReference Include="FsCheck.Xunit" Version="2.15.3" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 510c8f60a..a4ebab141 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index 2c6e2e5f6..dd593c9e7 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -12,11 +12,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
- <PackageReference Include="FsCheck.Xunit" Version="2.15.3" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.1" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
index 195fc8801..d9e33617b 100644
--- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
+++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 387f259ce..9b6ab7bdf 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -21,7 +21,7 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
index edd4b1e55..143020d43 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
@@ -66,7 +66,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
var germany = localizationManager.FindLanguageInfo(identifier);
Assert.NotNull(germany);
- Assert.Equal("ger", germany.ThreeLetterISOLanguageName);
+ Assert.Equal("ger", germany!.ThreeLetterISOLanguageName);
Assert.Equal("German", germany.DisplayName);
Assert.Equal("German", germany.Name);
Assert.Contains("deu", germany.ThreeLetterISOLanguageNames);
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs
new file mode 100644
index 000000000..d9b206f66
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Emby.Server.Implementations.Sorting;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Sorting
+{
+ public class AiredEpisodeOrderComparerTests
+ {
+ [Theory]
+ [ClassData(typeof(EpisodeBadData))]
+ public void Compare_GivenNull_ThrowsArgumentNullException(BaseItem x, BaseItem y)
+ {
+ var cmp = new AiredEpisodeOrderComparer();
+ Assert.Throws<ArgumentNullException>(() => cmp.Compare(x, y));
+ }
+
+ [Theory]
+ [ClassData(typeof(EpisodeTestData))]
+ public void AiredEpisodeOrderCompareTest(BaseItem x, BaseItem y, int expected)
+ {
+ var cmp = new AiredEpisodeOrderComparer();
+
+ Assert.Equal(expected, cmp.Compare(x, y));
+ Assert.Equal(-expected, cmp.Compare(y, x));
+ }
+
+ private class EpisodeBadData : IEnumerable<object?[]>
+ {
+ public IEnumerator<object?[]> GetEnumerator()
+ {
+ yield return new object?[] { null, new Episode() };
+ yield return new object?[] { new Episode(), null };
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+
+ private class EpisodeTestData : IEnumerable<object?[]>
+ {
+ public IEnumerator<object?[]> GetEnumerator()
+ {
+ yield return new object?[]
+ {
+ new Movie(),
+ new Movie(),
+ 0
+ };
+ yield return new object?[]
+ {
+ new Movie(),
+ new Episode(),
+ 1
+ };
+ // Good cases
+ yield return new object?[]
+ {
+ new Episode(),
+ new Episode(),
+ 0
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ 0
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ 1
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 2, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ 1
+ };
+ // Good Specials
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 0
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 0, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 1
+ };
+
+ // Specials to Episodes
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 1
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 2 },
+ 1
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 1
+ };
+
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 1
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 2 },
+ 1
+ };
+
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ 1
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 3, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 },
+ 1
+ };
+
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 3, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 },
+ 1
+ };
+
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1 },
+ 1
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 },
+ 1
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 },
+ 0
+ };
+ yield return new object?[]
+ {
+ new Episode { ParentIndexNumber = 1, IndexNumber = 3 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 },
+ 1
+ };
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs
new file mode 100644
index 000000000..31f33c682
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Linq;
+using Jellyfin.Data.Enums;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.TypedBaseItem
+{
+ public class BaseItemKindTests
+ {
+ public static TheoryData<Type> BaseItemKind_TestData()
+ {
+ var data = new TheoryData<Type>();
+
+ var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
+ foreach (var assembly in loadedAssemblies)
+ {
+ if (IsProjectAssemblyName(assembly.FullName))
+ {
+ var baseItemTypes = assembly.GetTypes()
+ .Where(targetType => targetType.IsClass
+ && !targetType.IsAbstract
+ && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem)));
+ foreach (var baseItemType in baseItemTypes)
+ {
+ data.Add(baseItemType);
+ }
+ }
+ }
+
+ return data;
+ }
+
+ [Theory]
+ [MemberData(nameof(BaseItemKind_TestData))]
+ public void EnumParse_GivenValidBaseItemType_ReturnsEnumValue(Type baseItemDescendantType)
+ {
+ var enumValue = Enum.Parse<BaseItemKind>(baseItemDescendantType.Name);
+ Assert.True(Enum.IsDefined(typeof(BaseItemKind), enumValue));
+ }
+
+ [Theory]
+ [MemberData(nameof(BaseItemKind_TestData))]
+ public void GetBaseItemKind_WhenCalledAfterDefaultCtor_DoesNotThrow(Type baseItemDescendantType)
+ {
+ var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes);
+ var instance = (MediaBrowser.Controller.Entities.BaseItem)defaultConstructor!.Invoke(null);
+ var exception = Record.Exception(() => instance.GetBaseItemKind());
+ Assert.Null(exception);
+ }
+
+ private static bool IsProjectAssemblyName(string? name)
+ {
+ if (name == null)
+ {
+ return false;
+ }
+
+ return name.StartsWith("Jellyfin", StringComparison.OrdinalIgnoreCase)
+ || name.StartsWith("Emby", StringComparison.OrdinalIgnoreCase)
+ || name.StartsWith("MediaBrowser", StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index cf4215339..592b444c9 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -9,9 +9,9 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.8" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.9" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="Xunit.Priority" Version="1.1.6" />
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index 2f95f5c01..f249be674 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -10,9 +10,9 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.8" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.9" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
index 146b16cf9..b92cb165c 100644
--- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
+++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
@@ -1,10 +1,15 @@
+using System;
+using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
+using System.Net;
using System.Text;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Manager;
using Jellyfin.Server.Extensions;
using MediaBrowser.Common.Configuration;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
@@ -13,20 +18,63 @@ namespace Jellyfin.Server.Tests
{
public class ParseNetworkTests
{
- /// <summary>
- /// Order of the result has always got to be hosts, then networks.
- /// </summary>
- /// <param name="ip4">IP4 enabled.</param>
- /// <param name="ip6">IP6 enabled.</param>
- /// <param name="hostList">List to parse.</param>
- /// <param name="match">What it should match.</param>
+ public static TheoryData<bool, bool, string[], IPAddress[], IPNetwork[]> TestNetworks_TestData()
+ {
+ var data = new TheoryData<bool, bool, string[], IPAddress[], IPNetwork[]>();
+ data.Add(
+ true,
+ true,
+ new string[] { "192.168.t", "127.0.0.1", "1234.1232.12.1234" },
+ new IPAddress[] { IPAddress.Loopback.MapToIPv6() },
+ Array.Empty<IPNetwork>());
+
+ data.Add(
+ true,
+ false,
+ new string[] { "192.168.x", "127.0.0.1", "1234.1232.12.1234" },
+ new IPAddress[] { IPAddress.Loopback },
+ Array.Empty<IPNetwork>());
+
+ data.Add(
+ true,
+ true,
+ new string[] { "::1" },
+ Array.Empty<IPAddress>(),
+ new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+
+ data.Add(
+ false,
+ false,
+ new string[] { "localhost" },
+ Array.Empty<IPAddress>(),
+ Array.Empty<IPNetwork>());
+
+ data.Add(
+ true,
+ false,
+ new string[] { "localhost" },
+ new IPAddress[] { IPAddress.Loopback },
+ Array.Empty<IPNetwork>());
+
+ data.Add(
+ false,
+ true,
+ new string[] { "localhost" },
+ Array.Empty<IPAddress>(),
+ new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+
+ data.Add(
+ true,
+ true,
+ new string[] { "localhost" },
+ new IPAddress[] { IPAddress.Loopback.MapToIPv6() },
+ new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ return data;
+ }
+
[Theory]
- // [InlineData(true, true, "192.168.0.0/16,www.yahoo.co.uk", "::ffff:212.82.100.150,::ffff:192.168.0.0/16")] <- fails on Max. www.yahoo.co.uk resolves to a different ip address.
- // [InlineData(true, false, "192.168.0.0/16,www.yahoo.co.uk", "212.82.100.150,192.168.0.0/16")]
- [InlineData(true, true, "192.168.t,127.0.0.1,1234.1232.12.1234", "::ffff:127.0.0.1")]
- [InlineData(true, false, "192.168.x,127.0.0.1,1234.1232.12.1234", "127.0.0.1")]
- [InlineData(true, true, "::1", "::1/128")]
- public void TestNetworks(bool ip4, bool ip6, string hostList, string match)
+ [MemberData(nameof(TestNetworks_TestData))]
+ public void TestNetworks(bool ip4, bool ip6, string[] hostList, IPAddress[] knownProxies, IPNetwork[] knownNetworks)
{
using var nm = CreateNetworkManager();
@@ -36,31 +84,25 @@ namespace Jellyfin.Server.Tests
EnableIPV6 = ip6
};
- var result = match + ",";
ForwardedHeadersOptions options = new ForwardedHeadersOptions();
// Need this here as ::1 and 127.0.0.1 are in them by default.
options.KnownProxies.Clear();
options.KnownNetworks.Clear();
- ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(','), options);
+ ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList, options);
- var sb = new StringBuilder();
- foreach (var item in options.KnownProxies)
+ Assert.Equal(knownProxies.Length, options.KnownProxies.Count);
+ foreach (var item in knownProxies)
{
- sb.Append(item)
- .Append(',');
+ Assert.True(options.KnownProxies.Contains(item));
}
- foreach (var item in options.KnownNetworks)
+ Assert.Equal(knownNetworks.Length, options.KnownNetworks.Count);
+ foreach (var item in knownNetworks)
{
- sb.Append(item.Prefix)
- .Append('/')
- .Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture))
- .Append(',');
+ Assert.NotNull(options.KnownNetworks.FirstOrDefault(x => x.Prefix.Equals(item.Prefix) && x.PrefixLength == item.PrefixLength));
}
-
- Assert.Equal(sb.ToString(), result);
}
private static IConfigurationManager GetMockConfig(NetworkConfiguration conf)
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index 78837bba6..e08590758 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />