Merge branch 'master' into RemoteAccessFix

pull/5495/head
BaronGreenback 3 years ago committed by GitHub
commit 5d16d1f66d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -36,7 +36,7 @@ namespace Emby.Dlna
private readonly ILogger<DlnaManager> _logger;
private readonly IServerApplicationHost _appHost;
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);
@ -333,7 +333,12 @@ namespace Emby.Dlna
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);
}

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

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

@ -352,8 +352,13 @@ namespace Emby.Drawing
}
/// <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()
.ToString("N", CultureInfo.InvariantCulture);
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
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="newName">Parsing result string.</param>
/// <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;
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)
{
if (string.IsNullOrEmpty(name))
{
newName = ReadOnlySpan<char>.Empty;
return false;
}
var match = expression.Match(name);
int index = match.Index;
if (match.Success && index != 0)

@ -221,20 +221,21 @@ namespace Emby.Naming.Video
string testFilename = Path.GetFileNameWithoutExtension(testFilePath);
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
if (folderName.Length <= testFilename.Length)
{
testFilename = cleanName.ToString();
testFilename = testFilename.Substring(folderName.Length).Trim();
}
if (folderName.Length <= testFilename.Length)
if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
{
testFilename = testFilename.Substring(folderName.Length).Trim();
testFilename = cleanName.Trim().ToString();
}
// The CleanStringParser should have removed common keywords etc.
return string.IsNullOrEmpty(testFilename)
|| testFilename[0] == '-'
|| testFilename[0] == '_'
|| string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
|| testFilename[0] == '-'
|| Regex.IsMatch(testFilename, @"^\[([^]]*)\]");
}
return false;

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
@ -146,7 +147,7 @@ namespace Emby.Naming.Video
/// <param name="name">Raw name.</param>
/// <param name="newName">Clean name.</param>
/// <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);
}

@ -10,8 +10,6 @@ using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna;
@ -51,7 +49,6 @@ using Jellyfin.Networking.Manager;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@ -470,7 +467,7 @@ namespace Emby.Server.Implementations
}
/// <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
var parts = GetExportTypes<T>()

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

@ -344,7 +344,20 @@ namespace Emby.Server.Implementations.Collections
}
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;
}
}
}
}

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

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

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

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

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

@ -59,11 +59,18 @@ namespace Emby.Server.Implementations.Library
/// <param name="newPath">The result of the sub path replacement</param>
/// <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>
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;
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;
}

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

@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
{
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();

@ -69,6 +69,110 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
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(
Folder parent,
List<FileSystemMetadata> files,
@ -216,110 +320,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
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>
/// Sets the initial item values.
/// </summary>

@ -1,5 +1,6 @@
using System;
using System.Linq;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
@ -11,12 +12,21 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// </summary>
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>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Episode.</returns>
protected override Episode Resolve(ItemResolveArgs args)
public override Episode Resolve(ItemResolveArgs args)
{
var parent = args.Parent;
@ -34,11 +44,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
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
if (season != null ||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
args.HasParent<Series>())
if ((season != null ||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
args.HasParent<Series>())
&& (parent is Series || !BaseItem.AllExtrasTypesFolderNames.Contains(parent.Name, StringComparer.OrdinalIgnoreCase)))
{
var episode = ResolveVideo<Episode>(args, false);
@ -74,14 +85,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
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)
{
}
}
}

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

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

@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ICryptoProvider _cryptoProvider;
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;
public SchedulesDirect(

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

@ -2,6 +2,7 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
@ -10,6 +11,7 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Controller.LiveTv;
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)
{
var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
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);
@ -166,9 +168,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_activeTuner = i;
var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue);
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
var lockkeyMsgLen = WriteSetMessage(buffer, i, "lockkey", lockKeyString, null);
await stream.WriteAsync(buffer.AsMemory(0, lockkeyMsgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -178,9 +180,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
foreach (var command in commands.GetCommands())
{
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
var channelMsgLen = WriteSetMessage(buffer, i, command.Item1, command.Item2, lockKeyValue);
await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
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 targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue);
var targetMsgLen = WriteSetMessage(buffer, i, "target", targetValue, lockKeyValue);
await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
await stream.WriteAsync(buffer.AsMemory(0, targetMsgLen), cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -232,9 +234,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
foreach (var command in commandList)
{
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.Item1, command.Item2, _lockkey);
await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -265,17 +267,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
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);
try
{
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
var releaseTargetLen = WriteSetMessage(buffer, _activeTuner, "target", "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;
await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false);
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
await stream.WriteAsync(buffer.AsMemory(0, releaseKeyMsgLen)).ConfigureAwait(false);
await stream.ReadAsync(buffer).ConfigureAwait(false);
}
finally
{
@ -283,107 +285,74 @@ 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));
int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length
var message = new byte[messageLength];
var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
int offset = WriteHeaderAndPayload(buffer, byteName);
return FinishPacket(buffer, offset);
}
int offset = InsertHeaderAndName(byteName, messageLength, message);
private static int WriteSetMessage(Span<byte> buffer, int tuner, string name, string value, uint? lockkey)
{
var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
int offset = WriteHeaderAndPayload(buffer, byteName);
bool flipEndian = BitConverter.IsLittleEndian;
buffer[offset++] = GetSetValue;
offset += WriteNullTerminatedString(buffer.Slice(offset), value);
// calculate crc and insert at the end of the message
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
if (flipEndian)
if (lockkey.HasValue)
{
Array.Reverse(crcBytes);
buffer[offset++] = GetSetLockkey;
buffer[offset++] = 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), lockkey.Value);
offset += 4;
}
Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
return message;
return FinishPacket(buffer, offset);
}
private static byte[] CreateSetMessage(int tuner, string name, string value, uint? lockkey)
internal static int WriteNullTerminatedString(Span<byte> buffer, ReadOnlySpan<char> payload)
{
var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
var byteValue = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\0", value));
int len = Encoding.UTF8.GetBytes(payload, buffer.Slice(1)) + 1;
int messageLength = byteName.Length + byteValue.Length + 12;
if (lockkey.HasValue)
{
messageLength += 6;
}
// TODO: variable length: this can be 2 bytes if len > 127
// Write length in front of value
buffer[0] = Convert.ToByte(len);
var message = new byte[messageLength];
// null-terminate
buffer[len++] = 0;
int offset = InsertHeaderAndName(byteName, messageLength, message);
return len;
}
bool flipEndian = BitConverter.IsLittleEndian;
private static int WriteHeaderAndPayload(Span<byte> buffer, ReadOnlySpan<char> payload)
{
// Packet type
BinaryPrimitives.WriteUInt16BigEndian(buffer, GetSetRequest);
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);
}
// We write the payload length at the end
int offset = 4;
Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
offset += 4;
}
// Tag
buffer[offset++] = GetSetName;
// 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);
// Payload length + data
int strLen = WriteNullTerminatedString(buffer.Slice(offset), payload);
offset += strLen;
return message;
return offset;
}
private static int InsertHeaderAndName(byte[] byteName, int messageLength, byte[] message)
private static int FinishPacket(Span<byte> buffer, int offset)
{
// check to see if we need to flip endiannes
bool flipEndian = BitConverter.IsLittleEndian;
int offset = 0;
// create header bytes
var getSetBytes = BitConverter.GetBytes(GetSetRequest);
var msgLenBytes = BitConverter.GetBytes((ushort)(messageLength - 8)); // Subtrace 4 bytes for header and 4 bytes for crc
if (flipEndian)
{
Array.Reverse(getSetBytes);
Array.Reverse(msgLenBytes);
}
// insert header bytes into message
Buffer.BlockCopy(getSetBytes, 0, message, offset, 2);
offset += 2;
Buffer.BlockCopy(msgLenBytes, 0, message, offset, 2);
offset += 2;
// insert tag name and length
message[offset++] = GetSetName;
message[offset++] = Convert.ToByte(byteName.Length);
// Payload length
BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), (ushort)(offset - 4));
// insert name string
Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
offset += byteName.Length;
// 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;
return offset + 4;
}
private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal)
@ -442,90 +411,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
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;
}
}
}
}

@ -22,5 +22,26 @@
"Artists": "Artistoj",
"Application": "Aplikaĵo",
"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"
}

@ -116,7 +116,7 @@
"TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.",
"TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.",
"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.",
"TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady."
}

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

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

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

@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.Plugins
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_pluginsPath = pluginsPath;
_appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion));
_jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions())
_jsonOptions = new JsonSerializerOptions(JsonDefaults.Options)
{
WriteIndented = true
};
@ -678,7 +678,7 @@ namespace Emby.Server.Implementations.Plugins
var entry = versions[x];
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)
{
lastName = entry.Name;

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

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

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

@ -61,7 +61,13 @@ namespace Jellyfin.Api.Controllers
{
// TODO: Deprecate with new iOS app
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);
}
@ -81,7 +87,13 @@ namespace Jellyfin.Api.Controllers
public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId)
{
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);
}
@ -130,7 +142,12 @@ namespace Jellyfin.Api.Controllers
var file = segmentId + Path.GetExtension(Request.Path);
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;

@ -74,7 +74,7 @@ namespace Jellyfin.Api.Controllers
: type;
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);
if (path == null)
@ -82,6 +82,11 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
if (!path.StartsWith(_applicationPaths.GeneralPath))
{
return BadRequest("Invalid image path.");
}
var contentType = MimeTypes.GetMimeType(path);
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>
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))
{
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 (!path.StartsWith(basePath))
{
return BadRequest("Invalid image path.");
}
var contentType = MimeTypes.GetMimeType(path);
return PhysicalFile(path, contentType);
}
}
var allFolder = Path.Combine(basePath, "all");
var allFolder = Path.GetFullPath(Path.Combine(basePath, "all"));
if (Directory.Exists(allFolder))
{
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 (!path.StartsWith(basePath))
{
return BadRequest("Invalid image path.");
}
var contentType = MimeTypes.GetMimeType(path);
return PhysicalFile(path, contentType);
}

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

@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
/// Creates an instant playlist based on a given song.
/// Creates an instant playlist based on a given album.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
/// Creates an instant playlist based on a given song.
/// Creates an instant playlist based on a given playlist.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
/// Creates an instant playlist based on a given song.
/// Creates an instant playlist based on a given genre.
/// </summary>
/// <param name="name">The genre name.</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>
[HttpGet("MusicGenres/{name}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre(
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreByName(
[FromRoute, Required] string name,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
/// Creates an instant playlist based on a given song.
/// 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>
@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
/// Creates an instant playlist based on a given song.
/// 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>
@ -243,7 +243,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
[FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
/// Creates an instant playlist based on a given song.
/// Creates an instant playlist based on a given item.
/// </summary>
/// <param name="id">The item id.</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);
}
/// <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)
{
var list = items;

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

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

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

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

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

@ -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; }
}
}

@ -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; }
}
}

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

@ -1,7 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using System.Xml.Serialization;
using Jellyfin.Data.Enums;
@ -27,14 +25,6 @@ namespace Jellyfin.Data.Entities
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>
/// Gets or sets the id of this instance.
/// </summary>
@ -42,8 +32,6 @@ namespace Jellyfin.Data.Entities
/// Identity, Indexed, Required.
/// </remarks>
[XmlIgnore]
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
@ -51,41 +39,24 @@ namespace Jellyfin.Data.Entities
/// Gets or sets the id of the associated user.
/// </summary>
[XmlIgnore]
[Required]
public Guid UserId { get; protected set; }
/// <summary>
/// Gets or sets the day of week.
/// </summary>
/// <value>The day of week.</value>
[Required]
public DynamicDayOfWeek DayOfWeek { get; set; }
/// <summary>
/// Gets or sets the start hour.
/// </summary>
/// <value>The start hour.</value>
[Required]
public double StartHour { get; set; }
/// <summary>
/// Gets or sets the end hour.
/// </summary>
/// <value>The end hour.</value>
[Required]
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);
}
}
}

@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities
/// <param name="name">The name.</param>
/// <param name="type">The type.</param>
/// <param name="userId">The user id.</param>
/// <param name="logLevel">The log level.</param>
public ActivityLog(string name, string type, Guid userId, LogLevel logLevel = LogLevel.Information)
public ActivityLog(string name, string type, Guid userId)
{
if (string.IsNullOrEmpty(name))
{
@ -35,15 +34,7 @@ namespace Jellyfin.Data.Entities
Type = type;
UserId = userId;
DateCreated = DateTime.UtcNow;
LogSeverity = logLevel;
}
/// <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()
{
LogSeverity = LogLevel.Information;
}
/// <summary>
@ -59,7 +50,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 512.
/// </remarks>
[Required]
[MaxLength(512)]
[StringLength(512)]
public string Name { get; set; }
@ -72,7 +62,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(512)]
[StringLength(512)]
public string Overview { get; set; }
public string? Overview { get; set; }
/// <summary>
/// Gets or sets the short overview.
@ -82,7 +72,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(512)]
[StringLength(512)]
public string ShortOverview { get; set; }
public string? ShortOverview { get; set; }
/// <summary>
/// Gets or sets the type.
@ -90,7 +80,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 256.
/// </remarks>
[Required]
[MaxLength(256)]
[StringLength(256)]
public string Type { get; set; }
@ -111,7 +100,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(256)]
[StringLength(256)]
public string ItemId { get; set; }
public string? ItemId { get; set; }
/// <summary>
/// Gets or sets the date created. This should be in UTC.

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

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

@ -32,16 +32,6 @@ namespace Jellyfin.Data.Entities
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>
/// Gets or sets the id of this group.
/// </summary>
@ -56,7 +46,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
[Required]
[MaxLength(255)]
[StringLength(255)]
public string Name { get; set; }

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

@ -19,16 +19,6 @@ namespace Jellyfin.Data.Entities
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>
/// Gets or sets the id.
/// </summary>
@ -49,7 +39,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
[Required]
[MaxLength(512)]
[StringLength(512)]
public string Path { get; set; }

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

@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="path">The path.</param>
/// <param name="kind">The kind of art.</param>
/// <param name="owner">The owner.</param>
public Artwork(string path, ArtKind kind, IHasArtwork owner)
public Artwork(string path, ArtKind kind)
{
if (string.IsNullOrEmpty(path))
{
@ -28,18 +27,6 @@ namespace Jellyfin.Data.Entities.Libraries
Path = path;
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>
@ -57,7 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 65535.
/// </remarks>
[Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Path { get; set; }

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

@ -1,8 +1,6 @@
#pragma warning disable CA2227
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
namespace Jellyfin.Data.Entities.Libraries
@ -17,29 +15,11 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="book">The book.</param>
public BookMetadata(string title, string language, Book book) : base(title, language)
public BookMetadata(string title, string language) : base(title, language)
{
if (book == null)
{
throw new ArgumentNullException(nameof(book));
}
book.BookMetadata.Add(this);
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>
/// Gets or sets the ISBN.
/// </summary>
@ -51,7 +31,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> Publishers { get; protected set; }
/// <inheritdoc />
[NotMapped]
public ICollection<Company> Companies => Publishers;
}
}

@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="startTime">The start time for this chapter.</param>
/// <param name="release">The release.</param>
public Chapter(string language, long startTime, Release release)
public Chapter(string language, long startTime)
{
if (string.IsNullOrEmpty(language))
{
@ -27,23 +26,6 @@ namespace Jellyfin.Data.Entities.Libraries
Language = language;
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>
@ -63,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }
public string? Name { get; set; }
/// <summary>
/// Gets or sets the language.
@ -72,7 +54,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// Required, Min length = 3, Max length = 3
/// ISO-639-3 3-character language codes.
/// </remarks>
[Required]
[MinLength(3)]
[MaxLength(3)]
[StringLength(3)]

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

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@ -13,39 +12,10 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="CollectionItem"/> class.
/// </summary>
/// <param name="collection">The collection.</param>
/// <param name="previous">The previous item.</param>
/// <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()
/// <param name="libraryItem">The library item.</param>
public CollectionItem(LibraryItem libraryItem)
{
LibraryItem = libraryItem;
}
/// <summary>
@ -75,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// TODO check if this properly updated Dependant and has the proper principal relationship.
/// </remarks>
public virtual CollectionItem Next { get; set; }
public virtual CollectionItem? Next { get; set; }
/// <summary>
/// Gets or sets the previous item in the collection.
@ -83,7 +53,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// TODO check if this properly updated Dependant and has the proper principal relationship.
/// </remarks>
public virtual CollectionItem Previous { get; set; }
public virtual CollectionItem? Previous { get; set; }
/// <inheritdoc />
public void OnSavingChanges()

@ -15,22 +15,10 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Company"/> class.
/// </summary>
/// <param name="owner">The owner of this company.</param>
public Company(IHasCompanies owner)
public Company()
{
owner?.Companies.Add(this);
CompanyMetadata = new HashSet<CompanyMetadata>();
}
/// <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()
{
ChildCompanies = new HashSet<Company>();
}
/// <summary>
@ -57,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> ChildCompanies { get; protected set; }
/// <inheritdoc />
[NotMapped]
public ICollection<Company> Companies => ChildCompanies;
/// <inheritdoc />

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

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

@ -1,5 +1,3 @@
using System;
namespace Jellyfin.Data.Entities.Libraries
{
/// <summary>
@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="item">The item.</param>
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()
public CustomItemMetadata(string title, string language) : base(title, language)
{
}
}

@ -1,6 +1,5 @@
#pragma warning disable CA2227
using System;
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
@ -14,30 +13,13 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Episode"/> class.
/// </summary>
/// <param name="season">The season.</param>
public Episode(Season season)
/// <param name="library">The library.</param>
public Episode(Library library) : base(library)
{
if (season == null)
{
throw new ArgumentNullException(nameof(season));
}
season.Episodes.Add(this);
Releases = new HashSet<Release>();
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>
/// Gets or sets the episode number.
/// </summary>

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries
@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="episode">The episode.</param>
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()
public EpisodeMetadata(string title, string language) : base(title, language)
{
}
@ -42,7 +24,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string Outline { get; set; }
public string? Outline { get; set; }
/// <summary>
/// Gets or sets the plot.
@ -52,7 +34,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
public string Plot { get; set; }
public string? Plot { get; set; }
/// <summary>
/// Gets or sets the tagline.
@ -62,6 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string Tagline { get; set; }
public string? Tagline { get; set; }
}
}

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@ -14,32 +13,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Genre"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="itemMetadata">The metadata.</param>
public Genre(string name, ItemMetadata itemMetadata)
public Genre(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(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>
@ -57,7 +33,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Indexed, Required, Max length = 255.
/// </remarks>
[Required]
[MaxLength(255)]
[StringLength(255)]
public string Name { get; set; }

@ -42,16 +42,6 @@ namespace Jellyfin.Data.Entities.Libraries
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>
/// Gets or sets the id.
/// </summary>
@ -67,7 +57,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 1024.
/// </remarks>
[Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Title { get; set; }
@ -80,7 +69,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string OriginalTitle { get; set; }
public string? OriginalTitle { get; set; }
/// <summary>
/// Gets or sets the sort title.
@ -90,7 +79,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string SortTitle { get; set; }
public string? SortTitle { get; set; }
/// <summary>
/// Gets or sets the language.
@ -99,7 +88,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// Required, Min length = 3, Max length = 3.
/// ISO-639-3 3-character language codes.
/// </remarks>
[Required]
[MinLength(3)]
[MaxLength(3)]
[StringLength(3)]

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@ -14,24 +13,11 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Library"/> class.
/// </summary>
/// <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;
}
/// <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()
{
Path = path;
}
/// <summary>
@ -49,7 +35,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 128.
/// </remarks>
[Required]
[MaxLength(128)]
[StringLength(128)]
public string Name { get; set; }
@ -60,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required.
/// </remarks>
[Required]
public string Path { get; set; }
/// <inheritdoc />

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

@ -19,8 +19,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="path">The path relative to the LibraryRoot.</param>
/// <param name="kind">The file kind.</param>
/// <param name="release">The release.</param>
public MediaFile(string path, MediaFileKind kind, Release release)
public MediaFile(string path, MediaFileKind kind)
{
if (string.IsNullOrEmpty(path))
{
@ -30,26 +29,9 @@ namespace Jellyfin.Data.Entities.Libraries
Path = path;
Kind = kind;
if (release == null)
{
throw new ArgumentNullException(nameof(release));
}
release.MediaFiles.Add(this);
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>
/// Gets or sets the id.
/// </summary>
@ -65,7 +47,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 65535.
/// </remarks>
[Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Path { get; set; }

@ -1,4 +1,5 @@
using System;
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@ -14,27 +15,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="MediaFileStream"/> class.
/// </summary>
/// <param name="streamNumber">The number of this stream.</param>
/// <param name="mediaFile">The media file.</param>
public MediaFileStream(int streamNumber, MediaFile mediaFile)
public MediaFileStream(int 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>

@ -24,16 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries
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>
/// Gets or sets the id.
/// </summary>
@ -49,7 +39,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 1024.
/// </remarks>
[Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }

@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
/// </summary>
/// <param name="providerId">The provider id.</param>
/// <param name="itemMetadata">The metadata entity.</param>
public MetadataProviderId(string providerId, ItemMetadata itemMetadata)
/// <param name="metadataProvider">The metadata provider.</param>
public MetadataProviderId(string providerId, MetadataProvider metadataProvider)
{
if (string.IsNullOrEmpty(providerId))
{
@ -23,23 +23,7 @@ namespace Jellyfin.Data.Entities.Libraries
}
ProviderId = providerId;
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()
{
MetadataProvider = metadataProvider;
}
/// <summary>
@ -57,7 +41,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
[Required]
[MaxLength(255)]
[StringLength(255)]
public string ProviderId { get; set; }

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

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
namespace Jellyfin.Data.Entities.Libraries
@ -17,22 +16,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the movie.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="movie">The movie.</param>
public MovieMetadata(string title, string language, Movie movie) : base(title, language)
public MovieMetadata(string title, string language) : base(title, language)
{
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>
@ -43,7 +29,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string Outline { get; set; }
public string? Outline { get; set; }
/// <summary>
/// Gets or sets the tagline.
@ -53,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string Tagline { get; set; }
public string? Tagline { get; set; }
/// <summary>
/// Gets or sets the plot.
@ -63,7 +49,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
public string Plot { get; set; }
public string? Plot { get; set; }
/// <summary>
/// Gets or sets the country code.
@ -73,7 +59,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(2)]
[StringLength(2)]
public string Country { get; set; }
public string? Country { get; set; }
/// <summary>
/// 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; }
/// <inheritdoc />
[NotMapped]
public ICollection<Company> Companies => Studios;
}
}

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

@ -15,22 +15,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the album.</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, MusicAlbum album) : base(title, language)
public MusicAlbumMetadata(string title, string language) : base(title, language)
{
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>
@ -41,7 +28,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string Barcode { get; set; }
public string? Barcode { get; set; }
/// <summary>
/// Gets or sets the label number.
@ -51,7 +38,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string LabelNumber { get; set; }
public string? LabelNumber { get; set; }
/// <summary>
/// Gets or sets the country code.
@ -61,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(2)]
[StringLength(2)]
public string Country { get; set; }
public string? Country { get; set; }
/// <summary>
/// Gets or sets a collection containing the labels.

@ -31,16 +31,6 @@ namespace Jellyfin.Data.Entities.Libraries
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>
/// Gets or sets the id.
/// </summary>
@ -56,7 +46,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 1024.
/// </remarks>
[Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }
@ -69,7 +58,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(256)]
[StringLength(256)]
public string SourceId { get; set; }
public string? SourceId { get; set; }
/// <summary>
/// Gets or sets the date added.

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

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

@ -1,5 +1,3 @@
using System;
namespace Jellyfin.Data.Entities.Libraries
{
/// <summary>
@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the photo.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="photo">The photo.</param>
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()
public PhotoMetadata(string title, string language) : base(title, language)
{
}
}

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@ -14,27 +13,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Rating"/> class.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="itemMetadata">The metadata.</param>
public Rating(double value, ItemMetadata itemMetadata)
public Rating(double 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>
@ -67,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Gets or sets the rating type.
/// If this is <c>null</c> it's the internal user rating.
/// </summary>
public virtual RatingSource RatingType { get; set; }
public virtual RatingSource? RatingType { get; set; }
/// <inheritdoc />
public void OnSavingChanges()

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@ -15,28 +14,10 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="minimumValue">The minimum value.</param>
/// <param name="maximumValue">The maximum value.</param>
/// <param name="rating">The rating.</param>
public RatingSource(double minimumValue, double maximumValue, Rating rating)
public RatingSource(double minimumValue, double maximumValue)
{
MinimumValue = minimumValue;
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>
@ -56,7 +37,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }
public string? Name { get; set; }
/// <summary>
/// Gets or sets the minimum value.
@ -81,7 +62,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Gets or sets the metadata source.
/// </summary>
public virtual MetadataProviderId Source { get; set; }
public virtual MetadataProviderId? Source { get; set; }
/// <inheritdoc />
public void OnSavingChanges()

@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Release"/> class.
/// </summary>
/// <param name="name">The name of this release.</param>
/// <param name="owner">The owner of this release.</param>
public Release(string name, IHasReleases owner)
public Release(string name)
{
if (string.IsNullOrEmpty(name))
{
@ -27,22 +26,10 @@ namespace Jellyfin.Data.Entities.Libraries
Name = name;
owner?.Releases.Add(this);
MediaFiles = new HashSet<MediaFile>();
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>
/// Gets or sets the id.
/// </summary>
@ -58,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 1024.
/// </remarks>
[Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }

@ -1,6 +1,5 @@
#pragma warning disable CA2227
using System;
using System.Collections.Generic;
namespace Jellyfin.Data.Entities.Libraries
@ -13,30 +12,13 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Season"/> class.
/// </summary>
/// <param name="series">The series.</param>
public Season(Series series)
/// <param name="library">The library.</param>
public Season(Library library) : base(library)
{
if (series == null)
{
throw new ArgumentNullException(nameof(series));
}
series.Seasons.Add(this);
Episodes = new HashSet<Episode>();
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>
/// Gets or sets the season number.
/// </summary>

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries
@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="season">The season.</param>
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()
public SeasonMetadata(string title, string language) : base(title, language)
{
}
@ -42,6 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string Outline { get; set; }
public string? Outline { get; set; }
}
}

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

@ -1,6 +1,5 @@
#pragma warning disable CA2227
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@ -18,29 +17,11 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="series">The series.</param>
public SeriesMetadata(string title, string language, Series series) : base(title, language)
public SeriesMetadata(string title, string language) : base(title, language)
{
if (series == null)
{
throw new ArgumentNullException(nameof(series));
}
series.SeriesMetadata.Add(this);
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>
/// Gets or sets the outline.
/// </summary>
@ -49,7 +30,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string Outline { get; set; }
public string? Outline { get; set; }
/// <summary>
/// Gets or sets the plot.
@ -59,7 +40,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
public string Plot { get; set; }
public string? Plot { get; set; }
/// <summary>
/// Gets or sets the tagline.
@ -69,7 +50,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
public string Tagline { get; set; }
public string? Tagline { get; set; }
/// <summary>
/// Gets or sets the country code.
@ -79,7 +60,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(2)]
[StringLength(2)]
public string Country { get; set; }
public string? Country { get; set; }
/// <summary>
/// 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; }
/// <inheritdoc />
[NotMapped]
public ICollection<Company> Companies => Networks;
}
}

@ -1,6 +1,5 @@
#pragma warning disable CA2227
using System;
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
@ -14,30 +13,13 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Track"/> class.
/// </summary>
/// <param name="album">The album.</param>
public Track(MusicAlbum album)
/// <param name="library">The library.</param>
public Track(Library library) : base(library)
{
if (album == null)
{
throw new ArgumentNullException(nameof(album));
}
album.Tracks.Add(this);
Releases = new HashSet<Release>();
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>
/// Gets or sets the track number.
/// </summary>

@ -1,5 +1,3 @@
using System;
namespace Jellyfin.Data.Entities.Libraries
{
/// <summary>
@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="track">The track.</param>
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()
public TrackMetadata(string title, string language) : base(title, language)
{
}
}

@ -1,3 +1,5 @@
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums;
@ -22,14 +24,6 @@ namespace Jellyfin.Data.Entities
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>
/// Gets or sets the id of this permission.
/// </summary>

@ -23,14 +23,6 @@ namespace Jellyfin.Data.Entities
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>
/// Gets or sets the id of this preference.
/// </summary>
@ -54,7 +46,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 65535.
/// </remarks>
[Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Value { get; set; }

@ -51,6 +51,7 @@ namespace Jellyfin.Data.Entities
PasswordResetProviderId = passwordResetProviderId;
AccessSchedules = new HashSet<AccessSchedule>();
DisplayPreferences = new HashSet<DisplayPreferences>();
ItemDisplayPreferences = new HashSet<ItemDisplayPreferences>();
// Groups = new HashSet<Group>();
Permissions = new HashSet<Permission>();
@ -72,17 +73,6 @@ namespace Jellyfin.Data.Entities
PlayDefaultAudioTrack = true;
SubtitleMode = SubtitlePlaybackMode.Default;
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>
@ -100,7 +90,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
[Required]
[MaxLength(255)]
[StringLength(255)]
public string Username { get; set; }
@ -113,7 +102,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
public string Password { get; set; }
public string? Password { get; set; }
/// <summary>
/// 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>
[MaxLength(65535)]
[StringLength(65535)]
public string EasyPassword { get; set; }
public string? EasyPassword { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the user must update their password.
@ -141,7 +130,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string AudioLanguagePreference { get; set; }
public string? AudioLanguagePreference { get; set; }
/// <summary>
/// Gets or sets the authentication provider id.
@ -149,7 +138,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
[Required]
[MaxLength(255)]
[StringLength(255)]
public string AuthenticationProviderId { get; set; }
@ -160,7 +148,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
[Required]
[MaxLength(255)]
[StringLength(255)]
public string PasswordResetProviderId { get; set; }
@ -217,7 +204,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string SubtitleLanguagePreference { get; set; }
public string? SubtitleLanguagePreference { get; set; }
/// <summary>
/// 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>.
/// </summary>
// [ForeignKey("UserId")]
public virtual ImageInfo ProfileImage { get; set; }
public virtual ImageInfo? ProfileImage { get; set; }
/// <summary>
/// Gets or sets the user's display preferences.
@ -320,8 +307,7 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
[Required]
public virtual DisplayPreferences DisplayPreferences { get; set; }
public virtual ICollection<DisplayPreferences> DisplayPreferences { get; set; }
/// <summary>
/// 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;
}
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;
}
/// <summary>
/// Initializes the default permissions for a user. Should only be called on user creation.
/// </summary>
// TODO: make these user configurable?
private void AddDefaultPermissions()
public void AddDefaultPermissions()
{
Permissions.Add(new Permission(PermissionKind.IsAdministrator, false));
Permissions.Add(new Permission(PermissionKind.IsDisabled, false));
@ -530,12 +509,25 @@ namespace Jellyfin.Data.Entities
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>())
{
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;
}
}
}

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

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

@ -11,6 +11,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@ -30,6 +32,11 @@
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
</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-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
@ -37,8 +44,4 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>

@ -274,8 +274,8 @@ namespace Jellyfin.Drawing.Skia
if (requiresTransparencyHack || forceCleanBitmap)
{
using var codec = SKCodec.Create(NormalizePath(path));
if (codec == null)
using SKCodec codec = SKCodec.Create(NormalizePath(path), out SKCodecResult res);
if (res != SKCodecResult.Success)
{
origin = GetSKEncodedOrigin(orientation);
return null;
@ -345,11 +345,6 @@ namespace Jellyfin.Drawing.Skia
private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
{
if (origin == SKEncodedOrigin.Default)
{
return bitmap;
}
var needsFlip = origin == SKEncodedOrigin.LeftBottom
|| origin == SKEncodedOrigin.LeftTop
|| origin == SKEncodedOrigin.RightBottom
@ -447,7 +442,7 @@ namespace Jellyfin.Drawing.Skia
}
/// <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)
{
@ -459,7 +454,7 @@ namespace Jellyfin.Drawing.Skia
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 hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer);

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

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

Loading…
Cancel
Save