Merge branch 'master' into RemoteAccessFix

This commit is contained in:
BaronGreenback 2021-03-22 17:05:44 +00:00 committed by GitHub
commit 5d16d1f66d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
174 changed files with 1361 additions and 1414 deletions

View File

@ -160,7 +160,6 @@ jobs:
dependsOn: dependsOn:
- BuildPackage - BuildPackage
- BuildDocker - BuildDocker
condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
@ -186,9 +185,6 @@ jobs:
- job: PublishNuget - job: PublishNuget
displayName: 'Publish NuGet packages' displayName: 'Publish NuGet packages'
dependsOn:
- BuildPackage
condition: succeeded('BuildPackage')
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'

View File

@ -36,7 +36,7 @@ namespace Emby.Dlna
private readonly ILogger<DlnaManager> _logger; private readonly ILogger<DlnaManager> _logger;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal); private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
@ -333,7 +333,12 @@ namespace Emby.Dlna
throw new ArgumentNullException(nameof(id)); throw new ArgumentNullException(nameof(id));
} }
var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase)); var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
if (info == null)
{
return null;
}
return ParseProfileFile(info.Path, info.Info.Type); return ParseProfileFile(info.Path, info.Info.Type);
} }

View File

@ -128,7 +128,8 @@ namespace Emby.Dlna.Main
_netConfig = config.GetConfiguration<NetworkConfiguration>("network"); _netConfig = config.GetConfiguration<NetworkConfiguration>("network");
_disabled = appHost.ListenWithHttps && _netConfig.RequireHttps; _disabled = appHost.ListenWithHttps && _netConfig.RequireHttps;
if (_disabled)
if (_disabled && _config.GetDlnaConfiguration().EnableServer)
{ {
_logger.LogError("The DLNA specification does not support HTTPS."); _logger.LogError("The DLNA specification does not support HTTPS.");
} }

View File

@ -69,7 +69,7 @@ namespace Emby.Dlna.Ssdp
{ {
lock (_syncLock) lock (_syncLock)
{ {
if (_listenerCount > 0 && _deviceLocator == null) if (_listenerCount > 0 && _deviceLocator == null && _commsServer != null)
{ {
_deviceLocator = new SsdpDeviceLocator(_commsServer); _deviceLocator = new SsdpDeviceLocator(_commsServer);

View File

@ -352,8 +352,13 @@ namespace Emby.Drawing
} }
/// <inheritdoc /> /// <inheritdoc />
public string GetImageCacheTag(User user) public string? GetImageCacheTag(User user)
{ {
if (user.ProfileImage == null)
{
return null;
}
return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5() return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5()
.ToString("N", CultureInfo.InvariantCulture); .ToString("N", CultureInfo.InvariantCulture);
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Emby.Naming.Video namespace Emby.Naming.Video
@ -16,8 +17,14 @@ namespace Emby.Naming.Video
/// <param name="expressions">List of regex to parse name and year from.</param> /// <param name="expressions">List of regex to parse name and year from.</param>
/// <param name="newName">Parsing result string.</param> /// <param name="newName">Parsing result string.</param>
/// <returns>True if parsing was successful.</returns> /// <returns>True if parsing was successful.</returns>
public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName) public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
{ {
if (string.IsNullOrEmpty(name))
{
newName = ReadOnlySpan<char>.Empty;
return false;
}
var len = expressions.Count; var len = expressions.Count;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
@ -33,12 +40,6 @@ namespace Emby.Naming.Video
private static bool TryClean(string name, Regex expression, out ReadOnlySpan<char> newName) private static bool TryClean(string name, Regex expression, out ReadOnlySpan<char> newName)
{ {
if (string.IsNullOrEmpty(name))
{
newName = ReadOnlySpan<char>.Empty;
return false;
}
var match = expression.Match(name); var match = expression.Match(name);
int index = match.Index; int index = match.Index;
if (match.Success && index != 0) if (match.Success && index != 0)

View File

@ -221,20 +221,21 @@ namespace Emby.Naming.Video
string testFilename = Path.GetFileNameWithoutExtension(testFilePath); string testFilename = Path.GetFileNameWithoutExtension(testFilePath);
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{ {
if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName)) // Remove the folder name before cleaning as we don't care about cleaning that part
{
testFilename = cleanName.ToString();
}
if (folderName.Length <= testFilename.Length) if (folderName.Length <= testFilename.Length)
{ {
testFilename = testFilename.Substring(folderName.Length).Trim(); testFilename = testFilename.Substring(folderName.Length).Trim();
} }
if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
{
testFilename = cleanName.Trim().ToString();
}
// The CleanStringParser should have removed common keywords etc.
return string.IsNullOrEmpty(testFilename) return string.IsNullOrEmpty(testFilename)
|| testFilename[0] == '-' || testFilename[0] == '-'
|| testFilename[0] == '_' || Regex.IsMatch(testFilename, @"^\[([^]]*)\]");
|| string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
} }
return false; return false;

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
@ -146,7 +147,7 @@ namespace Emby.Naming.Video
/// <param name="name">Raw name.</param> /// <param name="name">Raw name.</param>
/// <param name="newName">Clean name.</param> /// <param name="newName">Clean name.</param>
/// <returns>True if cleaning of name was successful.</returns> /// <returns>True if cleaning of name was successful.</returns>
public bool TryCleanString(string name, out ReadOnlySpan<char> newName) public bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan<char> newName)
{ {
return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName); return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
} }

View File

@ -10,8 +10,6 @@ using System.Net;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna; using Emby.Dlna;
@ -51,7 +49,6 @@ using Jellyfin.Networking.Manager;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events; using MediaBrowser.Common.Events;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
@ -470,7 +467,7 @@ namespace Emby.Server.Implementations
} }
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyCollection<T> GetExports<T>(CreationDelegate defaultFunc, bool manageLifetime = true) public IReadOnlyCollection<T> GetExports<T>(CreationDelegateFactory defaultFunc, bool manageLifetime = true)
{ {
// Convert to list so this isn't executed for each iteration // Convert to list so this isn't executed for each iteration
var parts = GetExportTypes<T>() var parts = GetExportTypes<T>()

View File

@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Channels
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly IMemoryCache _memoryCache; private readonly IMemoryCache _memoryCache;
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ChannelManager"/> class. /// Initializes a new instance of the <see cref="ChannelManager"/> class.

View File

@ -344,7 +344,20 @@ namespace Emby.Server.Implementations.Collections
} }
else else
{ {
results[item.Id] = item; var alreadyInResults = false;
foreach (var child in item.GetMediaSources(true))
{
if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id))
{
alreadyInResults = true;
break;
}
}
if (!alreadyInResults)
{
results[item.Id] = item;
}
} }
} }
} }

View File

@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Data
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_typeMapper = new TypeMapper(); _typeMapper = new TypeMapper();
_jsonOptions = JsonDefaults.GetOptions(); _jsonOptions = JsonDefaults.Options;
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
} }

View File

@ -27,6 +27,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" 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.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
<PackageReference Include="Mono.Nat" Version="3.0.1" /> <PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" />
<PackageReference Include="sharpcompress" Version="0.28.1" /> <PackageReference Include="sharpcompress" Version="0.28.1" />

View File

@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer
RemoteEndPoint = remoteEndPoint; RemoteEndPoint = remoteEndPoint;
QueryString = query; QueryString = query;
_jsonOptions = JsonDefaults.GetOptions(); _jsonOptions = JsonDefaults.Options;
LastActivityDate = DateTime.Now; LastActivityDate = DateTime.Now;
} }

View File

@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Library
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths) public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths)
{ {

View File

@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;

View File

@ -59,11 +59,18 @@ namespace Emby.Server.Implementations.Library
/// <param name="newPath">The result of the sub path replacement</param> /// <param name="newPath">The result of the sub path replacement</param>
/// <returns>The path after replacing the sub path.</returns> /// <returns>The path after replacing the sub path.</returns>
/// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception> /// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception>
public static bool TryReplaceSubPath(this string path, string subPath, string newSubPath, [NotNullWhen(true)] out string? newPath) public static bool TryReplaceSubPath(
[NotNullWhen(true)] this string? path,
[NotNullWhen(true)] string? subPath,
[NotNullWhen(true)] string? newSubPath,
[NotNullWhen(true)] out string? newPath)
{ {
newPath = null; newPath = null;
if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(subPath) || string.IsNullOrEmpty(newSubPath) || subPath.Length > path.Length) if (string.IsNullOrEmpty(path)
|| string.IsNullOrEmpty(subPath)
|| string.IsNullOrEmpty(newSubPath)
|| subPath.Length > path.Length)
{ {
return false; return false;
} }

View File

@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// </summary> /// </summary>
/// <param name="args">The args.</param> /// <param name="args">The args.</param>
/// <returns>`0.</returns> /// <returns>`0.</returns>
protected override T Resolve(ItemResolveArgs args) public override T Resolve(ItemResolveArgs args)
{ {
return ResolveVideo<T>(args, false); return ResolveVideo<T>(args, false);
} }
@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <param name="args">The args.</param> /// <param name="args">The args.</param>
/// <param name="parseName">if set to <c>true</c> [parse name].</param> /// <param name="parseName">if set to <c>true</c> [parse name].</param>
/// <returns>``0.</returns> /// <returns>``0.</returns>
protected TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName) protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new() where TVideoType : Video, new()
{ {
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();

View File

@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
{ {
private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" }; private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" };
protected override Book Resolve(ItemResolveArgs args) public override Book Resolve(ItemResolveArgs args)
{ {
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();

View File

@ -69,6 +69,110 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return result; return result;
} }
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Video.</returns>
public override Video Resolve(ItemResolveArgs args)
{
var collectionType = args.GetCollectionType();
// Find movies with their own folders
if (args.IsDirectory)
{
if (IsInvalid(args.Parent, collectionType))
{
return null;
}
var files = args.FileSystemChildren
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.IsNullOrEmpty(collectionType))
{
// Owned items will be caught by the plain video resolver
if (args.Parent == null)
{
// return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
if (args.HasParent<Series>())
{
return null;
}
{
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
return null;
}
// Handle owned items
if (args.Parent == null)
{
return base.Resolve(args);
}
if (IsInvalid(args.Parent, collectionType))
{
return null;
}
Video item = null;
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<MusicVideo>(args, false);
}
// To find a movie file, the collection type must be movies or boxsets
else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<Movie>(args, true);
}
else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<Video>(args, false);
}
else if (string.IsNullOrEmpty(collectionType))
{
if (args.HasParent<Series>())
{
return null;
}
item = ResolveVideo<Video>(args, false);
}
if (item != null)
{
item.IsInMixedFolder = true;
}
return item;
}
private MultiItemResolverResult ResolveMultipleInternal( private MultiItemResolverResult ResolveMultipleInternal(
Folder parent, Folder parent,
List<FileSystemMetadata> files, List<FileSystemMetadata> files,
@ -216,110 +320,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase); return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
} }
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Video.</returns>
protected override Video Resolve(ItemResolveArgs args)
{
var collectionType = args.GetCollectionType();
// Find movies with their own folders
if (args.IsDirectory)
{
if (IsInvalid(args.Parent, collectionType))
{
return null;
}
var files = args.FileSystemChildren
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.IsNullOrEmpty(collectionType))
{
// Owned items will be caught by the plain video resolver
if (args.Parent == null)
{
// return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
if (args.HasParent<Series>())
{
return null;
}
{
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
return null;
}
// Handle owned items
if (args.Parent == null)
{
return base.Resolve(args);
}
if (IsInvalid(args.Parent, collectionType))
{
return null;
}
Video item = null;
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<MusicVideo>(args, false);
}
// To find a movie file, the collection type must be movies or boxsets
else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<Movie>(args, true);
}
else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<Video>(args, false);
}
else if (string.IsNullOrEmpty(collectionType))
{
if (args.HasParent<Series>())
{
return null;
}
item = ResolveVideo<Video>(args, false);
}
if (item != null)
{
item.IsInMixedFolder = true;
}
return item;
}
/// <summary> /// <summary>
/// Sets the initial item values. /// Sets the initial item values.
/// </summary> /// </summary>

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -11,12 +12,21 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// </summary> /// </summary>
public class EpisodeResolver : BaseVideoResolver<Episode> public class EpisodeResolver : BaseVideoResolver<Episode>
{ {
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public EpisodeResolver(ILibraryManager libraryManager)
: base(libraryManager)
{
}
/// <summary> /// <summary>
/// Resolves the specified args. /// Resolves the specified args.
/// </summary> /// </summary>
/// <param name="args">The args.</param> /// <param name="args">The args.</param>
/// <returns>Episode.</returns> /// <returns>Episode.</returns>
protected override Episode Resolve(ItemResolveArgs args) public override Episode Resolve(ItemResolveArgs args)
{ {
var parent = args.Parent; var parent = args.Parent;
@ -34,11 +44,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
season = parent.GetParents().OfType<Season>().FirstOrDefault(); season = parent.GetParents().OfType<Season>().FirstOrDefault();
} }
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders // Also handle flat tv folders
if (season != null || if ((season != null ||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
args.HasParent<Series>()) args.HasParent<Series>())
&& (parent is Series || !BaseItem.AllExtrasTypesFolderNames.Contains(parent.Name, StringComparer.OrdinalIgnoreCase)))
{ {
var episode = ResolveVideo<Episode>(args, false); var episode = ResolveVideo<Episode>(args, false);
@ -74,14 +85,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null; return null;
} }
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public EpisodeResolver(ILibraryManager libraryManager)
: base(libraryManager)
{
}
} }
} }

View File

@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(); private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private bool _hasExited; private bool _hasExited;
private Stream _logFileStream; private Stream _logFileStream;
private string _targetPath; private string _targetPath;

View File

@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
private readonly string _dataPath; private readonly string _dataPath;
private readonly object _fileDataLock = new object(); private readonly object _fileDataLock = new object();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private T[] _items; private T[] _items;
public ItemDataProvider( public ItemDataProvider(

View File

@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ICryptoProvider _cryptoProvider; private readonly ICryptoProvider _cryptoProvider;
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private DateTime _lastErrorResponse; private DateTime _lastErrorResponse;
public SchedulesDirect( public SchedulesDirect(

View File

@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_networkManager = networkManager; _networkManager = networkManager;
_streamHelper = streamHelper; _streamHelper = streamHelper;
_jsonOptions = JsonDefaults.GetOptions(); _jsonOptions = JsonDefaults.Options;
} }
public string Name => "HD Homerun"; public string Name => "HD Homerun";

View File

@ -2,6 +2,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Net; using System.Net;
@ -10,6 +11,7 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@ -120,13 +122,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private static async Task<bool> CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken) private static async Task<bool> CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken)
{ {
var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192); byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
try try
{ {
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); var msgLen = WriteGetMessage(buffer, tuner, "lockkey");
await stream.WriteAsync(buffer.AsMemory(0, msgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
ParseReturnMessage(buffer, receivedBytes, out string returnVal); ParseReturnMessage(buffer, receivedBytes, out string returnVal);
@ -166,9 +168,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_activeTuner = i; _activeTuner = i;
var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue); var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue);
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); var lockkeyMsgLen = WriteSetMessage(buffer, i, "lockkey", lockKeyString, null);
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, lockkeyMsgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked // parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _)) if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -178,9 +180,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
foreach (var command in commands.GetCommands()) foreach (var command in commands.GetCommands())
{ {
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue); var channelMsgLen = WriteSetMessage(buffer, i, command.Item1, command.Item2, lockKeyValue);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked // parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _)) if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -191,10 +193,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort); var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort);
var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue); var targetMsgLen = WriteSetMessage(buffer, i, "target", targetValue, lockKeyValue);
await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, targetMsgLen), cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked // parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _)) if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -232,9 +234,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
foreach (var command in commandList) foreach (var command in commandList)
{ {
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey); var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.Item1, command.Item2, _lockkey);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked // parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _)) if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -265,17 +267,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
var stream = client.GetStream(); var stream = client.GetStream();
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false);
var buffer = ArrayPool<byte>.Shared.Rent(8192); var buffer = ArrayPool<byte>.Shared.Rent(8192);
try try
{ {
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); var releaseTargetLen = WriteSetMessage(buffer, _activeTuner, "target", "none", lockKeyValue);
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue); await stream.WriteAsync(buffer.AsMemory(0, releaseTargetLen)).ConfigureAwait(false);
await stream.ReadAsync(buffer).ConfigureAwait(false);
var releaseKeyMsgLen = WriteSetMessage(buffer, _activeTuner, "lockkey", "none", lockKeyValue);
_lockkey = null; _lockkey = null;
await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, releaseKeyMsgLen)).ConfigureAwait(false);
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); await stream.ReadAsync(buffer).ConfigureAwait(false);
} }
finally finally
{ {
@ -283,109 +285,76 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
} }
private static byte[] CreateGetMessage(int tuner, string name) internal static int WriteGetMessage(Span<byte> buffer, int tuner, string name)
{ {
var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name)); var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length int offset = WriteHeaderAndPayload(buffer, byteName);
return FinishPacket(buffer, offset);
var message = new byte[messageLength];
int offset = InsertHeaderAndName(byteName, messageLength, message);
bool flipEndian = BitConverter.IsLittleEndian;
// calculate crc and insert at the end of the message
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
if (flipEndian)
{
Array.Reverse(crcBytes);
}
Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
return message;
} }
private static byte[] CreateSetMessage(int tuner, string name, string value, uint? lockkey) private static int WriteSetMessage(Span<byte> buffer, int tuner, string name, string value, uint? lockkey)
{ {
var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name)); var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
var byteValue = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\0", value)); int offset = WriteHeaderAndPayload(buffer, byteName);
buffer[offset++] = GetSetValue;
offset += WriteNullTerminatedString(buffer.Slice(offset), value);
int messageLength = byteName.Length + byteValue.Length + 12;
if (lockkey.HasValue) if (lockkey.HasValue)
{ {
messageLength += 6; buffer[offset++] = GetSetLockkey;
} buffer[offset++] = 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), lockkey.Value);
var message = new byte[messageLength];
int offset = InsertHeaderAndName(byteName, messageLength, message);
bool flipEndian = BitConverter.IsLittleEndian;
message[offset++] = GetSetValue;
message[offset++] = Convert.ToByte(byteValue.Length);
Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
offset += byteValue.Length;
if (lockkey.HasValue)
{
message[offset++] = GetSetLockkey;
message[offset++] = 4;
var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
if (flipEndian)
{
Array.Reverse(lockKeyBytes);
}
Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
offset += 4; offset += 4;
} }
// calculate crc and insert at the end of the message return FinishPacket(buffer, offset);
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
if (flipEndian)
{
Array.Reverse(crcBytes);
}
Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
return message;
} }
private static int InsertHeaderAndName(byte[] byteName, int messageLength, byte[] message) internal static int WriteNullTerminatedString(Span<byte> buffer, ReadOnlySpan<char> payload)
{ {
// check to see if we need to flip endiannes int len = Encoding.UTF8.GetBytes(payload, buffer.Slice(1)) + 1;
bool flipEndian = BitConverter.IsLittleEndian;
int offset = 0;
// create header bytes // TODO: variable length: this can be 2 bytes if len > 127
var getSetBytes = BitConverter.GetBytes(GetSetRequest); // Write length in front of value
var msgLenBytes = BitConverter.GetBytes((ushort)(messageLength - 8)); // Subtrace 4 bytes for header and 4 bytes for crc buffer[0] = Convert.ToByte(len);
if (flipEndian) // null-terminate
{ buffer[len++] = 0;
Array.Reverse(getSetBytes);
Array.Reverse(msgLenBytes);
}
// insert header bytes into message return len;
Buffer.BlockCopy(getSetBytes, 0, message, offset, 2); }
offset += 2;
Buffer.BlockCopy(msgLenBytes, 0, message, offset, 2);
offset += 2;
// insert tag name and length private static int WriteHeaderAndPayload(Span<byte> buffer, ReadOnlySpan<char> payload)
message[offset++] = GetSetName; {
message[offset++] = Convert.ToByte(byteName.Length); // Packet type
BinaryPrimitives.WriteUInt16BigEndian(buffer, GetSetRequest);
// insert name string // We write the payload length at the end
Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length); int offset = 4;
offset += byteName.Length;
// Tag
buffer[offset++] = GetSetName;
// Payload length + data
int strLen = WriteNullTerminatedString(buffer.Slice(offset), payload);
offset += strLen;
return offset; return offset;
} }
private static int FinishPacket(Span<byte> buffer, int offset)
{
// Payload length
BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), (ushort)(offset - 4));
// calculate crc and insert at the end of the message
var crc = Crc32.Compute(buffer.Slice(0, offset));
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), crc);
return offset + 4;
}
private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal) private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal)
{ {
returnVal = string.Empty; returnVal = string.Empty;
@ -442,90 +411,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
return true; return true;
} }
private static class HdHomerunCrc
{
private static uint[] crc_table = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d };
public static uint GetCrc32(byte[] bytes, int numBytes)
{
var hash = 0xffffffff;
for (var i = 0; i < numBytes; i++)
{
hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
}
var tmp = ~hash & 0xffffffff;
var b0 = tmp & 0xff;
var b1 = (tmp >> 8) & 0xff;
var b2 = (tmp >> 16) & 0xff;
var b3 = (tmp >> 24) & 0xff;
return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
}
}
} }
} }

View File

@ -22,5 +22,26 @@
"Artists": "Artistoj", "Artists": "Artistoj",
"Application": "Aplikaĵo", "Application": "Aplikaĵo",
"AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}", "AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}",
"Albums": "Albumoj" "Albums": "Albumoj",
"TasksLibraryCategory": "Libraro",
"VersionNumber": "Versio {0}",
"UserDownloadingItemWithValues": "{0} elŝutas {1}",
"UserCreatedWithName": "Uzanto {0} kreiĝis",
"User": "Uzanto",
"System": "Sistemo",
"Songs": "Kantoj",
"ScheduledTaskStartedWithName": "{0} komencis",
"ScheduledTaskFailedWithName": "{0} malsukcesis",
"PluginUninstalledWithName": "{0} malinstaliĝis",
"PluginInstalledWithName": "{0} instaliĝis",
"Plugin": "Kromprogramo",
"Playlists": "Ludlistoj",
"Photos": "Fotoj",
"NotificationOptionPluginUninstalled": "Kromprogramo malinstaliĝis",
"NotificationOptionNewLibraryContent": "Nova enhavo aldoniĝis",
"NotificationOptionPluginInstalled": "Kromprogramo instaliĝis",
"MusicVideos": "Muzikvideoj",
"LabelIpAddressValue": "IP-adreso: {0}",
"Genres": "Ĝenroj",
"DeviceOfflineWithName": "{0} malkonektis"
} }

View File

@ -116,7 +116,7 @@
"TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.", "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.",
"TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.", "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.",
"TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.", "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.",
"TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşіn nobailar jasaidy.", "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşın nobailar jasaidy.",
"TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.", "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.",
"TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady." "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady."
} }

View File

@ -113,5 +113,9 @@
"TasksChannelsCategory": "Internetiniai Kanalai", "TasksChannelsCategory": "Internetiniai Kanalai",
"TasksApplicationCategory": "Programa", "TasksApplicationCategory": "Programa",
"TasksLibraryCategory": "Mediateka", "TasksLibraryCategory": "Mediateka",
"TasksMaintenanceCategory": "Priežiūra" "TasksMaintenanceCategory": "Priežiūra",
"TaskCleanActivityLog": "Švarus veiklos žurnalas",
"Undefined": "Neapibrėžtas",
"Forced": "Priverstas",
"Default": "Numatytas"
} }

View File

@ -16,7 +16,7 @@
"Folders": "Pastas", "Folders": "Pastas",
"Genres": "Gêneros", "Genres": "Gêneros",
"HeaderAlbumArtists": "Artistas do Álbum", "HeaderAlbumArtists": "Artistas do Álbum",
"HeaderContinueWatching": "Continuar Assistindo", "HeaderContinueWatching": "Continuar assistindo",
"HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteAlbums": "Álbuns Favoritos",
"HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episódios favoritos", "HeaderFavoriteEpisodes": "Episódios favoritos",

View File

@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Localization
private List<CultureDto> _cultures; private List<CultureDto> _cultures;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager" /> class. /// Initializes a new instance of the <see cref="LocalizationManager" /> class.

View File

@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.Plugins
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_pluginsPath = pluginsPath; _pluginsPath = pluginsPath;
_appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion));
_jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions()) _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options)
{ {
WriteIndented = true WriteIndented = true
}; };
@ -678,7 +678,7 @@ namespace Emby.Server.Implementations.Plugins
var entry = versions[x]; var entry = versions[x];
if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase))
{ {
entry.DllFiles.AddRange(Directory.EnumerateFiles(entry.Path, "*.dll", SearchOption.AllDirectories)); entry.DllFiles = Directory.GetFiles(entry.Path, "*.dll", SearchOption.AllDirectories);
if (entry.IsEnabledAndSupported) if (entry.IsEnabledAndSupported)
{ {
lastName = entry.Name; lastName = entry.Name;

View File

@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <summary> /// <summary>
/// The options for the json Serializer. /// The options for the json Serializer.
/// </summary> /// </summary>
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class. /// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.

View File

@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Updates
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_config = config; _config = config;
_zipClient = zipClient; _zipClient = zipClient;
_jsonSerializerOptions = JsonDefaults.GetOptions(); _jsonSerializerOptions = JsonDefaults.Options;
_pluginManager = pluginManager; _pluginManager = pluginManager;
} }

View File

@ -25,7 +25,7 @@ namespace Jellyfin.Api.Controllers
private readonly IServerConfigurationManager _configurationManager; private readonly IServerConfigurationManager _configurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.Options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConfigurationController"/> class. /// Initializes a new instance of the <see cref="ConfigurationController"/> class.

View File

@ -61,7 +61,13 @@ namespace Jellyfin.Api.Controllers
{ {
// TODO: Deprecate with new iOS app // TODO: Deprecate with new iOS app
var file = segmentId + Path.GetExtension(Request.Path); var file = segmentId + Path.GetExtension(Request.Path);
file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); var transcodePath = _serverConfigurationManager.GetTranscodePath();
file = Path.GetFullPath(Path.Combine(transcodePath, file));
var fileDir = Path.GetDirectoryName(file);
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath))
{
return BadRequest("Invalid segment.");
}
return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, HttpContext); return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, HttpContext);
} }
@ -81,7 +87,13 @@ namespace Jellyfin.Api.Controllers
public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId) public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId)
{ {
var file = playlistId + Path.GetExtension(Request.Path); var file = playlistId + Path.GetExtension(Request.Path);
file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); var transcodePath = _serverConfigurationManager.GetTranscodePath();
file = Path.GetFullPath(Path.Combine(transcodePath, file));
var fileDir = Path.GetDirectoryName(file);
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath) || Path.GetExtension(file) != ".m3u8")
{
return BadRequest("Invalid segment.");
}
return GetFileResult(file, file); return GetFileResult(file, file);
} }
@ -130,7 +142,12 @@ namespace Jellyfin.Api.Controllers
var file = segmentId + Path.GetExtension(Request.Path); var file = segmentId + Path.GetExtension(Request.Path);
var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath(); var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath();
file = Path.Combine(transcodeFolderPath, file); file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file));
var fileDir = Path.GetDirectoryName(file);
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath))
{
return BadRequest("Invalid segment.");
}
var normalizedPlaylistId = playlistId; var normalizedPlaylistId = playlistId;

View File

@ -74,7 +74,7 @@ namespace Jellyfin.Api.Controllers
: type; : type;
var path = BaseItem.SupportedImageExtensions var path = BaseItem.SupportedImageExtensions
.Select(i => Path.Combine(_applicationPaths.GeneralPath, name, filename + i)) .Select(i => Path.GetFullPath(Path.Combine(_applicationPaths.GeneralPath, name, filename + i)))
.FirstOrDefault(System.IO.File.Exists); .FirstOrDefault(System.IO.File.Exists);
if (path == null) if (path == null)
@ -82,6 +82,11 @@ namespace Jellyfin.Api.Controllers
return NotFound(); return NotFound();
} }
if (!path.StartsWith(_applicationPaths.GeneralPath))
{
return BadRequest("Invalid image path.");
}
var contentType = MimeTypes.GetMimeType(path); var contentType = MimeTypes.GetMimeType(path);
return File(System.IO.File.OpenRead(path), contentType); return File(System.IO.File.OpenRead(path), contentType);
} }
@ -163,7 +168,8 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="FileStreamResult"/> containing the image contents on success, or a <see cref="NotFoundResult"/> if the image could not be found.</returns> /// <returns>A <see cref="FileStreamResult"/> containing the image contents on success, or a <see cref="NotFoundResult"/> if the image could not be found.</returns>
private ActionResult GetImageFile(string basePath, string theme, string? name) private ActionResult GetImageFile(string basePath, string theme, string? name)
{ {
var themeFolder = Path.Combine(basePath, theme); var themeFolder = Path.GetFullPath(Path.Combine(basePath, theme));
if (Directory.Exists(themeFolder)) if (Directory.Exists(themeFolder))
{ {
var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, name + i)) var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, name + i))
@ -171,12 +177,18 @@ namespace Jellyfin.Api.Controllers
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
{ {
if (!path.StartsWith(basePath))
{
return BadRequest("Invalid image path.");
}
var contentType = MimeTypes.GetMimeType(path); var contentType = MimeTypes.GetMimeType(path);
return PhysicalFile(path, contentType); return PhysicalFile(path, contentType);
} }
} }
var allFolder = Path.Combine(basePath, "all"); var allFolder = Path.GetFullPath(Path.Combine(basePath, "all"));
if (Directory.Exists(allFolder)) if (Directory.Exists(allFolder))
{ {
var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, name + i)) var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, name + i))
@ -184,6 +196,11 @@ namespace Jellyfin.Api.Controllers
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
{ {
if (!path.StartsWith(basePath))
{
return BadRequest("Invalid image path.");
}
var contentType = MimeTypes.GetMimeType(path); var contentType = MimeTypes.GetMimeType(path);
return PhysicalFile(path, contentType); return PhysicalFile(path, contentType);
} }

View File

@ -196,6 +196,11 @@ namespace Jellyfin.Api.Controllers
} }
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
if (user?.ProfileImage == null)
{
return NoContent();
}
try try
{ {
System.IO.File.Delete(user.ProfileImage.Path); System.IO.File.Delete(user.ProfileImage.Path);
@ -235,6 +240,11 @@ namespace Jellyfin.Api.Controllers
} }
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
if (user?.ProfileImage == null)
{
return NoContent();
}
try try
{ {
System.IO.File.Delete(user.ProfileImage.Path); System.IO.File.Delete(user.ProfileImage.Path);
@ -1469,7 +1479,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? imageIndex) [FromQuery] int? imageIndex)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
if (user == null) if (user?.ProfileImage == null)
{ {
return NotFound(); return NotFound();
} }

View File

@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given album.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given playlist.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given genre.
/// </summary> /// </summary>
/// <param name="name">The genre name.</param> /// <param name="name">The genre name.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -172,7 +172,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/{name}/InstantMix")] [HttpGet("MusicGenres/{name}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreByName(
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given artist.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given genre.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -243,7 +243,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/{id}/InstantMix")] [HttpGet("MusicGenres/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
[FromRoute, Required] Guid id, [FromRoute, Required] Guid id,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given item.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -300,6 +300,80 @@ namespace Jellyfin.Api.Controllers
return GetResult(items, user, limit, dtoOptions); return GetResult(items, user, limit, dtoOptions);
} }
/// <summary>
/// Creates an instant playlist based on a given artist.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("Artists/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
[Obsolete("Use GetInstantMixFromArtists")]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists2(
[FromQuery, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
return GetInstantMixFromArtists(
id,
userId,
limit,
fields,
enableImages,
enableUserData,
imageTypeLimit,
enableImageTypes);
}
/// <summary>
/// Creates an instant playlist based on a given genre.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
[Obsolete("Use GetInstantMixFromMusicGenres instead")]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById2(
[FromQuery, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
return GetInstantMixFromMusicGenreById(
id,
userId,
limit,
fields,
enableImages,
enableUserData,
imageTypeLimit,
enableImageTypes);
}
private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions) private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions)
{ {
var list = items; var list = items;

View File

@ -303,7 +303,7 @@ namespace Jellyfin.Api.Controllers
/// </summary> /// </summary>
/// <response code="204">Library scan started.</response> /// <response code="204">Library scan started.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpGet("Library/Refresh")] [HttpPost("Library/Refresh")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> RefreshLibrary() public async Task<ActionResult> RefreshLibrary()
@ -590,15 +590,15 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// Reports that new movies have been added by an external source. /// Reports that new movies have been added by an external source.
/// </summary> /// </summary>
/// <param name="updates">A list of updated media paths.</param> /// <param name="dto">The update paths.</param>
/// <response code="204">Report success.</response> /// <response code="204">Report success.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Library/Media/Updated")] [HttpPost("Library/Media/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates) public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto)
{ {
foreach (var item in updates) foreach (var item in dto.Updates)
{ {
_libraryMonitor.ReportFileSystemChanged(item.Path); _libraryMonitor.ReportFileSystemChanged(item.Path);
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -86,26 +87,19 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// Sends a notification to all admins. /// Sends a notification to all admins.
/// </summary> /// </summary>
/// <param name="url">The URL of the notification.</param> /// <param name="notificationDto">The notification request.</param>
/// <param name="level">The level of the notification.</param>
/// <param name="name">The name of the notification.</param>
/// <param name="description">The description of the notification.</param>
/// <response code="204">Notification sent.</response> /// <response code="204">Notification sent.</response>
/// <returns>A <cref see="NoContentResult"/>.</returns> /// <returns>A <cref see="NoContentResult"/>.</returns>
[HttpPost("Admin")] [HttpPost("Admin")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CreateAdminNotification( public ActionResult CreateAdminNotification([FromBody, Required] AdminNotificationDto notificationDto)
[FromQuery] string? url,
[FromQuery] NotificationLevel? level,
[FromQuery] string name = "",
[FromQuery] string description = "")
{ {
var notification = new NotificationRequest var notification = new NotificationRequest
{ {
Name = name, Name = notificationDto.Name,
Description = description, Description = notificationDto.Description,
Url = url, Url = notificationDto.Url,
Level = level ?? NotificationLevel.Normal, Level = notificationDto.NotificationLevel ?? NotificationLevel.Normal,
UserIds = _userManager.Users UserIds = _userManager.Users
.Where(user => user.HasPermission(PermissionKind.IsAdministrator)) .Where(user => user.HasPermission(PermissionKind.IsAdministrator))
.Select(user => user.Id) .Select(user => user.Id)
@ -114,7 +108,6 @@ namespace Jellyfin.Api.Controllers
}; };
_notificationManager.SendNotification(notification, CancellationToken.None); _notificationManager.SendNotification(notification, CancellationToken.None);
return NoContent(); return NoContent();
} }

View File

@ -45,7 +45,7 @@ namespace Jellyfin.Api.Controllers
{ {
_installationManager = installationManager; _installationManager = installationManager;
_pluginManager = pluginManager; _pluginManager = pluginManager;
_serializerOptions = JsonDefaults.GetOptions(); _serializerOptions = JsonDefaults.Options;
_config = config; _config = config;
} }

View File

@ -132,7 +132,10 @@ namespace Jellyfin.Api.Controllers
{ {
var user = _userManager.Users.First(); var user = _userManager.Users.First();
user.Username = startupUserDto.Name; if (startupUserDto.Name != null)
{
user.Username = startupUserDto.Name;
}
await _userManager.UpdateUserAsync(user).ConfigureAwait(false); await _userManager.UpdateUserAsync(user).ConfigureAwait(false);

View File

@ -1,4 +1,7 @@
namespace Jellyfin.Api.Models.LibraryDtos using System;
using System.Collections.Generic;
namespace Jellyfin.Api.Models.LibraryDtos
{ {
/// <summary> /// <summary>
/// Media Update Info Dto. /// Media Update Info Dto.
@ -6,14 +9,8 @@
public class MediaUpdateInfoDto public class MediaUpdateInfoDto
{ {
/// <summary> /// <summary>
/// Gets or sets media path. /// Gets or sets the list of updates.
/// </summary> /// </summary>
public string? Path { get; set; } public IReadOnlyList<MediaUpdateInfoPathDto> Updates { get; set; } = Array.Empty<MediaUpdateInfoPathDto>();
/// <summary>
/// Gets or sets media update type.
/// Created, Modified, Deleted.
/// </summary>
public string? UpdateType { get; set; }
} }
} }

View File

@ -0,0 +1,19 @@
namespace Jellyfin.Api.Models.LibraryDtos
{
/// <summary>
/// The media update info path.
/// </summary>
public class MediaUpdateInfoPathDto
{
/// <summary>
/// Gets or sets media path.
/// </summary>
public string? Path { get; set; }
/// <summary>
/// Gets or sets media update type.
/// Created, Modified, Deleted.
/// </summary>
public string? UpdateType { get; set; }
}
}

View File

@ -0,0 +1,30 @@
using MediaBrowser.Model.Notifications;
namespace Jellyfin.Api.Models.NotificationDtos
{
/// <summary>
/// The admin notification dto.
/// </summary>
public class AdminNotificationDto
{
/// <summary>
/// Gets or sets the notification name.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Gets or sets the notification description.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Gets or sets the notification level.
/// </summary>
public NotificationLevel? NotificationLevel { get; set; }
/// <summary>
/// Gets or sets the notification url.
/// </summary>
public string? Url { get; set; }
}
}

View File

@ -1,67 +1,21 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Generic;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
namespace Jellyfin.Data namespace Jellyfin.Data
{ {
public static class DayOfWeekHelper public static class DayOfWeekHelper
{ {
public static List<DayOfWeek> GetDaysOfWeek(DynamicDayOfWeek day) public static DayOfWeek[] GetDaysOfWeek(DynamicDayOfWeek day)
{ {
var days = new List<DayOfWeek>(7); return day switch
if (day == DynamicDayOfWeek.Sunday
|| day == DynamicDayOfWeek.Weekend
|| day == DynamicDayOfWeek.Everyday)
{ {
days.Add(DayOfWeek.Sunday); DynamicDayOfWeek.Everyday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday },
} DynamicDayOfWeek.Weekday => new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday },
DynamicDayOfWeek.Weekend => new[] { DayOfWeek.Sunday, DayOfWeek.Saturday },
if (day == DynamicDayOfWeek.Monday _ => new[] { (DayOfWeek)day }
|| day == DynamicDayOfWeek.Weekday };
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Monday);
}
if (day == DynamicDayOfWeek.Tuesday
|| day == DynamicDayOfWeek.Weekday
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Tuesday);
}
if (day == DynamicDayOfWeek.Wednesday
|| day == DynamicDayOfWeek.Weekday
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Wednesday);
}
if (day == DynamicDayOfWeek.Thursday
|| day == DynamicDayOfWeek.Weekday
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Thursday);
}
if (day == DynamicDayOfWeek.Friday
|| day == DynamicDayOfWeek.Weekday
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Friday);
}
if (day == DynamicDayOfWeek.Saturday
|| day == DynamicDayOfWeek.Weekend
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Saturday);
}
return days;
} }
} }
} }

View File

@ -1,7 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using System.Xml.Serialization; using System.Xml.Serialization;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
@ -27,14 +25,6 @@ namespace Jellyfin.Data.Entities
EndHour = endHour; EndHour = endHour;
} }
/// <summary>
/// Initializes a new instance of the <see cref="AccessSchedule"/> class.
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
protected AccessSchedule()
{
}
/// <summary> /// <summary>
/// Gets or sets the id of this instance. /// Gets or sets the id of this instance.
/// </summary> /// </summary>
@ -42,8 +32,6 @@ namespace Jellyfin.Data.Entities
/// Identity, Indexed, Required. /// Identity, Indexed, Required.
/// </remarks> /// </remarks>
[XmlIgnore] [XmlIgnore]
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; protected set; }
@ -51,41 +39,24 @@ namespace Jellyfin.Data.Entities
/// Gets or sets the id of the associated user. /// Gets or sets the id of the associated user.
/// </summary> /// </summary>
[XmlIgnore] [XmlIgnore]
[Required]
public Guid UserId { get; protected set; } public Guid UserId { get; protected set; }
/// <summary> /// <summary>
/// Gets or sets the day of week. /// Gets or sets the day of week.
/// </summary> /// </summary>
/// <value>The day of week.</value> /// <value>The day of week.</value>
[Required]
public DynamicDayOfWeek DayOfWeek { get; set; } public DynamicDayOfWeek DayOfWeek { get; set; }
/// <summary> /// <summary>
/// Gets or sets the start hour. /// Gets or sets the start hour.
/// </summary> /// </summary>
/// <value>The start hour.</value> /// <value>The start hour.</value>
[Required]
public double StartHour { get; set; } public double StartHour { get; set; }
/// <summary> /// <summary>
/// Gets or sets the end hour. /// Gets or sets the end hour.
/// </summary> /// </summary>
/// <value>The end hour.</value> /// <value>The end hour.</value>
[Required]
public double EndHour { get; set; } public double EndHour { get; set; }
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
/// <param name="dayOfWeek">The day of the week.</param>
/// <param name="startHour">The start hour.</param>
/// <param name="endHour">The end hour.</param>
/// <param name="userId">The associated user's id.</param>
/// <returns>The newly created instance.</returns>
public static AccessSchedule Create(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId)
{
return new AccessSchedule(dayOfWeek, startHour, endHour, userId);
}
} }
} }

View File

@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="userId">The user id.</param> /// <param name="userId">The user id.</param>
/// <param name="logLevel">The log level.</param> public ActivityLog(string name, string type, Guid userId)
public ActivityLog(string name, string type, Guid userId, LogLevel logLevel = LogLevel.Information)
{ {
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
{ {
@ -35,15 +34,7 @@ namespace Jellyfin.Data.Entities
Type = type; Type = type;
UserId = userId; UserId = userId;
DateCreated = DateTime.UtcNow; DateCreated = DateTime.UtcNow;
LogSeverity = logLevel; LogSeverity = LogLevel.Information;
}
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLog"/> class.
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
protected ActivityLog()
{
} }
/// <summary> /// <summary>
@ -59,7 +50,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required, Max length = 512. /// Required, Max length = 512.
/// </remarks> /// </remarks>
[Required]
[MaxLength(512)] [MaxLength(512)]
[StringLength(512)] [StringLength(512)]
public string Name { get; set; } public string Name { get; set; }
@ -72,7 +62,7 @@ namespace Jellyfin.Data.Entities
/// </remarks> /// </remarks>
[MaxLength(512)] [MaxLength(512)]
[StringLength(512)] [StringLength(512)]
public string Overview { get; set; } public string? Overview { get; set; }
/// <summary> /// <summary>
/// Gets or sets the short overview. /// Gets or sets the short overview.
@ -82,7 +72,7 @@ namespace Jellyfin.Data.Entities
/// </remarks> /// </remarks>
[MaxLength(512)] [MaxLength(512)]
[StringLength(512)] [StringLength(512)]
public string ShortOverview { get; set; } public string? ShortOverview { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type. /// Gets or sets the type.
@ -90,7 +80,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required, Max length = 256. /// Required, Max length = 256.
/// </remarks> /// </remarks>
[Required]
[MaxLength(256)] [MaxLength(256)]
[StringLength(256)] [StringLength(256)]
public string Type { get; set; } public string Type { get; set; }
@ -111,7 +100,7 @@ namespace Jellyfin.Data.Entities
/// </remarks> /// </remarks>
[MaxLength(256)] [MaxLength(256)]
[StringLength(256)] [StringLength(256)]
public string ItemId { get; set; } public string? ItemId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the date created. This should be in UTC. /// Gets or sets the date created. This should be in UTC.

View File

@ -15,22 +15,15 @@ namespace Jellyfin.Data.Entities
/// <param name="userId">The user id.</param> /// <param name="userId">The user id.</param>
/// <param name="itemId">The item id.</param> /// <param name="itemId">The item id.</param>
/// <param name="client">The client.</param> /// <param name="client">The client.</param>
/// <param name="preferenceKey">The preference key.</param> /// <param name="key">The preference key.</param>
/// <param name="preferenceValue">The preference value.</param> /// <param name="value">The preference value.</param>
public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string preferenceKey, string preferenceValue) public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string key, string value)
{ {
UserId = userId; UserId = userId;
ItemId = itemId; ItemId = itemId;
Client = client; Client = client;
Key = preferenceKey; Key = key;
Value = preferenceValue; Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomItemDisplayPreferences"/> class.
/// </summary>
protected CustomItemDisplayPreferences()
{
} }
/// <summary> /// <summary>
@ -64,7 +57,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required. Max Length = 32. /// Required. Max Length = 32.
/// </remarks> /// </remarks>
[Required]
[MaxLength(32)] [MaxLength(32)]
[StringLength(32)] [StringLength(32)]
public string Client { get; set; } public string Client { get; set; }
@ -75,7 +67,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[Required]
public string Key { get; set; } public string Key { get; set; }
/// <summary> /// <summary>
@ -84,7 +75,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[Required]
public string Value { get; set; } public string Value { get; set; }
} }
} }

View File

@ -30,19 +30,10 @@ namespace Jellyfin.Data.Entities
SkipBackwardLength = 10000; SkipBackwardLength = 10000;
ScrollDirection = ScrollDirection.Horizontal; ScrollDirection = ScrollDirection.Horizontal;
ChromecastVersion = ChromecastVersion.Stable; ChromecastVersion = ChromecastVersion.Stable;
DashboardTheme = string.Empty;
TvHome = string.Empty;
HomeSections = new HashSet<HomeSection>(); HomeSections = new HashSet<HomeSection>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="DisplayPreferences"/> class.
/// </summary>
protected DisplayPreferences()
{
}
/// <summary> /// <summary>
/// Gets or sets the Id. /// Gets or sets the Id.
/// </summary> /// </summary>
@ -74,7 +65,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required. Max Length = 32. /// Required. Max Length = 32.
/// </remarks> /// </remarks>
[Required]
[MaxLength(32)] [MaxLength(32)]
[StringLength(32)] [StringLength(32)]
public string Client { get; set; } public string Client { get; set; }
@ -145,14 +135,14 @@ namespace Jellyfin.Data.Entities
/// </summary> /// </summary>
[MaxLength(32)] [MaxLength(32)]
[StringLength(32)] [StringLength(32)]
public string DashboardTheme { get; set; } public string? DashboardTheme { get; set; }
/// <summary> /// <summary>
/// Gets or sets the tv home screen. /// Gets or sets the tv home screen.
/// </summary> /// </summary>
[MaxLength(32)] [MaxLength(32)]
[StringLength(32)] [StringLength(32)]
public string TvHome { get; set; } public string? TvHome { get; set; }
/// <summary> /// <summary>
/// Gets or sets the home sections. /// Gets or sets the home sections.

View File

@ -32,16 +32,6 @@ namespace Jellyfin.Data.Entities
Preferences = new HashSet<Preference>(); Preferences = new HashSet<Preference>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="Group"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Group()
{
}
/// <summary> /// <summary>
/// Gets or sets the id of this group. /// Gets or sets the id of this group.
/// </summary> /// </summary>
@ -56,7 +46,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required, Max length = 255. /// Required, Max length = 255.
/// </remarks> /// </remarks>
[Required]
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string Name { get; set; } public string Name { get; set; }

View File

@ -15,7 +15,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Identity. Required. /// Identity. Required.
/// </remarks> /// </remarks>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; protected set; }

View File

@ -19,16 +19,6 @@ namespace Jellyfin.Data.Entities
LastModified = DateTime.UtcNow; LastModified = DateTime.UtcNow;
} }
/// <summary>
/// Initializes a new instance of the <see cref="ImageInfo"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected ImageInfo()
{
}
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -49,7 +39,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[Required]
[MaxLength(512)] [MaxLength(512)]
[StringLength(512)] [StringLength(512)]
public string Path { get; set; } public string Path { get; set; }

View File

@ -28,13 +28,6 @@ namespace Jellyfin.Data.Entities
RememberIndexing = false; RememberIndexing = false;
} }
/// <summary>
/// Initializes a new instance of the <see cref="ItemDisplayPreferences"/> class.
/// </summary>
protected ItemDisplayPreferences()
{
}
/// <summary> /// <summary>
/// Gets or sets the Id. /// Gets or sets the Id.
/// </summary> /// </summary>
@ -66,7 +59,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required. Max Length = 32. /// Required. Max Length = 32.
/// </remarks> /// </remarks>
[Required]
[MaxLength(32)] [MaxLength(32)]
[StringLength(32)] [StringLength(32)]
public string Client { get; set; } public string Client { get; set; }
@ -106,7 +98,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[Required]
[MaxLength(64)] [MaxLength(64)]
[StringLength(64)] [StringLength(64)]
public string SortBy { get; set; } public string SortBy { get; set; }

View File

@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <param name="kind">The kind of art.</param> /// <param name="kind">The kind of art.</param>
/// <param name="owner">The owner.</param> public Artwork(string path, ArtKind kind)
public Artwork(string path, ArtKind kind, IHasArtwork owner)
{ {
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
@ -28,18 +27,6 @@ namespace Jellyfin.Data.Entities.Libraries
Path = path; Path = path;
Kind = kind; Kind = kind;
owner?.Artwork.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="Artwork"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Artwork()
{
} }
/// <summary> /// <summary>
@ -57,7 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required, Max length = 65535. /// Required, Max length = 65535.
/// </remarks> /// </remarks>
[Required]
[MaxLength(65535)] [MaxLength(65535)]
[StringLength(65535)] [StringLength(65535)]
public string Path { get; set; } public string Path { get; set; }

View File

@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Book"/> class. /// Initializes a new instance of the <see cref="Book"/> class.
/// </summary> /// </summary>
public Book() /// <param name="library">The library.</param>
public Book(Library library) : base(library)
{ {
BookMetadata = new HashSet<BookMetadata>(); BookMetadata = new HashSet<BookMetadata>();
Releases = new HashSet<Release>(); Releases = new HashSet<Release>();

View File

@ -1,8 +1,6 @@
#pragma warning disable CA2227 #pragma warning disable CA2227
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
namespace Jellyfin.Data.Entities.Libraries namespace Jellyfin.Data.Entities.Libraries
@ -17,29 +15,11 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the object.</param> /// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="book">The book.</param> public BookMetadata(string title, string language) : base(title, language)
public BookMetadata(string title, string language, Book book) : base(title, language)
{ {
if (book == null)
{
throw new ArgumentNullException(nameof(book));
}
book.BookMetadata.Add(this);
Publishers = new HashSet<Company>(); Publishers = new HashSet<Company>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="BookMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected BookMetadata()
{
}
/// <summary> /// <summary>
/// Gets or sets the ISBN. /// Gets or sets the ISBN.
/// </summary> /// </summary>
@ -51,7 +31,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> Publishers { get; protected set; } public virtual ICollection<Company> Publishers { get; protected set; }
/// <inheritdoc /> /// <inheritdoc />
[NotMapped]
public ICollection<Company> Companies => Publishers; public ICollection<Company> Companies => Publishers;
} }
} }

View File

@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="startTime">The start time for this chapter.</param> /// <param name="startTime">The start time for this chapter.</param>
/// <param name="release">The release.</param> public Chapter(string language, long startTime)
public Chapter(string language, long startTime, Release release)
{ {
if (string.IsNullOrEmpty(language)) if (string.IsNullOrEmpty(language))
{ {
@ -27,23 +26,6 @@ namespace Jellyfin.Data.Entities.Libraries
Language = language; Language = language;
StartTime = startTime; StartTime = startTime;
if (release == null)
{
throw new ArgumentNullException(nameof(release));
}
release.Chapters.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="Chapter"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Chapter()
{
} }
/// <summary> /// <summary>
@ -63,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Name { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the language. /// Gets or sets the language.
@ -72,7 +54,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// Required, Min length = 3, Max length = 3 /// Required, Min length = 3, Max length = 3
/// ISO-639-3 3-character language codes. /// ISO-639-3 3-character language codes.
/// </remarks> /// </remarks>
[Required]
[MinLength(3)] [MinLength(3)]
[MaxLength(3)] [MaxLength(3)]
[StringLength(3)] [StringLength(3)]

View File

@ -1,3 +1,4 @@
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
#pragma warning disable CA2227 #pragma warning disable CA2227
using System.Collections.Generic; using System.Collections.Generic;
@ -37,7 +38,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Name { get; set; } public string? Name { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[ConcurrencyCheck] [ConcurrencyCheck]

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -13,39 +12,10 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CollectionItem"/> class. /// Initializes a new instance of the <see cref="CollectionItem"/> class.
/// </summary> /// </summary>
/// <param name="collection">The collection.</param> /// <param name="libraryItem">The library item.</param>
/// <param name="previous">The previous item.</param> public CollectionItem(LibraryItem libraryItem)
/// <param name="next">The next item.</param>
public CollectionItem(Collection collection, CollectionItem previous, CollectionItem next)
{
if (collection == null)
{
throw new ArgumentNullException(nameof(collection));
}
collection.Items.Add(this);
if (next != null)
{
Next = next;
next.Previous = this;
}
if (previous != null)
{
Previous = previous;
previous.Next = this;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CollectionItem"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected CollectionItem()
{ {
LibraryItem = libraryItem;
} }
/// <summary> /// <summary>
@ -75,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// TODO check if this properly updated Dependant and has the proper principal relationship. /// TODO check if this properly updated Dependant and has the proper principal relationship.
/// </remarks> /// </remarks>
public virtual CollectionItem Next { get; set; } public virtual CollectionItem? Next { get; set; }
/// <summary> /// <summary>
/// Gets or sets the previous item in the collection. /// Gets or sets the previous item in the collection.
@ -83,7 +53,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// TODO check if this properly updated Dependant and has the proper principal relationship. /// TODO check if this properly updated Dependant and has the proper principal relationship.
/// </remarks> /// </remarks>
public virtual CollectionItem Previous { get; set; } public virtual CollectionItem? Previous { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public void OnSavingChanges() public void OnSavingChanges()

View File

@ -15,22 +15,10 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Company"/> class. /// Initializes a new instance of the <see cref="Company"/> class.
/// </summary> /// </summary>
/// <param name="owner">The owner of this company.</param> public Company()
public Company(IHasCompanies owner)
{ {
owner?.Companies.Add(this);
CompanyMetadata = new HashSet<CompanyMetadata>(); CompanyMetadata = new HashSet<CompanyMetadata>();
} ChildCompanies = new HashSet<Company>();
/// <summary>
/// Initializes a new instance of the <see cref="Company"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Company()
{
} }
/// <summary> /// <summary>
@ -57,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> ChildCompanies { get; protected set; } public virtual ICollection<Company> ChildCompanies { get; protected set; }
/// <inheritdoc /> /// <inheritdoc />
[NotMapped]
public ICollection<Company> Companies => ChildCompanies; public ICollection<Company> Companies => ChildCompanies;
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries namespace Jellyfin.Data.Entities.Libraries
@ -13,21 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the object.</param> /// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="company">The company.</param> public CompanyMetadata(string title, string language) : base(title, language)
public CompanyMetadata(string title, string language, Company company) : base(title, language)
{
if (company == null)
{
throw new ArgumentNullException(nameof(company));
}
company.CompanyMetadata.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="CompanyMetadata"/> class.
/// </summary>
protected CompanyMetadata()
{ {
} }
@ -39,7 +24,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(65535)] [MaxLength(65535)]
[StringLength(65535)] [StringLength(65535)]
public string Description { get; set; } public string? Description { get; set; }
/// <summary> /// <summary>
/// Gets or sets the headquarters. /// Gets or sets the headquarters.
@ -49,7 +34,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string Headquarters { get; set; } public string? Headquarters { get; set; }
/// <summary> /// <summary>
/// Gets or sets the country code. /// Gets or sets the country code.
@ -59,7 +44,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(2)] [MaxLength(2)]
[StringLength(2)] [StringLength(2)]
public string Country { get; set; } public string? Country { get; set; }
/// <summary> /// <summary>
/// Gets or sets the homepage. /// Gets or sets the homepage.
@ -69,6 +54,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Homepage { get; set; } public string? Homepage { get; set; }
} }
} }

View File

@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CustomItem"/> class. /// Initializes a new instance of the <see cref="CustomItem"/> class.
/// </summary> /// </summary>
public CustomItem() /// <param name="library">The library.</param>
public CustomItem(Library library) : base(library)
{ {
CustomItemMetadata = new HashSet<CustomItemMetadata>(); CustomItemMetadata = new HashSet<CustomItemMetadata>();
Releases = new HashSet<Release>(); Releases = new HashSet<Release>();

View File

@ -1,5 +1,3 @@
using System;
namespace Jellyfin.Data.Entities.Libraries namespace Jellyfin.Data.Entities.Libraries
{ {
/// <summary> /// <summary>
@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the object.</param> /// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="item">The item.</param> public CustomItemMetadata(string title, string language) : base(title, language)
public CustomItemMetadata(string title, string language, CustomItem item) : base(title, language)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
item.CustomItemMetadata.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomItemMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected CustomItemMetadata()
{ {
} }
} }

View File

@ -1,6 +1,5 @@
#pragma warning disable CA2227 #pragma warning disable CA2227
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -14,30 +13,13 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Episode"/> class. /// Initializes a new instance of the <see cref="Episode"/> class.
/// </summary> /// </summary>
/// <param name="season">The season.</param> /// <param name="library">The library.</param>
public Episode(Season season) public Episode(Library library) : base(library)
{ {
if (season == null)
{
throw new ArgumentNullException(nameof(season));
}
season.Episodes.Add(this);
Releases = new HashSet<Release>(); Releases = new HashSet<Release>();
EpisodeMetadata = new HashSet<EpisodeMetadata>(); EpisodeMetadata = new HashSet<EpisodeMetadata>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="Episode"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Episode()
{
}
/// <summary> /// <summary>
/// Gets or sets the episode number. /// Gets or sets the episode number.
/// </summary> /// </summary>

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries namespace Jellyfin.Data.Entities.Libraries
@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the object.</param> /// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="episode">The episode.</param> public EpisodeMetadata(string title, string language) : base(title, language)
public EpisodeMetadata(string title, string language, Episode episode) : base(title, language)
{
if (episode == null)
{
throw new ArgumentNullException(nameof(episode));
}
episode.EpisodeMetadata.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected EpisodeMetadata()
{ {
} }
@ -42,7 +24,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Outline { get; set; } public string? Outline { get; set; }
/// <summary> /// <summary>
/// Gets or sets the plot. /// Gets or sets the plot.
@ -52,7 +34,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(65535)] [MaxLength(65535)]
[StringLength(65535)] [StringLength(65535)]
public string Plot { get; set; } public string? Plot { get; set; }
/// <summary> /// <summary>
/// Gets or sets the tagline. /// Gets or sets the tagline.
@ -62,6 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Tagline { get; set; } public string? Tagline { get; set; }
} }
} }

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -14,32 +13,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Genre"/> class. /// Initializes a new instance of the <see cref="Genre"/> class.
/// </summary> /// </summary>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="itemMetadata">The metadata.</param> public Genre(string name)
public Genre(string name, ItemMetadata itemMetadata)
{ {
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
Name = name; Name = name;
if (itemMetadata == null)
{
throw new ArgumentNullException(nameof(itemMetadata));
}
itemMetadata.Genres.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="Genre"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Genre()
{
} }
/// <summary> /// <summary>
@ -57,7 +33,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Indexed, Required, Max length = 255. /// Indexed, Required, Max length = 255.
/// </remarks> /// </remarks>
[Required]
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string Name { get; set; } public string Name { get; set; }

View File

@ -42,16 +42,6 @@ namespace Jellyfin.Data.Entities.Libraries
Sources = new HashSet<MetadataProviderId>(); Sources = new HashSet<MetadataProviderId>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="ItemMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to being abstract.
/// </remarks>
protected ItemMetadata()
{
}
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -67,7 +57,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required, Max length = 1024. /// Required, Max length = 1024.
/// </remarks> /// </remarks>
[Required]
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Title { get; set; } public string Title { get; set; }
@ -80,7 +69,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string OriginalTitle { get; set; } public string? OriginalTitle { get; set; }
/// <summary> /// <summary>
/// Gets or sets the sort title. /// Gets or sets the sort title.
@ -90,7 +79,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string SortTitle { get; set; } public string? SortTitle { get; set; }
/// <summary> /// <summary>
/// Gets or sets the language. /// Gets or sets the language.
@ -99,7 +88,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// Required, Min length = 3, Max length = 3. /// Required, Min length = 3, Max length = 3.
/// ISO-639-3 3-character language codes. /// ISO-639-3 3-character language codes.
/// </remarks> /// </remarks>
[Required]
[MinLength(3)] [MinLength(3)]
[MaxLength(3)] [MaxLength(3)]
[StringLength(3)] [StringLength(3)]

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -14,24 +13,11 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Library"/> class. /// Initializes a new instance of the <see cref="Library"/> class.
/// </summary> /// </summary>
/// <param name="name">The name of the library.</param> /// <param name="name">The name of the library.</param>
public Library(string name) /// <param name="path">The path of the library.</param>
public Library(string name, string path)
{ {
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
Name = name; Name = name;
} Path = path;
/// <summary>
/// Initializes a new instance of the <see cref="Library"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Library()
{
} }
/// <summary> /// <summary>
@ -49,7 +35,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required, Max length = 128. /// Required, Max length = 128.
/// </remarks> /// </remarks>
[Required]
[MaxLength(128)] [MaxLength(128)]
[StringLength(128)] [StringLength(128)]
public string Name { get; set; } public string Name { get; set; }
@ -60,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[Required]
public string Path { get; set; } public string Path { get; set; }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -20,13 +20,6 @@ namespace Jellyfin.Data.Entities.Libraries
Library = library; Library = library;
} }
/// <summary>
/// Initializes a new instance of the <see cref="LibraryItem"/> class.
/// </summary>
protected LibraryItem()
{
}
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -51,7 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[Required]
public virtual Library Library { get; set; } public virtual Library Library { get; set; }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -19,8 +19,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="path">The path relative to the LibraryRoot.</param> /// <param name="path">The path relative to the LibraryRoot.</param>
/// <param name="kind">The file kind.</param> /// <param name="kind">The file kind.</param>
/// <param name="release">The release.</param> public MediaFile(string path, MediaFileKind kind)
public MediaFile(string path, MediaFileKind kind, Release release)
{ {
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
@ -30,26 +29,9 @@ namespace Jellyfin.Data.Entities.Libraries
Path = path; Path = path;
Kind = kind; Kind = kind;
if (release == null)
{
throw new ArgumentNullException(nameof(release));
}
release.MediaFiles.Add(this);
MediaFileStreams = new HashSet<MediaFileStream>(); MediaFileStreams = new HashSet<MediaFileStream>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="MediaFile"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected MediaFile()
{
}
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -65,7 +47,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required, Max length = 65535. /// Required, Max length = 65535.
/// </remarks> /// </remarks>
[Required]
[MaxLength(65535)] [MaxLength(65535)]
[StringLength(65535)] [StringLength(65535)]
public string Path { get; set; } public string Path { get; set; }

View File

@ -1,4 +1,5 @@
using System; #pragma warning disable CA1711 // Identifiers should not have incorrect suffix
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -14,27 +15,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="MediaFileStream"/> class. /// Initializes a new instance of the <see cref="MediaFileStream"/> class.
/// </summary> /// </summary>
/// <param name="streamNumber">The number of this stream.</param> /// <param name="streamNumber">The number of this stream.</param>
/// <param name="mediaFile">The media file.</param> public MediaFileStream(int streamNumber)
public MediaFileStream(int streamNumber, MediaFile mediaFile)
{ {
StreamNumber = streamNumber; StreamNumber = streamNumber;
if (mediaFile == null)
{
throw new ArgumentNullException(nameof(mediaFile));
}
mediaFile.MediaFileStreams.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="MediaFileStream"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected MediaFileStream()
{
} }
/// <summary> /// <summary>

View File

@ -24,16 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries
Name = name; Name = name;
} }
/// <summary>
/// Initializes a new instance of the <see cref="MetadataProvider"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected MetadataProvider()
{
}
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -49,7 +39,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required, Max length = 1024. /// Required, Max length = 1024.
/// </remarks> /// </remarks>
[Required]
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Name { get; set; } public string Name { get; set; }

View File

@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="MetadataProviderId"/> class. /// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
/// </summary> /// </summary>
/// <param name="providerId">The provider id.</param> /// <param name="providerId">The provider id.</param>
/// <param name="itemMetadata">The metadata entity.</param> /// <param name="metadataProvider">The metadata provider.</param>
public MetadataProviderId(string providerId, ItemMetadata itemMetadata) public MetadataProviderId(string providerId, MetadataProvider metadataProvider)
{ {
if (string.IsNullOrEmpty(providerId)) if (string.IsNullOrEmpty(providerId))
{ {
@ -23,23 +23,7 @@ namespace Jellyfin.Data.Entities.Libraries
} }
ProviderId = providerId; ProviderId = providerId;
MetadataProvider = metadataProvider;
if (itemMetadata == null)
{
throw new ArgumentNullException(nameof(itemMetadata));
}
itemMetadata.Sources.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected MetadataProviderId()
{
} }
/// <summary> /// <summary>
@ -57,7 +41,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required, Max length = 255. /// Required, Max length = 255.
/// </remarks> /// </remarks>
[Required]
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string ProviderId { get; set; } public string ProviderId { get; set; }

View File

@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Movie"/> class. /// Initializes a new instance of the <see cref="Movie"/> class.
/// </summary> /// </summary>
public Movie() /// <param name="library">The library.</param>
public Movie(Library library) : base(library)
{ {
Releases = new HashSet<Release>(); Releases = new HashSet<Release>();
MovieMetadata = new HashSet<MovieMetadata>(); MovieMetadata = new HashSet<MovieMetadata>();

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
namespace Jellyfin.Data.Entities.Libraries namespace Jellyfin.Data.Entities.Libraries
@ -17,22 +16,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the movie.</param> /// <param name="title">The title or name of the movie.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="movie">The movie.</param> public MovieMetadata(string title, string language) : base(title, language)
public MovieMetadata(string title, string language, Movie movie) : base(title, language)
{ {
Studios = new HashSet<Company>(); Studios = new HashSet<Company>();
movie.MovieMetadata.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="MovieMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected MovieMetadata()
{
} }
/// <summary> /// <summary>
@ -43,7 +29,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Outline { get; set; } public string? Outline { get; set; }
/// <summary> /// <summary>
/// Gets or sets the tagline. /// Gets or sets the tagline.
@ -53,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Tagline { get; set; } public string? Tagline { get; set; }
/// <summary> /// <summary>
/// Gets or sets the plot. /// Gets or sets the plot.
@ -63,7 +49,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(65535)] [MaxLength(65535)]
[StringLength(65535)] [StringLength(65535)]
public string Plot { get; set; } public string? Plot { get; set; }
/// <summary> /// <summary>
/// Gets or sets the country code. /// Gets or sets the country code.
@ -73,7 +59,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(2)] [MaxLength(2)]
[StringLength(2)] [StringLength(2)]
public string Country { get; set; } public string? Country { get; set; }
/// <summary> /// <summary>
/// Gets or sets the studios that produced this movie. /// Gets or sets the studios that produced this movie.
@ -81,7 +67,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> Studios { get; protected set; } public virtual ICollection<Company> Studios { get; protected set; }
/// <inheritdoc /> /// <inheritdoc />
[NotMapped]
public ICollection<Company> Companies => Studios; public ICollection<Company> Companies => Studios;
} }
} }

View File

@ -12,7 +12,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MusicAlbum"/> class. /// Initializes a new instance of the <see cref="MusicAlbum"/> class.
/// </summary> /// </summary>
public MusicAlbum() /// <param name="library">The library.</param>
public MusicAlbum(Library library) : base(library)
{ {
MusicAlbumMetadata = new HashSet<MusicAlbumMetadata>(); MusicAlbumMetadata = new HashSet<MusicAlbumMetadata>();
Tracks = new HashSet<Track>(); Tracks = new HashSet<Track>();

View File

@ -15,22 +15,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the album.</param> /// <param name="title">The title or name of the album.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="album">The music album.</param> public MusicAlbumMetadata(string title, string language) : base(title, language)
public MusicAlbumMetadata(string title, string language, MusicAlbum album) : base(title, language)
{ {
Labels = new HashSet<Company>(); Labels = new HashSet<Company>();
album.MusicAlbumMetadata.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="MusicAlbumMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected MusicAlbumMetadata()
{
} }
/// <summary> /// <summary>
@ -41,7 +28,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string Barcode { get; set; } public string? Barcode { get; set; }
/// <summary> /// <summary>
/// Gets or sets the label number. /// Gets or sets the label number.
@ -51,7 +38,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string LabelNumber { get; set; } public string? LabelNumber { get; set; }
/// <summary> /// <summary>
/// Gets or sets the country code. /// Gets or sets the country code.
@ -61,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(2)] [MaxLength(2)]
[StringLength(2)] [StringLength(2)]
public string Country { get; set; } public string? Country { get; set; }
/// <summary> /// <summary>
/// Gets or sets a collection containing the labels. /// Gets or sets a collection containing the labels.

View File

@ -31,16 +31,6 @@ namespace Jellyfin.Data.Entities.Libraries
Sources = new HashSet<MetadataProviderId>(); Sources = new HashSet<MetadataProviderId>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="Person"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Person()
{
}
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -56,7 +46,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required, Max length = 1024. /// Required, Max length = 1024.
/// </remarks> /// </remarks>
[Required]
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Name { get; set; } public string Name { get; set; }
@ -69,7 +58,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(256)] [MaxLength(256)]
[StringLength(256)] [StringLength(256)]
public string SourceId { get; set; } public string? SourceId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the date added. /// Gets or sets the date added.

View File

@ -1,6 +1,5 @@
#pragma warning disable CA2227 #pragma warning disable CA2227
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -18,31 +17,15 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="PersonRole"/> class. /// Initializes a new instance of the <see cref="PersonRole"/> class.
/// </summary> /// </summary>
/// <param name="type">The role type.</param> /// <param name="type">The role type.</param>
/// <param name="itemMetadata">The metadata.</param> /// <param name="person">The person.</param>
public PersonRole(PersonRoleType type, ItemMetadata itemMetadata) public PersonRole(PersonRoleType type, Person person)
{ {
Type = type; Type = type;
Person = person;
if (itemMetadata == null) Artwork = new HashSet<Artwork>();
{
throw new ArgumentNullException(nameof(itemMetadata));
}
itemMetadata.PersonRoles.Add(this);
Sources = new HashSet<MetadataProviderId>(); Sources = new HashSet<MetadataProviderId>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="PersonRole"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected PersonRole()
{
}
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -60,7 +43,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Role { get; set; } public string? Role { get; set; }
/// <summary> /// <summary>
/// Gets or sets the person's role type. /// Gets or sets the person's role type.
@ -80,7 +63,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[Required]
public virtual Person Person { get; set; } public virtual Person Person { get; set; }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Photo"/> class. /// Initializes a new instance of the <see cref="Photo"/> class.
/// </summary> /// </summary>
public Photo() /// <param name="library">The library.</param>
public Photo(Library library) : base(library)
{ {
PhotoMetadata = new HashSet<PhotoMetadata>(); PhotoMetadata = new HashSet<PhotoMetadata>();
Releases = new HashSet<Release>(); Releases = new HashSet<Release>();

View File

@ -1,5 +1,3 @@
using System;
namespace Jellyfin.Data.Entities.Libraries namespace Jellyfin.Data.Entities.Libraries
{ {
/// <summary> /// <summary>
@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the photo.</param> /// <param name="title">The title or name of the photo.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="photo">The photo.</param> public PhotoMetadata(string title, string language) : base(title, language)
public PhotoMetadata(string title, string language, Photo photo) : base(title, language)
{
if (photo == null)
{
throw new ArgumentNullException(nameof(photo));
}
photo.PhotoMetadata.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="PhotoMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected PhotoMetadata()
{ {
} }
} }

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -14,27 +13,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Rating"/> class. /// Initializes a new instance of the <see cref="Rating"/> class.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="itemMetadata">The metadata.</param> public Rating(double value)
public Rating(double value, ItemMetadata itemMetadata)
{ {
Value = value; Value = value;
if (itemMetadata == null)
{
throw new ArgumentNullException(nameof(itemMetadata));
}
itemMetadata.Ratings.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="Rating"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Rating()
{
} }
/// <summary> /// <summary>
@ -67,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Gets or sets the rating type. /// Gets or sets the rating type.
/// If this is <c>null</c> it's the internal user rating. /// If this is <c>null</c> it's the internal user rating.
/// </summary> /// </summary>
public virtual RatingSource RatingType { get; set; } public virtual RatingSource? RatingType { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public void OnSavingChanges() public void OnSavingChanges()

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -15,28 +14,10 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="minimumValue">The minimum value.</param> /// <param name="minimumValue">The minimum value.</param>
/// <param name="maximumValue">The maximum value.</param> /// <param name="maximumValue">The maximum value.</param>
/// <param name="rating">The rating.</param> public RatingSource(double minimumValue, double maximumValue)
public RatingSource(double minimumValue, double maximumValue, Rating rating)
{ {
MinimumValue = minimumValue; MinimumValue = minimumValue;
MaximumValue = maximumValue; MaximumValue = maximumValue;
if (rating == null)
{
throw new ArgumentNullException(nameof(rating));
}
rating.RatingType = this;
}
/// <summary>
/// Initializes a new instance of the <see cref="RatingSource"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected RatingSource()
{
} }
/// <summary> /// <summary>
@ -56,7 +37,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Name { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the minimum value. /// Gets or sets the minimum value.
@ -81,7 +62,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Gets or sets the metadata source. /// Gets or sets the metadata source.
/// </summary> /// </summary>
public virtual MetadataProviderId Source { get; set; } public virtual MetadataProviderId? Source { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public void OnSavingChanges() public void OnSavingChanges()

View File

@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Release"/> class. /// Initializes a new instance of the <see cref="Release"/> class.
/// </summary> /// </summary>
/// <param name="name">The name of this release.</param> /// <param name="name">The name of this release.</param>
/// <param name="owner">The owner of this release.</param> public Release(string name)
public Release(string name, IHasReleases owner)
{ {
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
{ {
@ -27,22 +26,10 @@ namespace Jellyfin.Data.Entities.Libraries
Name = name; Name = name;
owner?.Releases.Add(this);
MediaFiles = new HashSet<MediaFile>(); MediaFiles = new HashSet<MediaFile>();
Chapters = new HashSet<Chapter>(); Chapters = new HashSet<Chapter>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="Release"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Release()
{
}
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -58,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks> /// <remarks>
/// Required, Max length = 1024. /// Required, Max length = 1024.
/// </remarks> /// </remarks>
[Required]
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Name { get; set; } public string Name { get; set; }

View File

@ -1,6 +1,5 @@
#pragma warning disable CA2227 #pragma warning disable CA2227
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Jellyfin.Data.Entities.Libraries namespace Jellyfin.Data.Entities.Libraries
@ -13,30 +12,13 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Season"/> class. /// Initializes a new instance of the <see cref="Season"/> class.
/// </summary> /// </summary>
/// <param name="series">The series.</param> /// <param name="library">The library.</param>
public Season(Series series) public Season(Library library) : base(library)
{ {
if (series == null)
{
throw new ArgumentNullException(nameof(series));
}
series.Seasons.Add(this);
Episodes = new HashSet<Episode>(); Episodes = new HashSet<Episode>();
SeasonMetadata = new HashSet<SeasonMetadata>(); SeasonMetadata = new HashSet<SeasonMetadata>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="Season"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Season()
{
}
/// <summary> /// <summary>
/// Gets or sets the season number. /// Gets or sets the season number.
/// </summary> /// </summary>

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries namespace Jellyfin.Data.Entities.Libraries
@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the object.</param> /// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="season">The season.</param> public SeasonMetadata(string title, string language) : base(title, language)
public SeasonMetadata(string title, string language, Season season) : base(title, language)
{
if (season == null)
{
throw new ArgumentNullException(nameof(season));
}
season.SeasonMetadata.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="SeasonMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected SeasonMetadata()
{ {
} }
@ -42,6 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Outline { get; set; } public string? Outline { get; set; }
} }
} }

View File

@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Series"/> class. /// Initializes a new instance of the <see cref="Series"/> class.
/// </summary> /// </summary>
public Series() /// <param name="library">The library.</param>
public Series(Library library) : base(library)
{ {
DateAdded = DateTime.UtcNow; DateAdded = DateTime.UtcNow;
Seasons = new HashSet<Season>(); Seasons = new HashSet<Season>();

View File

@ -1,6 +1,5 @@
#pragma warning disable CA2227 #pragma warning disable CA2227
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -18,29 +17,11 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the object.</param> /// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="series">The series.</param> public SeriesMetadata(string title, string language) : base(title, language)
public SeriesMetadata(string title, string language, Series series) : base(title, language)
{ {
if (series == null)
{
throw new ArgumentNullException(nameof(series));
}
series.SeriesMetadata.Add(this);
Networks = new HashSet<Company>(); Networks = new HashSet<Company>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="SeriesMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected SeriesMetadata()
{
}
/// <summary> /// <summary>
/// Gets or sets the outline. /// Gets or sets the outline.
/// </summary> /// </summary>
@ -49,7 +30,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Outline { get; set; } public string? Outline { get; set; }
/// <summary> /// <summary>
/// Gets or sets the plot. /// Gets or sets the plot.
@ -59,7 +40,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(65535)] [MaxLength(65535)]
[StringLength(65535)] [StringLength(65535)]
public string Plot { get; set; } public string? Plot { get; set; }
/// <summary> /// <summary>
/// Gets or sets the tagline. /// Gets or sets the tagline.
@ -69,7 +50,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(1024)] [MaxLength(1024)]
[StringLength(1024)] [StringLength(1024)]
public string Tagline { get; set; } public string? Tagline { get; set; }
/// <summary> /// <summary>
/// Gets or sets the country code. /// Gets or sets the country code.
@ -79,7 +60,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks> /// </remarks>
[MaxLength(2)] [MaxLength(2)]
[StringLength(2)] [StringLength(2)]
public string Country { get; set; } public string? Country { get; set; }
/// <summary> /// <summary>
/// Gets or sets a collection containing the networks. /// Gets or sets a collection containing the networks.
@ -87,7 +68,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> Networks { get; protected set; } public virtual ICollection<Company> Networks { get; protected set; }
/// <inheritdoc /> /// <inheritdoc />
[NotMapped]
public ICollection<Company> Companies => Networks; public ICollection<Company> Companies => Networks;
} }
} }

View File

@ -1,6 +1,5 @@
#pragma warning disable CA2227 #pragma warning disable CA2227
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -14,30 +13,13 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Track"/> class. /// Initializes a new instance of the <see cref="Track"/> class.
/// </summary> /// </summary>
/// <param name="album">The album.</param> /// <param name="library">The library.</param>
public Track(MusicAlbum album) public Track(Library library) : base(library)
{ {
if (album == null)
{
throw new ArgumentNullException(nameof(album));
}
album.Tracks.Add(this);
Releases = new HashSet<Release>(); Releases = new HashSet<Release>();
TrackMetadata = new HashSet<TrackMetadata>(); TrackMetadata = new HashSet<TrackMetadata>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="Track"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected Track()
{
}
/// <summary> /// <summary>
/// Gets or sets the track number. /// Gets or sets the track number.
/// </summary> /// </summary>

View File

@ -1,5 +1,3 @@
using System;
namespace Jellyfin.Data.Entities.Libraries namespace Jellyfin.Data.Entities.Libraries
{ {
/// <summary> /// <summary>
@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary> /// </summary>
/// <param name="title">The title or name of the object.</param> /// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param> /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="track">The track.</param> public TrackMetadata(string title, string language) : base(title, language)
public TrackMetadata(string title, string language, Track track) : base(title, language)
{
if (track == null)
{
throw new ArgumentNullException(nameof(track));
}
track.TrackMetadata.Add(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="TrackMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </remarks>
protected TrackMetadata()
{ {
} }
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
@ -22,14 +24,6 @@ namespace Jellyfin.Data.Entities
Value = value; Value = value;
} }
/// <summary>
/// Initializes a new instance of the <see cref="Permission"/> class.
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
protected Permission()
{
}
/// <summary> /// <summary>
/// Gets or sets the id of this permission. /// Gets or sets the id of this permission.
/// </summary> /// </summary>

View File

@ -23,14 +23,6 @@ namespace Jellyfin.Data.Entities
Value = value ?? throw new ArgumentNullException(nameof(value)); Value = value ?? throw new ArgumentNullException(nameof(value));
} }
/// <summary>
/// Initializes a new instance of the <see cref="Preference"/> class.
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
protected Preference()
{
}
/// <summary> /// <summary>
/// Gets or sets the id of this preference. /// Gets or sets the id of this preference.
/// </summary> /// </summary>
@ -54,7 +46,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required, Max length = 65535. /// Required, Max length = 65535.
/// </remarks> /// </remarks>
[Required]
[MaxLength(65535)] [MaxLength(65535)]
[StringLength(65535)] [StringLength(65535)]
public string Value { get; set; } public string Value { get; set; }

View File

@ -51,6 +51,7 @@ namespace Jellyfin.Data.Entities
PasswordResetProviderId = passwordResetProviderId; PasswordResetProviderId = passwordResetProviderId;
AccessSchedules = new HashSet<AccessSchedule>(); AccessSchedules = new HashSet<AccessSchedule>();
DisplayPreferences = new HashSet<DisplayPreferences>();
ItemDisplayPreferences = new HashSet<ItemDisplayPreferences>(); ItemDisplayPreferences = new HashSet<ItemDisplayPreferences>();
// Groups = new HashSet<Group>(); // Groups = new HashSet<Group>();
Permissions = new HashSet<Permission>(); Permissions = new HashSet<Permission>();
@ -72,17 +73,6 @@ namespace Jellyfin.Data.Entities
PlayDefaultAudioTrack = true; PlayDefaultAudioTrack = true;
SubtitleMode = SubtitlePlaybackMode.Default; SubtitleMode = SubtitlePlaybackMode.Default;
SyncPlayAccess = SyncPlayUserAccessType.CreateAndJoinGroups; SyncPlayAccess = SyncPlayUserAccessType.CreateAndJoinGroups;
AddDefaultPermissions();
AddDefaultPreferences();
}
/// <summary>
/// Initializes a new instance of the <see cref="User"/> class.
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
protected User()
{
} }
/// <summary> /// <summary>
@ -100,7 +90,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required, Max length = 255. /// Required, Max length = 255.
/// </remarks> /// </remarks>
[Required]
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string Username { get; set; } public string Username { get; set; }
@ -113,7 +102,7 @@ namespace Jellyfin.Data.Entities
/// </remarks> /// </remarks>
[MaxLength(65535)] [MaxLength(65535)]
[StringLength(65535)] [StringLength(65535)]
public string Password { get; set; } public string? Password { get; set; }
/// <summary> /// <summary>
/// Gets or sets the user's easy password, or <c>null</c> if none is set. /// Gets or sets the user's easy password, or <c>null</c> if none is set.
@ -123,7 +112,7 @@ namespace Jellyfin.Data.Entities
/// </remarks> /// </remarks>
[MaxLength(65535)] [MaxLength(65535)]
[StringLength(65535)] [StringLength(65535)]
public string EasyPassword { get; set; } public string? EasyPassword { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the user must update their password. /// Gets or sets a value indicating whether the user must update their password.
@ -141,7 +130,7 @@ namespace Jellyfin.Data.Entities
/// </remarks> /// </remarks>
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string AudioLanguagePreference { get; set; } public string? AudioLanguagePreference { get; set; }
/// <summary> /// <summary>
/// Gets or sets the authentication provider id. /// Gets or sets the authentication provider id.
@ -149,7 +138,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required, Max length = 255. /// Required, Max length = 255.
/// </remarks> /// </remarks>
[Required]
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string AuthenticationProviderId { get; set; } public string AuthenticationProviderId { get; set; }
@ -160,7 +148,6 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required, Max length = 255. /// Required, Max length = 255.
/// </remarks> /// </remarks>
[Required]
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string PasswordResetProviderId { get; set; } public string PasswordResetProviderId { get; set; }
@ -217,7 +204,7 @@ namespace Jellyfin.Data.Entities
/// </remarks> /// </remarks>
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
public string SubtitleLanguagePreference { get; set; } public string? SubtitleLanguagePreference { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether missing episodes should be displayed. /// Gets or sets a value indicating whether missing episodes should be displayed.
@ -312,7 +299,7 @@ namespace Jellyfin.Data.Entities
/// Gets or sets the user's profile image. Can be <c>null</c>. /// Gets or sets the user's profile image. Can be <c>null</c>.
/// </summary> /// </summary>
// [ForeignKey("UserId")] // [ForeignKey("UserId")]
public virtual ImageInfo ProfileImage { get; set; } public virtual ImageInfo? ProfileImage { get; set; }
/// <summary> /// <summary>
/// Gets or sets the user's display preferences. /// Gets or sets the user's display preferences.
@ -320,8 +307,7 @@ namespace Jellyfin.Data.Entities
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[Required] public virtual ICollection<DisplayPreferences> DisplayPreferences { get; set; }
public virtual DisplayPreferences DisplayPreferences { get; set; }
/// <summary> /// <summary>
/// Gets or sets the level of sync play permissions this user has. /// Gets or sets the level of sync play permissions this user has.
@ -494,18 +480,11 @@ namespace Jellyfin.Data.Entities
return Array.IndexOf(GetPreferenceValues<Guid>(PreferenceKind.GroupedFolders), id) != -1; return Array.IndexOf(GetPreferenceValues<Guid>(PreferenceKind.GroupedFolders), id) != -1;
} }
private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) /// <summary>
{ /// Initializes the default permissions for a user. Should only be called on user creation.
var localTime = date.ToLocalTime(); /// </summary>
var hour = localTime.TimeOfDay.TotalHours;
return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek)
&& hour >= schedule.StartHour
&& hour <= schedule.EndHour;
}
// TODO: make these user configurable? // TODO: make these user configurable?
private void AddDefaultPermissions() public void AddDefaultPermissions()
{ {
Permissions.Add(new Permission(PermissionKind.IsAdministrator, false)); Permissions.Add(new Permission(PermissionKind.IsAdministrator, false));
Permissions.Add(new Permission(PermissionKind.IsDisabled, false)); Permissions.Add(new Permission(PermissionKind.IsDisabled, false));
@ -530,12 +509,25 @@ namespace Jellyfin.Data.Entities
Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false)); Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false));
} }
private void AddDefaultPreferences() /// <summary>
/// Initializes the default preferences. Should only be called on user creation.
/// </summary>
public void AddDefaultPreferences()
{ {
foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast<PreferenceKind>()) foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast<PreferenceKind>())
{ {
Preferences.Add(new Preference(val, string.Empty)); Preferences.Add(new Preference(val, string.Empty));
} }
} }
private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date)
{
var localTime = date.ToLocalTime();
var hour = localTime.TimeOfDay.TotalHours;
return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek)
&& hour >= schedule.StartHour
&& hour <= schedule.EndHour;
}
} }
} }

View File

@ -2,7 +2,7 @@
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
namespace Jellyfin.Data namespace Jellyfin.Data.Interfaces
{ {
/// <summary> /// <summary>
/// An abstraction representing an entity that has permissions. /// An abstraction representing an entity that has permissions.

View File

@ -5,6 +5,9 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<Nullable>enable</Nullable>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols> <IncludeSymbols>true</IncludeSymbols>
@ -24,10 +27,6 @@
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
@ -40,8 +39,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -11,6 +11,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -30,6 +32,11 @@
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<!-- Needed for https://github.com/dotnet/roslyn-analyzers/issues/4382 which is in the SDK yet -->
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
</ItemGroup>
<!-- Code analysers--> <!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
@ -37,8 +44,4 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project> </Project>

View File

@ -274,8 +274,8 @@ namespace Jellyfin.Drawing.Skia
if (requiresTransparencyHack || forceCleanBitmap) if (requiresTransparencyHack || forceCleanBitmap)
{ {
using var codec = SKCodec.Create(NormalizePath(path)); using SKCodec codec = SKCodec.Create(NormalizePath(path), out SKCodecResult res);
if (codec == null) if (res != SKCodecResult.Success)
{ {
origin = GetSKEncodedOrigin(orientation); origin = GetSKEncodedOrigin(orientation);
return null; return null;
@ -345,11 +345,6 @@ namespace Jellyfin.Drawing.Skia
private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin) private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
{ {
if (origin == SKEncodedOrigin.Default)
{
return bitmap;
}
var needsFlip = origin == SKEncodedOrigin.LeftBottom var needsFlip = origin == SKEncodedOrigin.LeftBottom
|| origin == SKEncodedOrigin.LeftTop || origin == SKEncodedOrigin.LeftTop
|| origin == SKEncodedOrigin.RightBottom || origin == SKEncodedOrigin.RightBottom
@ -447,7 +442,7 @@ namespace Jellyfin.Drawing.Skia
} }
/// <inheritdoc/> /// <inheritdoc/>
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat)
{ {
if (inputPath.Length == 0) if (inputPath.Length == 0)
{ {
@ -459,7 +454,7 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentException("String can't be empty.", nameof(outputPath)); throw new ArgumentException("String can't be empty.", nameof(outputPath));
} }
var skiaOutputFormat = GetImageFormat(selectedOutputFormat); var skiaOutputFormat = GetImageFormat(outputFormat);
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor); var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer); var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer);

View File

@ -5,6 +5,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -18,10 +20,6 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />

Some files were not shown because too many files have changed in this diff Show More