Merge branch 'master' into authenticationdb-efcore

# Conflicts:
#	Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
#	Emby.Server.Implementations/Session/SessionManager.cs
#	Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
pull/6201/head
Patrick Barron 3 years ago
commit ae878fa051

@ -11,6 +11,7 @@ jobs:
label:
name: Labeling
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- name: Apply label
uses: eps1lon/actions-label-merge-conflict@v2.0.1
@ -22,6 +23,7 @@ jobs:
project:
name: Project board
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- name: Remove from 'Current Release' project
uses: alex-page/github-project-automation-plus@v0.7.1

@ -14,9 +14,9 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Emby.Dlna.Profiles;
using Emby.Dlna.Server;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;

@ -6,9 +6,9 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Diacritics.Extensions;
using Emby.Dlna.Didl;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.Service

@ -1,7 +1,7 @@
using System;
using System.IO;
using Emby.Naming.Common;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
namespace Emby.Naming.Audio
{

@ -2,7 +2,7 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Emby.Naming.Common;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
namespace Emby.Naming.Video
{

@ -11,7 +11,7 @@ using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;

@ -11,10 +11,12 @@ using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using Diacritics.Extensions;
using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;

@ -7,7 +7,7 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;

@ -6,8 +6,8 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;

@ -21,6 +21,7 @@ using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.ScheduledTasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller;

@ -12,7 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;

@ -15,7 +15,7 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;

@ -6,7 +6,7 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Video;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;

@ -5,12 +5,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;

@ -11,9 +11,9 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;

@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV

@ -15,7 +15,7 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Cryptography;

@ -12,8 +12,9 @@ using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;

@ -10,6 +10,7 @@ using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;

@ -8,8 +8,8 @@ using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;

@ -10,8 +10,8 @@ using System.Text.Json;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Json.Converters;
using Jellyfin.Extensions.Json;
using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Configuration;

@ -1,15 +1,12 @@
#nullable disable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.QuickConnect;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.QuickConnect;
@ -22,9 +19,18 @@ namespace Emby.Server.Implementations.QuickConnect
/// </summary>
public class QuickConnectManager : IQuickConnect, IDisposable
{
private readonly RNGCryptoServiceProvider _rng = new ();
private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new ();
private readonly ConcurrentDictionary<string, (string Token, Guid UserId)> _quickConnectTokens = new ();
/// <summary>
/// The length of user facing codes.
/// </summary>
private const int CodeLength = 6;
/// <summary>
/// The time (in minutes) that the quick connect token is valid.
/// </summary>
private const int Timeout = 10;
private readonly RNGCryptoServiceProvider _rng = new();
private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new();
private readonly IServerConfigurationManager _config;
private readonly ILogger<QuickConnectManager> _logger;
@ -34,80 +40,42 @@ namespace Emby.Server.Implementations.QuickConnect
/// Initializes a new instance of the <see cref="QuickConnectManager"/> class.
/// Should only be called at server startup when a singleton is created.
/// </summary>
/// <param name="config">The server configuration manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param>
public QuickConnectManager(IServerConfigurationManager config, ILogger<QuickConnectManager> logger, ISessionManager sessionManager)
/// <param name="config">Configuration.</param>
/// <param name="logger">Logger.</param>
/// <param name="sessionManager">Session Manager.</param>
public QuickConnectManager(
IServerConfigurationManager config,
ILogger<QuickConnectManager> logger,
ISessionManager sessionManager)
{
_config = config;
_logger = logger;
_sessionManager = sessionManager;
ReloadConfiguration();
}
/// <inheritdoc/>
public int CodeLength { get; set; } = 6;
/// <inheritdoc />
public bool IsEnabled => _config.Configuration.QuickConnectAvailable;
/// <inheritdoc/>
public string TokenName { get; set; } = "QuickConnect";
/// <inheritdoc/>
public QuickConnectState State { get; private set; } = QuickConnectState.Unavailable;
/// <inheritdoc/>
public int Timeout { get; set; } = 5;
private DateTime DateActivated { get; set; }
/// <inheritdoc/>
public void AssertActive()
/// <summary>
/// Assert that quick connect is currently active and throws an exception if it is not.
/// </summary>
private void AssertActive()
{
if (State != QuickConnectState.Active)
if (!IsEnabled)
{
throw new ArgumentException("Quick connect is not active on this server");
throw new AuthenticationException("Quick connect is not active on this server");
}
}
/// <inheritdoc/>
public void Activate()
{
DateActivated = DateTime.UtcNow;
SetState(QuickConnectState.Active);
}
/// <inheritdoc/>
public void SetState(QuickConnectState newState)
{
_logger.LogDebug("Changed quick connect state from {State} to {newState}", State, newState);
ExpireRequests(true);
State = newState;
_config.Configuration.QuickConnectAvailable = newState == QuickConnectState.Available || newState == QuickConnectState.Active;
_config.SaveConfiguration();
_logger.LogDebug("Configuration saved");
}
/// <inheritdoc/>
public QuickConnectResult TryConnect()
{
AssertActive();
ExpireRequests();
if (State != QuickConnectState.Active)
{
_logger.LogDebug("Refusing quick connect initiation request, current state is {State}", State);
throw new AuthenticationException("Quick connect is not active on this server");
}
var secret = GenerateSecureRandom();
var code = GenerateCode();
var result = new QuickConnectResult()
{
Secret = GenerateSecureRandom(),
DateAdded = DateTime.UtcNow,
Code = code
};
var result = new QuickConnectResult(secret, code, DateTime.UtcNow);
_currentRequests[code] = result;
return result;
@ -116,12 +84,12 @@ namespace Emby.Server.Implementations.QuickConnect
/// <inheritdoc/>
public QuickConnectResult CheckRequestStatus(string secret)
{
ExpireRequests();
AssertActive();
ExpireRequests();
string code = _currentRequests.Where(x => x.Value.Secret == secret).Select(x => x.Value.Code).DefaultIfEmpty(string.Empty).First();
if (!_currentRequests.TryGetValue(code, out QuickConnectResult result))
if (!_currentRequests.TryGetValue(code, out QuickConnectResult? result))
{
throw new ResourceNotFoundException("Unable to find request with provided secret");
}
@ -129,22 +97,11 @@ namespace Emby.Server.Implementations.QuickConnect
return result;
}
/// <inheritdoc/>
public void AuthenticateRequest(AuthenticationRequest request, string token)
{
if (!_quickConnectTokens.TryGetValue(token, out var entry))
{
throw new SecurityException("Unknown quick connect token");
}
request.UserId = entry.UserId;
_quickConnectTokens.Remove(token, out _);
_sessionManager.AuthenticateQuickConnect(request, token);
}
/// <inheritdoc/>
public string GenerateCode()
/// <summary>
/// Generates a short code to display to the user to uniquely identify this request.
/// </summary>
/// <returns>A short, unique alphanumeric string.</returns>
private string GenerateCode()
{
Span<byte> raw = stackalloc byte[4];
@ -163,12 +120,12 @@ namespace Emby.Server.Implementations.QuickConnect
}
/// <inheritdoc/>
public bool AuthorizeRequest(Guid userId, string code)
public async Task<bool> AuthorizeRequest(Guid userId, string code)
{
ExpireRequests();
AssertActive();
ExpireRequests();
if (!_currentRequests.TryGetValue(code, out QuickConnectResult result))
if (!_currentRequests.TryGetValue(code, out QuickConnectResult? result))
{
throw new ResourceNotFoundException("Unable to find request");
}
@ -178,37 +135,19 @@ namespace Emby.Server.Implementations.QuickConnect
throw new InvalidOperationException("Request is already authorized");
}
result.Authentication = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
var token = Guid.NewGuid();
result.Authentication = token;
// Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated.
var added = result.DateAdded ?? DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(Timeout));
result.DateAdded = added.Subtract(TimeSpan.FromMinutes(Timeout - 1));
result.DateAdded = DateTime.Now.Add(TimeSpan.FromMinutes(1));
_quickConnectTokens[result.Authentication] = (TokenName, userId);
await _sessionManager.AuthenticateQuickConnect(userId).ConfigureAwait(false);
_logger.LogDebug("Authorizing device with code {Code} to login as user {userId}", code, userId);
return true;
}
/// <inheritdoc/>
public int DeleteAllDevices(Guid user)
{
var tokens = _quickConnectTokens
.Where(entry => entry.Value.Token.StartsWith(TokenName, StringComparison.Ordinal) && entry.Value.UserId == user)
.ToList();
var removed = 0;
foreach (var token in tokens)
{
_quickConnectTokens.Remove(token.Key, out _);
_logger.LogDebug("Deleted token {AccessToken}", token.Key);
removed++;
}
return removed;
}
/// <summary>
/// Dispose.
/// </summary>
@ -226,7 +165,7 @@ namespace Emby.Server.Implementations.QuickConnect
{
if (disposing)
{
_rng?.Dispose();
_rng.Dispose();
}
}
@ -238,22 +177,19 @@ namespace Emby.Server.Implementations.QuickConnect
return Convert.ToHexString(bytes);
}
/// <inheritdoc/>
public void ExpireRequests(bool expireAll = false)
/// <summary>
/// Expire quick connect requests that are over the time limit. If <paramref name="expireAll"/> is true, all requests are unconditionally expired.
/// </summary>
/// <param name="expireAll">If true, all requests will be expired.</param>
private void ExpireRequests(bool expireAll = false)
{
// Check if quick connect should be deactivated
if (State == QuickConnectState.Active && DateTime.UtcNow > DateActivated.AddMinutes(Timeout) && !expireAll)
{
_logger.LogDebug("Quick connect time expired, deactivating");
SetState(QuickConnectState.Available);
expireAll = true;
}
// All requests before this timestamp have expired
var minTime = DateTime.UtcNow.AddMinutes(-Timeout);
// Expire stale connection requests
foreach (var (_, currentRequest) in _currentRequests)
{
var added = currentRequest.DateAdded ?? DateTime.UnixEpoch;
if (expireAll || DateTime.UtcNow > added.AddMinutes(Timeout))
if (expireAll || currentRequest.DateAdded > minTime)
{
var code = currentRequest.Code;
_logger.LogDebug("Removing expired request {Code}", code);
@ -265,10 +201,5 @@ namespace Emby.Server.Implementations.QuickConnect
}
}
}
private void ReloadConfiguration()
{
State = _config.Configuration.QuickConnectAvailable ? QuickConnectState.Available : QuickConnectState.Unavailable;
}
}
}

@ -12,7 +12,7 @@ using System.Threading.Tasks;
using Jellyfin.Data.Events;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Progress;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;

@ -14,6 +14,7 @@ using Jellyfin.Data.Entities.Security;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using Jellyfin.Data.Queries;
using Jellyfin.Extensions;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
@ -1438,9 +1439,9 @@ namespace Emby.Server.Implementations.Session
return AuthenticateNewSessionInternal(request, true);
}
public Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token)
public Task<AuthenticationResult> AuthenticateQuickConnect(Guid userId)
{
return AuthenticateNewSessionInternal(request, false);
return AuthenticateNewSessionInternal(new AuthenticationRequest { UserId = userId }, false);
}
private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)

@ -4,6 +4,7 @@
using System;
using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
@ -30,7 +31,7 @@ namespace Emby.Server.Implementations.Sorting
throw new ArgumentNullException(nameof(y));
}
return AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty);
return AlphanumericComparator.CompareValues(x.Studios.FirstOrDefault(), y.Studios.FirstOrDefault());
}
/// <summary>

@ -11,7 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;

@ -1,5 +1,5 @@
using System.Net.Mime;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api

@ -6,7 +6,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.ConfigurationDtos;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;

@ -8,8 +8,8 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.PluginDtos;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Net;

@ -1,7 +1,9 @@
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.QuickConnect;
using MediaBrowser.Model.QuickConnect;
using Microsoft.AspNetCore.Authorization;
@ -30,13 +32,12 @@ namespace Jellyfin.Api.Controllers
/// Gets the current quick connect state.
/// </summary>
/// <response code="200">Quick connect state returned.</response>
/// <returns>The current <see cref="QuickConnectState"/>.</returns>
[HttpGet("Status")]
/// <returns>Whether Quick Connect is enabled on the server or not.</returns>
[HttpGet("Enabled")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QuickConnectState> GetStatus()
public ActionResult<bool> GetEnabled()
{
_quickConnect.ExpireRequests();
return _quickConnect.State;
return _quickConnect.IsEnabled;
}
/// <summary>
@ -49,7 +50,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QuickConnectResult> Initiate()
{
return _quickConnect.TryConnect();
try
{
return _quickConnect.TryConnect();
}
catch (AuthenticationException)
{
return Unauthorized("Quick connect is disabled");
}
}
/// <summary>
@ -72,42 +80,10 @@ namespace Jellyfin.Api.Controllers
{
return NotFound("Unknown secret");
}
}
/// <summary>
/// Temporarily activates quick connect for five minutes.
/// </summary>
/// <response code="204">Quick connect has been temporarily activated.</response>
/// <response code="403">Quick connect is unavailable on this server.</response>
/// <returns>An <see cref="NoContentResult"/> on success.</returns>
[HttpPost("Activate")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult Activate()
{
if (_quickConnect.State == QuickConnectState.Unavailable)
catch (AuthenticationException)
{
return StatusCode(StatusCodes.Status403Forbidden, "Quick connect is unavailable");
return Unauthorized("Quick connect is disabled");
}
_quickConnect.Activate();
return NoContent();
}
/// <summary>
/// Enables or disables quick connect.
/// </summary>
/// <param name="status">New <see cref="QuickConnectState"/>.</param>
/// <response code="204">Quick connect state set successfully.</response>
/// <returns>An <see cref="NoContentResult"/> on success.</returns>
[HttpPost("Available")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult Available([FromQuery] QuickConnectState status = QuickConnectState.Available)
{
_quickConnect.SetState(status);
return NoContent();
}
/// <summary>
@ -121,7 +97,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult<bool> Authorize([FromQuery, Required] string code)
public async Task<ActionResult<bool>> Authorize([FromQuery, Required] string code)
{
var userId = ClaimHelpers.GetUserId(Request.HttpContext.User);
if (!userId.HasValue)
@ -129,26 +105,14 @@ namespace Jellyfin.Api.Controllers
return StatusCode(StatusCodes.Status403Forbidden, "Unknown user id");
}
return _quickConnect.AuthorizeRequest(userId.Value, code);
}
/// <summary>
/// Deauthorize all quick connect devices for the current user.
/// </summary>
/// <response code="200">All quick connect devices were deleted.</response>
/// <returns>The number of devices that were deleted.</returns>
[HttpPost("Deauthorize")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<int> Deauthorize()
{
var userId = ClaimHelpers.GetUserId(Request.HttpContext.User);
if (!userId.HasValue)
try
{
return 0;
return await _quickConnect.AuthorizeRequest(userId.Value, code).ConfigureAwait(false);
}
catch (AuthenticationException)
{
return Unauthorized("Quick connect is disabled");
}
return _quickConnect.DeleteAllDevices(userId.Value);
}
}
}

@ -6,7 +6,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;

@ -9,7 +9,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Json.Converters;
using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MediaBrowser.Common.Json.Converters;
using Jellyfin.Extensions.Json.Converters;
namespace Jellyfin.Api.Models.PlaylistDtos
{

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MediaBrowser.Common.Json.Converters;
using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Session;
@ -85,4 +85,4 @@ namespace Jellyfin.Api.Models.SessionDtos
};
}
}
}
}

@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using BlurHashSharp.SkiaSharp;
using Diacritics.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Drawing;
using Microsoft.Extensions.Logging;
using SkiaSharp;
@ -142,9 +142,6 @@ namespace Jellyfin.Drawing.Skia
return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128);
}
private static bool HasDiacritics(string text)
=> !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
private bool RequiresSpecialCharacterHack(string path)
{
for (int i = 0; i < path.Length; i++)
@ -155,7 +152,7 @@ namespace Jellyfin.Drawing.Skia
}
}
return HasDiacritics(path);
return path.HasDiacritics();
}
private string NormalizePath(string path)

@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http;

@ -21,11 +21,11 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Controllers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions.Json;
using Jellyfin.Networking.Configuration;
using Jellyfin.Server.Configuration;
using Jellyfin.Server.Filters;
using Jellyfin.Server.Formatters;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authentication;

@ -1,4 +1,4 @@
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;

@ -1,5 +1,5 @@
using System.Net.Mime;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Primitives;

@ -4,9 +4,9 @@ using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions.Json;
using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;

@ -83,6 +83,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions", "src\Jellyfin.Extensions\Jellyfin.Extensions.csproj", "{750B8757-BE3D-4F8C-941A-FBAD94904ADA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -229,6 +235,14 @@ Global
{A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.Build.0 = Release|Any CPU
{750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Release|Any CPU.Build.0 = Release|Any CPU
{332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -247,6 +261,8 @@ Global
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{750B8757-BE3D-4F8C-941A-FBAD94904ADA} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{332A5C7A-F907-47CA-910E-BE6F7371B9E0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}

@ -3,7 +3,7 @@
using System;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;

@ -8,9 +8,9 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;

@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MediaBrowser.Controller.Extensions;
using Diacritics.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.Audio

@ -11,13 +11,14 @@ using System.Text;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;

@ -10,7 +10,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;

@ -2,7 +2,7 @@
using System;
using System.Linq;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities

@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities

@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MediaBrowser.Controller.Extensions;
using Diacritics.Extensions;
using MediaBrowser.Controller.Providers;
using Microsoft.Extensions.Logging;

@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MediaBrowser.Controller.Extensions;
using Diacritics.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities

@ -1,73 +0,0 @@
#pragma warning disable CS1591
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace MediaBrowser.Controller.Extensions
{
/// <summary>
/// Class BaseExtensions.
/// </summary>
public static class StringExtensions
{
public static string RemoveDiacritics(this string text)
{
var chars = Normalize(text, NormalizationForm.FormD)
.Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark);
return Normalize(string.Concat(chars), NormalizationForm.FormC);
}
/// <summary>
/// Counts the number of occurrences of [needle] in the string.
/// </summary>
/// <param name="value">The haystack to search in.</param>
/// <param name="needle">The character to search for.</param>
/// <returns>The number of occurrences of the [needle] character.</returns>
public static int Count(this ReadOnlySpan<char> value, char needle)
{
var count = 0;
var length = value.Length;
for (var i = 0; i < length; i++)
{
if (value[i] == needle)
{
count++;
}
}
return count;
}
private static string Normalize(string text, NormalizationForm form, bool stripStringOnFailure = true)
{
if (stripStringOnFailure)
{
try
{
return text.Normalize(form);
}
catch (ArgumentException)
{
// will throw if input contains invalid unicode chars
// https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
text = Regex.Replace(text, "([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])", string.Empty);
return Normalize(text, form, false);
}
}
try
{
return text.Normalize(form);
}
catch (ArgumentException)
{
// if it still fails, return the original text
return text;
}
}
}
}

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Diacritics.Extensions;
using MediaBrowser.Controller.Extensions;
namespace MediaBrowser.Controller.Library

@ -14,6 +14,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Diacritics" Version="2.1.20036.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />

@ -1,6 +1,5 @@
#nullable disable
using System;
using System.Threading.Tasks;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.QuickConnect;
@ -12,40 +11,9 @@ namespace MediaBrowser.Controller.QuickConnect
public interface IQuickConnect
{
/// <summary>
/// Gets or sets the length of user facing codes.
/// </summary>
int CodeLength { get; set; }
/// <summary>
/// Gets or sets the name of internal access tokens.
/// </summary>
string TokenName { get; set; }
/// <summary>
/// Gets the current state of quick connect.
/// </summary>
QuickConnectState State { get; }
/// <summary>
/// Gets or sets the time (in minutes) before quick connect will automatically deactivate.
/// </summary>
int Timeout { get; set; }
/// <summary>
/// Assert that quick connect is currently active and throws an exception if it is not.
/// </summary>
void AssertActive();
/// <summary>
/// Temporarily activates quick connect for a short amount of time.
/// Gets a value indicating whether quick connect is enabled or not.
/// </summary>
void Activate();
/// <summary>
/// Changes the state of quick connect.
/// </summary>
/// <param name="newState">New state to change to.</param>
void SetState(QuickConnectState newState);
bool IsEnabled { get; }
/// <summary>
/// Initiates a new quick connect request.
@ -60,38 +28,12 @@ namespace MediaBrowser.Controller.QuickConnect
/// <returns>Quick connect result.</returns>
QuickConnectResult CheckRequestStatus(string secret);
/// <summary>
/// Authenticates a QuickConnect request.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="token">The token.</param>
void AuthenticateRequest(AuthenticationRequest request, string token);
/// <summary>
/// Authorizes a quick connect request to connect as the calling user.
/// </summary>
/// <param name="userId">User id.</param>
/// <param name="code">Identifying code for the request.</param>
/// <returns>A boolean indicating if the authorization completed successfully.</returns>
bool AuthorizeRequest(Guid userId, string code);
/// <summary>
/// Expire quick connect requests that are over the time limit. If <paramref name="expireAll"/> is true, all requests are unconditionally expired.
/// </summary>
/// <param name="expireAll">If true, all requests will be expired.</param>
void ExpireRequests(bool expireAll = false);
/// <summary>
/// Deletes all quick connect access tokens for the provided user.
/// </summary>
/// <param name="user">Guid of the user to delete tokens for.</param>
/// <returns>A count of the deleted tokens.</returns>
int DeleteAllDevices(Guid user);
/// <summary>
/// Generates a short code to display to the user to uniquely identify this request.
/// </summary>
/// <returns>A short, unique alphanumeric string.</returns>
string GenerateCode();
Task<bool> AuthorizeRequest(Guid userId, string code);
}
}

@ -276,10 +276,9 @@ namespace MediaBrowser.Controller.Session
/// <summary>
/// Authenticates a new session with quick connect.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="token">Quick connect access token.</param>
/// <param name="userId">The user id.</param>
/// <returns>Task{SessionInfo}.</returns>
Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token);
Task<AuthenticationResult> AuthenticateQuickConnect(Guid userId);
/// <summary>
/// Reports the capabilities.

@ -1,16 +1,15 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Extensions;
namespace MediaBrowser.Controller.Sorting
{
public static class SortExtensions
{
private static readonly AlphanumComparator _comparer = new AlphanumComparator();
private static readonly AlphanumericComparator _comparer = new AlphanumericComparator();
public static IEnumerable<T> OrderByString<T>(this IEnumerable<T> list, Func<T, string> getName)
{

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;

@ -11,9 +11,9 @@ using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing;

@ -2,7 +2,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core;

@ -2,25 +2,28 @@
namespace MediaBrowser.Model.Dlna
{
/// <summary>
/// Delivery method to use during playback of a specific subtitle format.
/// </summary>
public enum SubtitleDeliveryMethod
{
/// <summary>
/// The encode.
/// Burn the subtitles in the video track.
/// </summary>
Encode = 0,
/// <summary>
/// The embed.
/// Embed the subtitles in the file or stream.
/// </summary>
Embed = 1,
/// <summary>
/// The external.
/// Serve the subtitles as an external file.
/// </summary>
External = 2,
/// <summary>
/// The HLS.
/// Serve the subtitles as a separate HLS stream.
/// </summary>
Hls = 3
}

@ -3,6 +3,7 @@
using System;
using System.Text.Json.Serialization;
using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Model.Entities

@ -50,7 +50,8 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" />
<ProjectReference Include="../Jellyfin.Data/Jellyfin.Data.csproj" />
<ProjectReference Include="../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" />
</ItemGroup>
</Project>

@ -3,38 +3,46 @@ using System;
namespace MediaBrowser.Model.QuickConnect
{
/// <summary>
/// Stores the result of an incoming quick connect request.
/// Stores the state of an quick connect request.
/// </summary>
public class QuickConnectResult
{
/// <summary>
/// Gets a value indicating whether this request is authorized.
/// Initializes a new instance of the <see cref="QuickConnectResult"/> class.
/// </summary>
public bool Authenticated => !string.IsNullOrEmpty(Authentication);
/// <param name="secret">The secret used to query the request state.</param>
/// <param name="code">The code used to allow the request.</param>
/// <param name="dateAdded">The time when the request was created.</param>
public QuickConnectResult(string secret, string code, DateTime dateAdded)
{
Secret = secret;
Code = code;
DateAdded = dateAdded;
}
/// <summary>
/// Gets or sets the secret value used to uniquely identify this request. Can be used to retrieve authentication information.
/// Gets a value indicating whether this request is authorized.
/// </summary>
public string? Secret { get; set; }
public bool Authenticated => Authentication != null;
/// <summary>
/// Gets or sets the user facing code used so the user can quickly differentiate this request from others.
/// Gets the secret value used to uniquely identify this request. Can be used to retrieve authentication information.
/// </summary>
public string? Code { get; set; }
public string Secret { get; }
/// <summary>
/// Gets or sets the private access token.
/// Gets the user facing code used so the user can quickly differentiate this request from others.
/// </summary>
public string? Authentication { get; set; }
public string Code { get; }
/// <summary>
/// Gets or sets an error message.
/// Gets or sets the private access token.
/// </summary>
public string? Error { get; set; }
public Guid? Authentication { get; set; }
/// <summary>
/// Gets or sets the DateTime that this request was created.
/// </summary>
public DateTime? DateAdded { get; set; }
public DateTime DateAdded { get; set; }
}
}

@ -1,23 +0,0 @@
namespace MediaBrowser.Model.QuickConnect
{
/// <summary>
/// Quick connect state.
/// </summary>
public enum QuickConnectState
{
/// <summary>
/// This feature has not been opted into and is unavailable until the server administrator chooses to opt-in.
/// </summary>
Unavailable = 0,
/// <summary>
/// The feature is enabled for use on the server but is not currently accepting connection requests.
/// </summary>
Available = 1,
/// <summary>
/// The feature is actively accepting connection requests.
/// </summary>
Active = 2
}
}

@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Diacritics.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;

@ -6,7 +6,7 @@ using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;

@ -11,7 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;

@ -6,7 +6,7 @@ using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;

@ -10,7 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;

@ -11,8 +11,8 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Diacritics.Extensions;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
@ -53,7 +53,7 @@ namespace MediaBrowser.Providers.Music
}
}
if (HasDiacritics(searchInfo.Name))
if (searchInfo.Name.HasDiacritics())
{
// Try again using the search with accent characters url
url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
@ -251,16 +251,6 @@ namespace MediaBrowser.Providers.Music
return result;
}
/// <summary>
/// Determines whether the specified text has diacritics.
/// </summary>
/// <param name="text">The text.</param>
/// <returns><c>true</c> if the specified text has diacritics; otherwise, <c>false</c>.</returns>
private bool HasDiacritics(string text)
{
return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
}
/// <summary>
/// Encodes an URL.
/// </summary>

@ -1,9 +1,11 @@
using System;
#nullable enable
using System;
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace MediaBrowser.Providers.Plugins.Omdb
{
/// <summary>
/// Converts a string <c>N/A</c> to <c>string.Empty</c>.

@ -1,8 +1,10 @@
using System;
#nullable enable
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace MediaBrowser.Providers.Plugins.Omdb
{
/// <summary>
/// Converts a string <c>N/A</c> to <c>string.Empty</c>.

@ -10,8 +10,8 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Json.Converters;
using Jellyfin.Extensions.Json;
using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;

@ -10,8 +10,8 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Json.Converters;
using Jellyfin.Extensions.Json;
using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;

@ -15,7 +15,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
[assembly: InternalsVisibleTo("Jellyfin.Common.Tests")]
[assembly: InternalsVisibleTo("Jellyfin.Providers.Tests")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from

@ -8,7 +8,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;

2
debian/rules vendored

@ -39,7 +39,7 @@ override_dh_auto_test:
override_dh_clistrip:
override_dh_auto_build:
dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
dotnet publish -maxcpucount:1 --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
"-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
override_dh_auto_clean:

@ -1,12 +1,19 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Sorting
namespace Jellyfin.Extensions
{
public class AlphanumComparator : IComparer<string?>
/// <summary>
/// Alphanumeric <see cref="IComparer{T}" />.
/// </summary>
public class AlphanumericComparator : IComparer<string?>
{
/// <summary>
/// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
/// </summary>
/// <param name="s1">The first object to compare.</param>
/// <param name="s2">The second object to compare.</param>
/// <returns>A signed integer that indicates the relative values of <c>x</c> and <c>y</c>.</returns>
public static int CompareValues(string? s1, string? s2)
{
if (s1 == null && s2 == null)

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace MediaBrowser.Common.Extensions
namespace Jellyfin.Extensions
{
/// <summary>
/// Provides <c>CopyTo</c> extensions methods for <see cref="IReadOnlyList{T}" />.

@ -1,7 +1,7 @@
using System;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Common.Extensions
namespace Jellyfin.Extensions
{
/// <summary>
/// Static extensions for the <see cref="IEnumerable{T}"/> interface.

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<Compile Include="../../SharedVersion.cs" />
</ItemGroup>
<!-- Code Analyzers-->
<ItemGroup>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
</Project>

@ -2,7 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Converts a number to a boolean.
@ -27,4 +27,4 @@ namespace MediaBrowser.Common.Json.Converters
writer.WriteBooleanValue(value);
}
}
}
}

@ -3,7 +3,7 @@ using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Convert comma delimited string to array of type.

@ -2,7 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Json comma delimited array converter factory.

@ -3,7 +3,7 @@ using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Legacy DateTime converter.
@ -31,4 +31,4 @@ namespace MediaBrowser.Common.Json.Converters
}
}
}
}
}

@ -3,7 +3,7 @@ using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Convert delimited string to array of type.

@ -3,7 +3,7 @@ using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Converts a GUID object or value to/from JSON.

@ -1,12 +1,8 @@
#nullable disable
// THIS IS A HACK
// TODO: @bond Move to separate project
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Model.Entities
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Converts an object to a lowercase string.
@ -15,7 +11,7 @@ namespace MediaBrowser.Model.Entities
public class JsonLowerCaseConverter<T> : JsonConverter<T>
{
/// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<T>(ref reader, options);
}
@ -23,7 +19,7 @@ namespace MediaBrowser.Model.Entities
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStringValue(value?.ToString().ToLowerInvariant());
writer.WriteStringValue(value?.ToString()?.ToLowerInvariant());
}
}
}

@ -3,7 +3,7 @@ using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Converts a GUID object or value to/from JSON.

@ -2,7 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Converts a nullable struct or value to/from JSON.

@ -2,7 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Json nullable struct converter factory.

@ -3,7 +3,7 @@ using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Convert Pipe delimited string to array of type.

@ -2,7 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Json Pipe delimited array converter factory.

@ -4,7 +4,7 @@ using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Converter to allow the serializer to read strings.

@ -2,7 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
namespace Jellyfin.Extensions.Json.Converters
{
/// <summary>
/// Converts a Version object or value to/from JSON.

@ -1,8 +1,8 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using MediaBrowser.Common.Json.Converters;
using Jellyfin.Extensions.Json.Converters;
namespace MediaBrowser.Common.Json
namespace Jellyfin.Extensions.Json
{
/// <summary>
/// Helper class for having compatible JSON throughout the codebase.

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace MediaBrowser.Common.Extensions
namespace Jellyfin.Extensions
{
/// <summary>
/// Provides <c>Shuffle</c> extensions methods for <see cref="IList{T}" />.

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

Loading…
Cancel
Save