diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 20f4dfe33b..543fd7fc6d 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -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'
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml
index 95e0d8c58d..7838b3b026 100644
--- a/.ci/azure-pipelines-test.yml
+++ b/.ci/azure-pipelines-test.yml
@@ -94,5 +94,5 @@ jobs:
displayName: 'Publish OpenAPI Artifact'
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
inputs:
- targetPath: "tests/Jellyfin.Api.Tests/bin/Release/net5.0/openapi.json"
+ targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json"
artifactName: 'OpenAPI Spec'
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 0874cae2e3..70bcd49737 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,4 +6,10 @@ updates:
interval: weekly
time: '12:00'
open-pull-requests-limit: 10
-
+
+- package-ecosystem: github-actions
+ directory: '/'
+ schedule:
+ interval: weekly
+ time: '12:00'
+ open-pull-requests-limit: 10
diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml
new file mode 100644
index 0000000000..db34693cc7
--- /dev/null
+++ b/.github/workflows/automation.yml
@@ -0,0 +1,66 @@
+name: Automation
+
+on:
+ pull_request:
+ issues:
+ issue_comment:
+
+jobs:
+ main:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Does PR has the stable backport label?
+ uses: Dreamcodeio/does-pr-has-label@v1.2
+ id: checkLabel
+ with:
+ label: stable backport
+
+ - name: Remove from 'Current Release' project
+ uses: alex-page/github-project-automation-plus@v0.5.1
+ if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel
+ continue-on-error: true
+ with:
+ project: Current Release
+ action: delete
+ repo-token: ${{ secrets.GH_TOKEN }}
+
+ - name: Add to 'Release Next' project
+ uses: alex-page/github-project-automation-plus@v0.5.1
+ if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
+ continue-on-error: true
+ with:
+ project: Release Next
+ column: In progress
+ repo-token: ${{ secrets.GH_TOKEN }}
+
+ - name: Add to 'Current Release' project
+ uses: alex-page/github-project-automation-plus@v0.5.1
+ if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel
+ continue-on-error: true
+ with:
+ project: Current Release
+ column: In progress
+ repo-token: ${{ secrets.GH_TOKEN }}
+
+ - name: Check number of comments from the team member
+ if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
+ id: member_comments
+ run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
+
+ - name: Move issue to needs triage
+ uses: alex-page/github-project-automation-plus@v0.5.1
+ if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
+ continue-on-error: true
+ with:
+ project: Issue Triage for Main Repo
+ column: Needs triage
+ repo-token: ${{ secrets.GH_TOKEN }}
+
+ - name: Add issue to triage project
+ uses: alex-page/github-project-automation-plus@v0.5.1
+ if: github.event.issue.pull_request == '' && github.event.action == 'opened'
+ continue-on-error: true
+ with:
+ project: Issue Triage for Main Repo
+ column: Pending response
+ repo-token: ${{ secrets.GH_TOKEN }}
diff --git a/.github/workflows/merge-conflicts.yml b/.github/workflows/merge-conflicts.yml
new file mode 100644
index 0000000000..ce808617a1
--- /dev/null
+++ b/.github/workflows/merge-conflicts.yml
@@ -0,0 +1,17 @@
+name: 'Merge Conflicts'
+
+on:
+ push:
+ branches:
+ - master
+ pull_request_target:
+ types:
+ - synchronize
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: eps1lon/actions-label-merge-conflict@v2.0.1
+ with:
+ dirtyLabel: 'merge conflict'
+ repoToken: ${{ secrets.GH_TOKEN }}
diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml
new file mode 100644
index 0000000000..3172ec0d9f
--- /dev/null
+++ b/.github/workflows/rebase.yml
@@ -0,0 +1,27 @@
+name: Automatic Rebase
+on:
+ issue_comment:
+
+jobs:
+ rebase:
+ name: Rebase
+ if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '@jellyfin-bot rebase') && github.event.comment.author_association == 'MEMBER'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Notify as seen
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ with:
+ token: ${{ secrets.GH_TOKEN }}
+ comment-id: ${{ github.event.comment.id }}
+ reactions: '+1'
+
+ - name: Checkout the latest code
+ uses: actions/checkout@v2
+ with:
+ token: ${{ secrets.GH_TOKEN }}
+ fetch-depth: 0
+
+ - name: Automatic Rebase
+ uses: cirrus-actions/rebase@1.4
+ env:
+ GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 1200275d52..7a763a46c1 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -17,6 +17,7 @@
- [bugfixin](https://github.com/bugfixin)
- [chaosinnovator](https://github.com/chaosinnovator)
- [ckcr4lyf](https://github.com/ckcr4lyf)
+ - [cocool97](https://github.com/cocool97)
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
- [crankdoofus](https://github.com/crankdoofus)
- [crobibero](https://github.com/crobibero)
@@ -49,6 +50,7 @@
- [h1nk](https://github.com/h1nk)
- [hawken93](https://github.com/hawken93)
- [HelloWorld017](https://github.com/HelloWorld017)
+ - [ikomhoog](https://github.com/ikomhoog)
- [jftuga](https://github.com/jftuga)
- [joern-h](https://github.com/joern-h)
- [joshuaboniface](https://github.com/joshuaboniface)
@@ -104,6 +106,7 @@
- [shemanaev](https://github.com/shemanaev)
- [skaro13](https://github.com/skaro13)
- [sl1288](https://github.com/sl1288)
+ - [Smith00101010](https://github.com/Smith00101010)
- [sorinyo2004](https://github.com/sorinyo2004)
- [sparky8251](https://github.com/sparky8251)
- [spookbits](https://github.com/spookbits)
diff --git a/Dockerfile b/Dockerfile
index 41dd3d081e..ebe5eb00c6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,10 +5,10 @@ ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
- && yarn install \
+ && npm ci --no-audit \
&& mv dist /dist
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster-slim as builder
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
diff --git a/Dockerfile.arm b/Dockerfile.arm
index e0eaca0edd..d63dbee758 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -10,7 +10,7 @@ ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
- && yarn install \
+ && npm ci --no-audit \
&& mv dist /dist
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index db7de935cf..e95999f2a6 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -10,7 +10,7 @@ ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
- && yarn install \
+ && npm ci --no-audit \
&& mv dist /dist
diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs
index 2f8d197a7a..1a1790ee6a 100644
--- a/Emby.Dlna/ConnectionManager/ControlHandler.cs
+++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs
@@ -31,7 +31,7 @@ namespace Emby.Dlna.ConnectionManager
}
///
- protected override void WriteResult(string methodName, IDictionary methodParams, XmlWriter xmlWriter)
+ protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter)
{
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
{
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 27f1fdabad..90ba601b4a 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -7,7 +6,6 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Xml;
-using Emby.Dlna.Configuration;
using Emby.Dlna.Didl;
using Emby.Dlna.Service;
using Jellyfin.Data.Entities;
@@ -121,7 +119,7 @@ namespace Emby.Dlna.ContentDirectory
}
///
- protected override void WriteResult(string methodName, IDictionary methodParams, XmlWriter xmlWriter)
+ protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter)
{
if (xmlWriter == null)
{
@@ -201,8 +199,8 @@ namespace Emby.Dlna.ContentDirectory
///
/// Adds a "XSetBookmark" element to the xml document.
///
- /// The .
- private void HandleXSetBookmark(IDictionary sparams)
+ /// The method parameters.
+ private void HandleXSetBookmark(IReadOnlyDictionary sparams)
{
var id = sparams["ObjectID"];
@@ -305,35 +303,18 @@ namespace Emby.Dlna.ContentDirectory
return builder.ToString();
}
- ///
- /// Returns the value in the key of the dictionary, or defaultValue if it doesn't exist.
- ///
- /// The .
- /// The key.
- /// The defaultValue.
- /// The .
- public static string GetValueOrDefault(IDictionary sparams, string key, string defaultValue)
- {
- if (sparams != null && sparams.TryGetValue(key, out string val))
- {
- return val;
- }
-
- return defaultValue;
- }
-
///
/// Builds the "Browse" xml response.
///
/// The .
- /// The .
+ /// The method parameters.
/// The device Id to use.
- private void HandleBrowse(XmlWriter xmlWriter, IDictionary sparams, string deviceId)
+ private void HandleBrowse(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId)
{
var id = sparams["ObjectID"];
var flag = sparams["BrowseFlag"];
- var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
- var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));
+ var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
+ var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty));
var provided = 0;
@@ -435,9 +416,9 @@ namespace Emby.Dlna.ContentDirectory
/// Builds the response to the "X_BrowseByLetter request.
///
/// The .
- /// The .
+ /// The method parameters.
/// The device id.
- private void HandleXBrowseByLetter(XmlWriter xmlWriter, IDictionary sparams, string deviceId)
+ private void HandleXBrowseByLetter(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId)
{
// TODO: Implement this method
HandleSearch(xmlWriter, sparams, deviceId);
@@ -447,13 +428,13 @@ namespace Emby.Dlna.ContentDirectory
/// Builds a response to the "Search" request.
///
/// The xmlWriter.
- /// The sparams.
+ /// The method parameters.
/// The deviceId.
- private void HandleSearch(XmlWriter xmlWriter, IDictionary sparams, string deviceId)
+ private void HandleSearch(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId)
{
- var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", string.Empty));
- var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));
- var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
+ var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", string.Empty));
+ var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty));
+ var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
// sort example: dc:title, dc:date
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index 7f8dba35fb..8aed4a50fa 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -36,7 +36,7 @@ namespace Emby.Dlna
private readonly ILogger _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> _profiles = new Dictionary>(StringComparer.Ordinal);
@@ -126,14 +126,14 @@ namespace Emby.Dlna
var builder = new StringBuilder();
builder.AppendLine("No matching device profile found. The default will need to be used.");
- builder.Append("FriendlyName:").AppendLine(profile.FriendlyName);
- builder.Append("Manufacturer:").AppendLine(profile.Manufacturer);
- builder.Append("ManufacturerUrl:").AppendLine(profile.ManufacturerUrl);
- builder.Append("ModelDescription:").AppendLine(profile.ModelDescription);
- builder.Append("ModelName:").AppendLine(profile.ModelName);
- builder.Append("ModelNumber:").AppendLine(profile.ModelNumber);
- builder.Append("ModelUrl:").AppendLine(profile.ModelUrl);
- builder.Append("SerialNumber:").AppendLine(profile.SerialNumber);
+ builder.Append("FriendlyName: ").AppendLine(profile.FriendlyName);
+ builder.Append("Manufacturer: ").AppendLine(profile.Manufacturer);
+ builder.Append("ManufacturerUrl: ").AppendLine(profile.ManufacturerUrl);
+ builder.Append("ModelDescription: ").AppendLine(profile.ModelDescription);
+ builder.Append("ModelName: ").AppendLine(profile.ModelName);
+ builder.Append("ModelNumber: ").AppendLine(profile.ModelNumber);
+ builder.Append("ModelUrl: ").AppendLine(profile.ModelUrl);
+ builder.Append("SerialNumber: ").AppendLine(profile.SerialNumber);
_logger.LogInformation(builder.ToString());
}
@@ -290,7 +290,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);
}
@@ -352,7 +357,8 @@ namespace Emby.Dlna
{
Directory.CreateDirectory(systemProfilesPath);
- using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
{
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index 8b057a0950..480621dd70 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -25,7 +25,6 @@
-
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index ec87160293..bdfe430cf3 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -5,7 +5,6 @@ using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
-using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna.PlayTo;
using Emby.Dlna.Ssdp;
@@ -128,7 +127,8 @@ namespace Emby.Dlna.Main
_netConfig = config.GetConfiguration("network");
_disabled = appHost.ListenWithHttps && _netConfig.RequireHttps;
- if (_disabled)
+
+ if (_disabled && _config.GetDlnaConfiguration().EnableServer)
{
_logger.LogError("The DLNA specification does not support HTTPS.");
}
@@ -316,7 +316,7 @@ namespace Emby.Dlna.Main
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri);
- if (_appHost.PublishedServerUrl == null)
+ if (!string.IsNullOrEmpty(_appHost.PublishedServerUrl))
{
// DLNA will only work over http, so we must reset to http:// : {port}.
uri.Scheme = "http";
diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs
index 464f71a6f1..d8fb127420 100644
--- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs
+++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs
@@ -24,7 +24,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
}
///
- protected override void WriteResult(string methodName, IDictionary methodParams, XmlWriter xmlWriter)
+ protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter)
{
if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
{
diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs
index 37840cd096..f3789a791c 100644
--- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs
+++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
-using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.MediaReceiverRegistrar
{
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 7bf7047fbb..abd99bbc3c 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -219,7 +219,7 @@ namespace Emby.Dlna.PlayTo
{
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
if (command == null)
{
return false;
@@ -259,7 +259,7 @@ namespace Emby.Dlna.PlayTo
{
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
if (command == null)
{
return;
@@ -290,7 +290,7 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
if (command == null)
{
return;
@@ -323,7 +323,7 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
if (command == null)
{
return;
@@ -403,6 +403,10 @@ namespace Emby.Dlna.PlayTo
public async Task SetPlay(CancellationToken cancellationToken)
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
+ if (avCommands == null)
+ {
+ return;
+ }
await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
@@ -413,7 +417,7 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
if (command == null)
{
return;
@@ -437,7 +441,7 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
if (command == null)
{
return;
@@ -565,7 +569,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
if (command == null)
{
return;
@@ -615,7 +619,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
if (command == null)
{
return;
@@ -702,6 +706,10 @@ namespace Emby.Dlna.PlayTo
}
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
+ if (rendererCommands == null)
+ {
+ return null;
+ }
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
@@ -770,6 +778,11 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
+ if (rendererCommands == null)
+ {
+ return (false, null);
+ }
+
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
@@ -951,6 +964,10 @@ namespace Emby.Dlna.PlayTo
var httpClient = new SsdpHttpClient(_httpClientFactory);
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
+ if (document == null)
+ {
+ return null;
+ }
AvCommands = TransportCommands.Create(document);
return AvCommands;
@@ -979,6 +996,10 @@ namespace Emby.Dlna.PlayTo
var httpClient = new SsdpHttpClient(_httpClientFactory);
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
+ if (document == null)
+ {
+ return null;
+ }
RendererCommands = TransportCommands.Create(document);
return RendererCommands;
@@ -1010,6 +1031,10 @@ namespace Emby.Dlna.PlayTo
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
+ if (document == null)
+ {
+ return null;
+ }
var friendlyNames = new List();
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index e4923b9eb0..25ba18add8 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -132,7 +132,7 @@ namespace Emby.Dlna.PlayTo
private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e)
{
- if (_disposed)
+ if (_disposed || string.IsNullOrEmpty(e.OldMediaInfo.Url))
{
return;
}
@@ -943,11 +943,7 @@ namespace Emby.Dlna.PlayTo
request.DeviceId = values.GetValueOrDefault("DeviceId");
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
-
- // Be careful, IsDirectStream==true by default (Static != false or not in query).
- // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true.
- request.IsDirectStream = !string.Equals("false", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
-
+ request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
request.StartPositionTicks = GetLongValue(values, "StartPositionTicks");
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index a6793a7081..8272e505a0 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -178,6 +178,11 @@ namespace Emby.Dlna.PlayTo
if (controller == null)
{
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
+ if (device == null)
+ {
+ _logger.LogError("Ignoring device as xml response is invalid.");
+ return;
+ }
string deviceName = device.Properties.Name;
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index 557bc69a73..d9f1ce4907 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -2,7 +2,6 @@
using System;
using System.Globalization;
-using System.IO;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
@@ -45,10 +44,10 @@ namespace Emby.Dlna.PlayTo
cancellationToken)
.ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var reader = new StreamReader(stream, Encoding.UTF8);
- return XDocument.Parse(
- await reader.ReadToEndAsync().ConfigureAwait(false),
- LoadOptions.PreserveWhitespace);
+ return await XDocument.LoadAsync(
+ stream,
+ LoadOptions.PreserveWhitespace,
+ cancellationToken).ConfigureAwait(false);
}
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
@@ -94,10 +93,17 @@ namespace Emby.Dlna.PlayTo
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var reader = new StreamReader(stream, Encoding.UTF8);
- return XDocument.Parse(
- await reader.ReadToEndAsync().ConfigureAwait(false),
- LoadOptions.PreserveWhitespace);
+ try
+ {
+ return await XDocument.LoadAsync(
+ stream,
+ LoadOptions.PreserveWhitespace,
+ cancellationToken).ConfigureAwait(false);
+ }
+ catch
+ {
+ return null;
+ }
}
private async Task PostSoapDataAsync(
diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs
index 0865968ad1..cbcf66e45c 100644
--- a/Emby.Dlna/PlayTo/TransportCommands.cs
+++ b/Emby.Dlna/PlayTo/TransportCommands.cs
@@ -13,12 +13,10 @@ namespace Emby.Dlna.PlayTo
public class TransportCommands
{
private const string CommandBase = "\r\n" + "" + "" + "" + "{2}" + "" + "";
- private List _stateVariables = new List();
- private List _serviceActions = new List();
- public List StateVariables => _stateVariables;
+ public List StateVariables { get; } = new List();
- public List ServiceActions => _serviceActions;
+ public List ServiceActions { get; } = new List();
public static TransportCommands Create(XDocument document)
{
diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs
index d4af72b626..8eaf12ba9a 100644
--- a/Emby.Dlna/Profiles/DefaultProfile.cs
+++ b/Emby.Dlna/Profiles/DefaultProfile.cs
@@ -1,5 +1,7 @@
#pragma warning disable CS1591
+using System;
+using System.Globalization;
using System.Linq;
using MediaBrowser.Model.Dlna;
@@ -10,6 +12,7 @@ namespace Emby.Dlna.Profiles
{
public DefaultProfile()
{
+ Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
Name = "Generic Device";
ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";
diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs
index 8d2486fee6..fda8346f9e 100644
--- a/Emby.Dlna/Service/BaseControlHandler.cs
+++ b/Emby.Dlna/Service/BaseControlHandler.cs
@@ -210,7 +210,7 @@ namespace Emby.Dlna.Service
}
}
- protected abstract void WriteResult(string methodName, IDictionary methodParams, XmlWriter xmlWriter);
+ protected abstract void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter);
private void LogRequest(ControlRequest request)
{
diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
index 8c7d961f3e..d13871add8 100644
--- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
@@ -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);
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj
index 7d479a5c65..5c5afe1c6e 100644
--- a/Emby.Drawing/Emby.Drawing.csproj
+++ b/Emby.Drawing/Emby.Drawing.csproj
@@ -25,7 +25,6 @@
-
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index 8a2301d2d6..7d952aa23b 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
@@ -171,21 +172,31 @@ namespace Emby.Drawing
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
- ImageDimensions newSize = ImageHelper.GetNewImageSize(options, null);
int quality = options.Quality;
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
- string cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
+ string cacheFilePath = GetCacheFilePath(
+ originalImagePath,
+ options.Width,
+ options.Height,
+ options.MaxWidth,
+ options.MaxHeight,
+ options.FillWidth,
+ options.FillHeight,
+ quality,
+ dateModified,
+ outputFormat,
+ options.AddPlayedIndicator,
+ options.PercentPlayed,
+ options.UnplayedCount,
+ options.Blur,
+ options.BackgroundColor,
+ options.ForegroundLayer);
try
{
if (!File.Exists(cacheFilePath))
{
- if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath))
- {
- options.CropWhiteSpace = false;
- }
-
string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat);
if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase))
@@ -246,48 +257,111 @@ namespace Emby.Drawing
///
/// Gets the cache file path based on a set of parameters.
///
- private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
+ private string GetCacheFilePath(
+ string originalPath,
+ int? width,
+ int? height,
+ int? maxWidth,
+ int? maxHeight,
+ int? fillWidth,
+ int? fillHeight,
+ int quality,
+ DateTime dateModified,
+ ImageFormat format,
+ bool addPlayedIndicator,
+ double percentPlayed,
+ int? unwatchedCount,
+ int? blur,
+ string backgroundColor,
+ string foregroundLayer)
{
- var filename = originalPath
- + "width=" + outputSize.Width
- + "height=" + outputSize.Height
- + "quality=" + quality
- + "datemodified=" + dateModified.Ticks
- + "f=" + format;
+ var filename = new StringBuilder(256);
+ filename.Append(originalPath);
+
+ filename.Append(",quality=");
+ filename.Append(quality);
+
+ filename.Append(",datemodified=");
+ filename.Append(dateModified.Ticks);
+
+ filename.Append(",f=");
+ filename.Append(format);
+
+ if (width.HasValue)
+ {
+ filename.Append(",width=");
+ filename.Append(width.Value);
+ }
+
+ if (height.HasValue)
+ {
+ filename.Append(",height=");
+ filename.Append(height.Value);
+ }
+
+ if (maxWidth.HasValue)
+ {
+ filename.Append(",maxwidth=");
+ filename.Append(maxWidth.Value);
+ }
+
+ if (maxHeight.HasValue)
+ {
+ filename.Append(",maxheight=");
+ filename.Append(maxHeight.Value);
+ }
+
+ if (fillWidth.HasValue)
+ {
+ filename.Append(",fillwidth=");
+ filename.Append(fillWidth.Value);
+ }
+
+ if (fillHeight.HasValue)
+ {
+ filename.Append(",fillheight=");
+ filename.Append(fillHeight.Value);
+ }
if (addPlayedIndicator)
{
- filename += "pl=true";
+ filename.Append(",pl=true");
}
if (percentPlayed > 0)
{
- filename += "p=" + percentPlayed;
+ filename.Append(",p=");
+ filename.Append(percentPlayed);
}
if (unwatchedCount.HasValue)
{
- filename += "p=" + unwatchedCount.Value;
+ filename.Append(",p=");
+ filename.Append(unwatchedCount.Value);
}
if (blur.HasValue)
{
- filename += "blur=" + blur.Value;
+ filename.Append(",blur=");
+ filename.Append(blur.Value);
}
if (!string.IsNullOrEmpty(backgroundColor))
{
- filename += "b=" + backgroundColor;
+ filename.Append(",b=");
+ filename.Append(backgroundColor);
}
if (!string.IsNullOrEmpty(foregroundLayer))
{
- filename += "fl=" + foregroundLayer;
+ filename.Append(",fl=");
+ filename.Append(foregroundLayer);
}
- filename += "v=" + Version;
+ filename.Append(",v=");
+ filename.Append(Version);
- return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant());
+ return GetCachePath(ResizedImageCachePath, filename.ToString(), "." + format.ToString().ToLowerInvariant());
}
///
@@ -352,8 +426,13 @@ namespace Emby.Drawing
}
///
- 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);
}
diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs
index 2a1cfd3da5..1c05aa9161 100644
--- a/Emby.Drawing/NullImageEncoder.cs
+++ b/Emby.Drawing/NullImageEncoder.cs
@@ -32,7 +32,7 @@ namespace Emby.Drawing
=> throw new NotImplementedException();
///
- 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)
{
throw new NotImplementedException();
}
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index b43203e9dd..63116f3680 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -44,7 +44,6 @@
-
diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs
index 09a0cd1893..4eef3ebc5e 100644
--- a/Emby.Naming/Video/CleanStringParser.cs
+++ b/Emby.Naming/Video/CleanStringParser.cs
@@ -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
/// List of regex to parse name and year from.
/// Parsing result string.
/// True if parsing was successful.
- public static bool TryClean(string name, IReadOnlyList expressions, out ReadOnlySpan newName)
+ public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList expressions, out ReadOnlySpan newName)
{
+ if (string.IsNullOrEmpty(name))
+ {
+ newName = ReadOnlySpan.Empty;
+ return false;
+ }
+
var len = expressions.Count;
for (int i = 0; i < len; i++)
{
@@ -41,7 +48,7 @@ namespace Emby.Naming.Video
return true;
}
- newName = string.Empty;
+ newName = ReadOnlySpan.Empty;
return false;
}
}
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 09a030d2de..7b6a1705ba 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -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;
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index 619d1520e4..79a6da8f7b 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -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
/// Raw name.
/// Clean name.
/// True if cleaning of name was successful.
- public bool TryCleanString(string name, out ReadOnlySpan newName)
+ public bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan newName)
{
return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
}
diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj
index 16ee918c46..5a2aea6423 100644
--- a/Emby.Notifications/Emby.Notifications.csproj
+++ b/Emby.Notifications/Emby.Notifications.csproj
@@ -11,6 +11,8 @@
true
true
enable
+ AllEnabledByDefault
+ ../jellyfin.ruleset
@@ -25,14 +27,9 @@
-
-
- ../jellyfin.ruleset
-
-
diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index 62e33e6c44..2b66181599 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -24,18 +24,15 @@
true
true
enable
+ AllEnabledByDefault
+ ../jellyfin.ruleset
-
-
- ../jellyfin.ruleset
-
-
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
index 77819c7649..29bac66340 100644
--- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -3,7 +3,6 @@
using System;
using System.IO;
using System.Linq;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase
@@ -53,7 +52,8 @@ namespace Emby.Server.Implementations.AppBase
Directory.CreateDirectory(directory);
// Save it after load in case we got new items
- using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
{
fs.Write(newBytes, 0, newBytesLen);
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index e8aa019ab2..0512adf106 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -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;
@@ -43,6 +41,7 @@ using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.SyncPlay;
using Emby.Server.Implementations.TV;
+using Emby.Server.Implementations.Udp;
using Emby.Server.Implementations.Updates;
using Jellyfin.Api.Helpers;
using Jellyfin.Networking.Configuration;
@@ -50,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;
@@ -99,6 +97,7 @@ using MediaBrowser.Providers.Subtitles;
using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Prometheus.DotNetRuntime;
@@ -118,6 +117,7 @@ namespace Emby.Server.Implementations
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
private readonly IFileSystem _fileSystemManager;
+ private readonly IConfiguration _startupConfig;
private readonly IXmlSerializer _xmlSerializer;
private readonly IStartupOptions _startupOptions;
private readonly IPluginManager _pluginManager;
@@ -126,7 +126,6 @@ namespace Emby.Server.Implementations
private IMediaEncoder _mediaEncoder;
private ISessionManager _sessionManager;
private string[] _urlPrefixes;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
///
/// Gets a value indicating whether this instance can self restart.
@@ -135,9 +134,6 @@ namespace Emby.Server.Implementations
public bool CoreStartupHasCompleted { get; private set; }
- ///
- public Uri PublishedServerUrl => _startupOptions.PublishedServerUrl;
-
public virtual bool CanLaunchWebBrowser
{
get
@@ -214,7 +210,7 @@ namespace Emby.Server.Implementations
/// Gets or sets the configuration manager.
///
/// The configuration manager.
- protected IConfigurationManager ConfigurationManager { get; set; }
+ public ServerConfigurationManager ConfigurationManager { get; set; }
///
/// Gets or sets the service provider.
@@ -232,10 +228,9 @@ namespace Emby.Server.Implementations
public int HttpsPort { get; private set; }
///
- /// Gets the server configuration manager.
+ /// Gets the value of the PublishedServerUrl setting.
///
- /// The server configuration manager.
- public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager;
+ public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
///
/// Initializes a new instance of the class.
@@ -243,51 +238,37 @@ namespace Emby.Server.Implementations
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
+ /// The interface.
/// Instance of the interface.
/// Instance of the interface.
public ApplicationHost(
IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
IStartupOptions options,
+ IConfiguration startupConfig,
IFileSystem fileSystem,
IServiceCollection serviceCollection)
{
- _xmlSerializer = new MyXmlSerializer();
-
- ServiceCollection = serviceCollection;
-
ApplicationPaths = applicationPaths;
LoggerFactory = loggerFactory;
+ _startupOptions = options;
+ _startupConfig = startupConfig;
_fileSystemManager = fileSystem;
-
- ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
- // Have to migrate settings here as migration subsystem not yet initialised.
- MigrateNetworkConfiguration();
-
- // Have to pre-register the NetworkConfigurationFactory, as the configuration sub-system is not yet initialised.
- ConfigurationManager.RegisterConfiguration();
- NetManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger());
+ ServiceCollection = serviceCollection;
Logger = LoggerFactory.CreateLogger();
-
- _startupOptions = options;
-
- // Initialize runtime stat collection
- if (ServerConfigurationManager.Configuration.EnableMetrics)
- {
- DotNetRuntimeStatsBuilder.Default().StartCollecting();
- }
-
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
ApplicationVersionString = ApplicationVersion.ToString(3);
ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
+ _xmlSerializer = new MyXmlSerializer();
+ ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
_pluginManager = new PluginManager(
LoggerFactory.CreateLogger(),
this,
- ServerConfigurationManager.Configuration,
+ ConfigurationManager.Configuration,
ApplicationPaths.PluginsPath,
ApplicationVersion);
}
@@ -302,9 +283,9 @@ namespace Emby.Server.Implementations
if (!File.Exists(path))
{
var networkSettings = new NetworkConfiguration();
- ClassMigrationHelper.CopyProperties(ServerConfigurationManager.Configuration, networkSettings);
+ ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings);
_xmlSerializer.SerializeToFile(networkSettings, path);
- Logger?.LogDebug("Successfully migrated network settings.");
+ Logger.LogDebug("Successfully migrated network settings.");
}
}
@@ -463,7 +444,7 @@ namespace Emby.Server.Implementations
}
///
- public IReadOnlyCollection GetExports(CreationDelegate defaultFunc, bool manageLifetime = true)
+ public IReadOnlyCollection GetExports(CreationDelegateFactory defaultFunc, bool manageLifetime = true)
{
// Convert to list so this isn't executed for each iteration
var parts = GetExportTypes()
@@ -487,8 +468,9 @@ namespace Emby.Server.Implementations
/// Runs the startup tasks.
///
/// .
- public async Task RunStartupTasksAsync()
+ public async Task RunStartupTasksAsync(CancellationToken cancellationToken)
{
+ cancellationToken.ThrowIfCancellationRequested();
Logger.LogInformation("Running startup tasks");
Resolve().AddTasks(GetExports(false));
@@ -502,14 +484,21 @@ namespace Emby.Server.Implementations
var entryPoints = GetExports();
+ cancellationToken.ThrowIfCancellationRequested();
+
var stopWatch = new Stopwatch();
stopWatch.Start();
+
await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false);
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
Logger.LogInformation("Core startup complete");
CoreStartupHasCompleted = true;
+
+ cancellationToken.ThrowIfCancellationRequested();
+
stopWatch.Restart();
+
await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
stopWatch.Stop();
@@ -533,7 +522,21 @@ namespace Emby.Server.Implementations
///
public void Init()
{
- var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration();
+ DiscoverTypes();
+
+ ConfigurationManager.AddParts(GetExports());
+
+ // Have to migrate settings here as migration subsystem not yet initialised.
+ MigrateNetworkConfiguration();
+ NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger());
+
+ // Initialize runtime stat collection
+ if (ConfigurationManager.Configuration.EnableMetrics)
+ {
+ DotNetRuntimeStatsBuilder.Default().StartCollecting();
+ }
+
+ var networkConfiguration = ConfigurationManager.GetNetworkConfiguration();
HttpPort = networkConfiguration.HttpServerPortNumber;
HttpsPort = networkConfiguration.HttpsPortNumber;
@@ -551,8 +554,6 @@ namespace Emby.Server.Implementations
};
Certificate = GetCertificate(CertificateInfo);
- DiscoverTypes();
-
RegisterServices();
_pluginManager.RegisterServices(ServiceCollection);
@@ -567,7 +568,8 @@ namespace Emby.Server.Implementations
ServiceCollection.AddMemoryCache();
- ServiceCollection.AddSingleton(ConfigurationManager);
+ ServiceCollection.AddSingleton(ConfigurationManager);
+ ServiceCollection.AddSingleton(ConfigurationManager);
ServiceCollection.AddSingleton(this);
ServiceCollection.AddSingleton(_pluginManager);
ServiceCollection.AddSingleton(ApplicationPaths);
@@ -594,8 +596,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton(this);
ServiceCollection.AddSingleton(ApplicationPaths);
- ServiceCollection.AddSingleton(ServerConfigurationManager);
-
ServiceCollection.AddSingleton();
ServiceCollection.AddSingleton();
@@ -685,6 +685,8 @@ namespace Emby.Server.Implementations
ServiceCollection.AddScoped();
ServiceCollection.AddScoped();
ServiceCollection.AddScoped();
+
+ ServiceCollection.AddSingleton();
}
///
@@ -782,7 +784,7 @@ namespace Emby.Server.Implementations
{
// For now there's no real way to inject these properly
BaseItem.Logger = Resolve>();
- BaseItem.ConfigurationManager = ServerConfigurationManager;
+ BaseItem.ConfigurationManager = ConfigurationManager;
BaseItem.LibraryManager = Resolve();
BaseItem.ProviderManager = Resolve();
BaseItem.LocalizationManager = Resolve();
@@ -804,13 +806,12 @@ namespace Emby.Server.Implementations
///
private void FindParts()
{
- if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
+ if (!ConfigurationManager.Configuration.IsPortAuthorized)
{
- ServerConfigurationManager.Configuration.IsPortAuthorized = true;
+ ConfigurationManager.Configuration.IsPortAuthorized = true;
ConfigurationManager.SaveConfiguration();
}
- ConfigurationManager.AddParts(GetExports());
_pluginManager.CreatePlugins();
_urlPrefixes = GetUrlPrefixes().ToArray();
@@ -914,7 +915,7 @@ namespace Emby.Server.Implementations
protected void OnConfigurationUpdated(object sender, EventArgs e)
{
var requiresRestart = false;
- var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration();
+ var networkConfiguration = ConfigurationManager.GetNetworkConfiguration();
// Don't do anything if these haven't been set yet
if (HttpPort != 0 && HttpsPort != 0)
@@ -923,10 +924,10 @@ namespace Emby.Server.Implementations
if (networkConfiguration.HttpServerPortNumber != HttpPort ||
networkConfiguration.HttpsPortNumber != HttpsPort)
{
- if (ServerConfigurationManager.Configuration.IsPortAuthorized)
+ if (ConfigurationManager.Configuration.IsPortAuthorized)
{
- ServerConfigurationManager.Configuration.IsPortAuthorized = false;
- ServerConfigurationManager.SaveConfiguration();
+ ConfigurationManager.Configuration.IsPortAuthorized = false;
+ ConfigurationManager.SaveConfiguration();
requiresRestart = true;
}
@@ -1142,16 +1143,16 @@ namespace Emby.Server.Implementations
}
///
- public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.GetNetworkConfiguration().EnableHttps;
+ public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps;
///
public string GetSmartApiUrl(IPAddress ipAddress, int? port = null)
{
// Published server ends with a /
- if (_startupOptions.PublishedServerUrl != null)
+ if (!string.IsNullOrEmpty(PublishedServerUrl))
{
// Published server ends with a '/', so we need to remove it.
- return _startupOptions.PublishedServerUrl.ToString().Trim('/');
+ return PublishedServerUrl.Trim('/');
}
string smart = NetManager.GetBindInterface(ipAddress, out port);
@@ -1168,10 +1169,10 @@ namespace Emby.Server.Implementations
public string GetSmartApiUrl(HttpRequest request, int? port = null)
{
// Published server ends with a /
- if (_startupOptions.PublishedServerUrl != null)
+ if (!string.IsNullOrEmpty(PublishedServerUrl))
{
// Published server ends with a '/', so we need to remove it.
- return _startupOptions.PublishedServerUrl.ToString().Trim('/');
+ return PublishedServerUrl.Trim('/');
}
string smart = NetManager.GetBindInterface(request, out port);
@@ -1188,10 +1189,10 @@ namespace Emby.Server.Implementations
public string GetSmartApiUrl(string hostname, int? port = null)
{
// Published server ends with a /
- if (_startupOptions.PublishedServerUrl != null)
+ if (!string.IsNullOrEmpty(PublishedServerUrl))
{
// Published server ends with a '/', so we need to remove it.
- return _startupOptions.PublishedServerUrl.ToString().Trim('/');
+ return PublishedServerUrl.Trim('/');
}
string smart = NetManager.GetBindInterface(hostname, out port);
@@ -1226,14 +1227,14 @@ namespace Emby.Server.Implementations
Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
Host = host,
Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
- Path = ServerConfigurationManager.GetNetworkConfiguration().BaseUrl
+ Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl
}.ToString().TrimEnd('/');
}
public string FriendlyName =>
- string.IsNullOrEmpty(ServerConfigurationManager.Configuration.ServerName)
+ string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName)
? Environment.MachineName
- : ServerConfigurationManager.Configuration.ServerName;
+ : ConfigurationManager.Configuration.ServerName;
///
/// Shuts down.
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 8c5fa09f69..7324b0ee9f 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -49,7 +48,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;
///
/// Initializes a new instance of the class.
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 1ab2bdfbe8..e984afdbab 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -8,11 +7,9 @@ using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Collections;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
@@ -344,7 +341,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;
+ }
}
}
}
diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs
index cd9dbb1bda..01dc728c1c 100644
--- a/Emby.Server.Implementations/ConfigurationOptions.cs
+++ b/Emby.Server.Implementations/ConfigurationOptions.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using Emby.Server.Implementations.HttpServer;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Emby.Server.Implementations
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index d78b93bd78..694805ebe9 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -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");
}
@@ -5415,7 +5415,6 @@ AND Type = @InternalPersonType)");
ItemIds = query.ItemIds,
TopParentIds = query.TopParentIds,
ParentId = query.ParentId,
- IsPlayed = query.IsPlayed,
IsAiring = query.IsAiring,
IsMovie = query.IsMovie,
IsSports = query.IsSports,
@@ -5441,6 +5440,7 @@ AND Type = @InternalPersonType)");
var outerQuery = new InternalItemsQuery(query.User)
{
+ IsPlayed = query.IsPlayed,
IsFavorite = query.IsFavorite,
IsFavoriteOrLiked = query.IsFavoriteOrLiked,
IsLiked = query.IsLiked,
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index f03f04e021..9248053f54 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -27,6 +27,7 @@
+
@@ -45,20 +46,17 @@
true
AD0001
+ AllEnabledByDefault
+ ../jellyfin.ruleset
-
-
- ../jellyfin.ruleset
-
-
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index a12a6b26c5..3624e079f5 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -1,5 +1,6 @@
#nullable enable
+using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@@ -51,6 +52,8 @@ namespace Emby.Server.Implementations.EntryPoints
///
public Task RunAsync()
{
+ CheckDisposed();
+
try
{
_udpServer = new UdpServer(_logger, _appHost, _config);
@@ -64,6 +67,14 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask;
}
+ private void CheckDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(this.GetType().Name);
+ }
+ }
+
///
public void Dispose()
{
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
index 4a0fc8239e..9afabf5272 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -1,6 +1,5 @@
#pragma warning disable CS1591
-using System;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
index 040b6b9e4e..dd77b45d89 100644
--- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
var authorization = _authContext.GetAuthorizationInfo(requestContext);
var user = authorization.User;
- return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp(), user);
+ return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user);
}
public SessionInfo GetSession(object requestContext)
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index 7e0c2c1da2..06acb56061 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer
RemoteEndPoint = remoteEndPoint;
QueryString = query;
- _jsonOptions = JsonDefaults.GetOptions();
+ _jsonOptions = JsonDefaults.Options;
LastActivityDate = DateTime.Now;
}
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
index d6cf6233e4..1bee1ac310 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
@@ -14,15 +14,18 @@ namespace Emby.Server.Implementations.HttpServer
public class WebSocketManager : IWebSocketManager
{
private readonly IWebSocketListener[] _webSocketListeners;
+ private readonly IAuthService _authService;
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
public WebSocketManager(
+ IAuthService authService,
IEnumerable webSocketListeners,
ILogger logger,
ILoggerFactory loggerFactory)
{
_webSocketListeners = webSocketListeners.ToArray();
+ _authService = authService;
_logger = logger;
_loggerFactory = loggerFactory;
}
@@ -30,6 +33,7 @@ namespace Emby.Server.Implementations.HttpServer
///
public async Task WebSocketRequestHandler(HttpContext context)
{
+ _ = _authService.Authenticate(context.Request);
try
{
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index c0e757543d..3893a15771 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.IO
}
var extension = Path.GetExtension(filename);
- return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
+ return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
}
///
@@ -249,9 +249,18 @@ namespace Emby.Server.Implementations.IO
// Issue #2354 get the size of files behind symbolic links
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
- using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
+ try
{
- result.Length = thisFileStream.Length;
+ using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
+ {
+ result.Length = thisFileStream.Length;
+ }
+ }
+ catch (FileNotFoundException ex)
+ {
+ // Dangling symlinks cannot be detected before opening the file unfortunately...
+ Logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
+ result.Exists = false;
}
}
@@ -487,26 +496,9 @@ namespace Emby.Server.Implementations.IO
throw new ArgumentNullException(nameof(path));
}
- var separatorChar = Path.DirectorySeparatorChar;
-
- return path.IndexOf(parentPath.TrimEnd(separatorChar) + separatorChar, StringComparison.OrdinalIgnoreCase) != -1;
- }
-
- public virtual bool IsRootPath(string path)
- {
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- var parent = Path.GetDirectoryName(path);
-
- if (!string.IsNullOrEmpty(parent))
- {
- return false;
- }
-
- return true;
+ return path.Contains(
+ Path.TrimEndingDirectorySeparator(parentPath) + Path.DirectorySeparatorChar,
+ _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
public virtual string NormalizePath(string path)
@@ -521,7 +513,7 @@ namespace Emby.Server.Implementations.IO
return path;
}
- return path.TrimEnd(Path.DirectorySeparatorChar);
+ return Path.TrimEndingDirectorySeparator(path);
}
public virtual bool AreEqual(string path1, string path2)
@@ -536,7 +528,10 @@ namespace Emby.Server.Implementations.IO
return false;
}
- return string.Equals(NormalizePath(path1), NormalizePath(path2), StringComparison.OrdinalIgnoreCase);
+ return string.Equals(
+ NormalizePath(path1),
+ NormalizePath(path2),
+ _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
public virtual string GetFileNameWithoutExtension(FileSystemMetadata info)
diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs
index 4bef59543f..f719dc5f89 100644
--- a/Emby.Server.Implementations/IStartupOptions.cs
+++ b/Emby.Server.Implementations/IStartupOptions.cs
@@ -1,6 +1,5 @@
#pragma warning disable CS1591
-
-using System;
+#nullable enable
namespace Emby.Server.Implementations
{
@@ -9,7 +8,7 @@ namespace Emby.Server.Implementations
///
/// Gets the value of the --ffmpeg command line option.
///
- string FFmpegPath { get; }
+ string? FFmpegPath { get; }
///
/// Gets the value of the --service command line option.
@@ -19,21 +18,21 @@ namespace Emby.Server.Implementations
///
/// Gets the value of the --package-name command line option.
///
- string PackageName { get; }
+ string? PackageName { get; }
///
/// Gets the value of the --restartpath command line option.
///
- string RestartPath { get; }
+ string? RestartPath { get; }
///
/// Gets the value of the --restartargs command line option.
///
- string RestartArgs { get; }
+ string? RestartArgs { get; }
///
/// Gets the value of the --published-server-url command line option.
///
- Uri PublishedServerUrl { get; }
+ string? PublishedServerUrl { get; }
}
}
diff --git a/Emby.Server.Implementations/Images/ArtistImageProvider.cs b/Emby.Server.Implementations/Images/ArtistImageProvider.cs
index afa4ec7b1b..e96b64595c 100644
--- a/Emby.Server.Implementations/Images/ArtistImageProvider.cs
+++ b/Emby.Server.Implementations/Images/ArtistImageProvider.cs
@@ -2,20 +2,12 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Images
{
diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
index 462eb03a80..50c5314820 100644
--- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index d9ffe64b3b..6a9f4174dc 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -196,33 +196,33 @@ namespace Emby.Server.Implementations.Library
/// Gets or sets the postscan tasks.
///
/// The postscan tasks.
- private ILibraryPostScanTask[] PostscanTasks { get; set; }
+ private ILibraryPostScanTask[] PostscanTasks { get; set; } = Array.Empty();
///
/// Gets or sets the intro providers.
///
/// The intro providers.
- private IIntroProvider[] IntroProviders { get; set; }
+ private IIntroProvider[] IntroProviders { get; set; } = Array.Empty();
///
/// Gets or sets the list of entity resolution ignore rules.
///
/// The entity resolution ignore rules.
- private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
+ private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } = Array.Empty();
///
/// Gets or sets the list of currently registered entity resolvers.
///
/// The entity resolvers enumerable.
- private IItemResolver[] EntityResolvers { get; set; }
+ private IItemResolver[] EntityResolvers { get; set; } = Array.Empty();
- private IMultiItemResolver[] MultiItemResolvers { get; set; }
+ private IMultiItemResolver[] MultiItemResolvers { get; set; } = Array.Empty();
///
/// Gets or sets the comparers.
///
/// The comparers.
- private IBaseItemComparer[] Comparers { get; set; }
+ private IBaseItemComparer[] Comparers { get; set; } = Array.Empty();
public bool IsScanRunning { get; private set; }
@@ -1247,7 +1247,7 @@ namespace Emby.Server.Implementations.Library
{
// TODO: @bond use a ReadOnlySpan here when Enum.TryParse supports it
// https://github.com/dotnet/runtime/issues/20008
- if (Enum.TryParse(Path.GetExtension(file), true, out var res))
+ if (Enum.TryParse(Path.GetFileNameWithoutExtension(file), true, out var res))
{
return res;
}
@@ -1914,12 +1914,17 @@ namespace Emby.Server.Implementations.Library
}
catch (ArgumentException)
{
- _logger.LogWarning("Cannot get image index for {0}", img.Path);
+ _logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
continue;
}
- catch (InvalidOperationException)
+ catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
{
- _logger.LogWarning("Cannot fetch image from {0}", img.Path);
+ _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
+ continue;
+ }
+ catch (HttpRequestException ex)
+ {
+ _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode);
continue;
}
}
@@ -1932,7 +1937,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Cannot get image dimensions for {0}", image.Path);
+ _logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
image.Width = 0;
image.Height = 0;
continue;
@@ -1944,7 +1949,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path);
+ _logger.LogError(ex, "Cannot compute blurhash for {ImagePath}", image.Path);
image.BlurHash = string.Empty;
}
@@ -1954,7 +1959,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Cannot update DateModified for {0}", image.Path);
+ _logger.LogError(ex, "Cannot update DateModified for {ImagePath}", image.Path);
}
}
@@ -2776,6 +2781,7 @@ namespace Emby.Server.Implementations.Library
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
{
+ string newPath;
if (ownerItem != null)
{
var libraryOptions = GetLibraryOptions(ownerItem);
@@ -2783,15 +2789,9 @@ namespace Emby.Server.Implementations.Library
{
foreach (var pathInfo in libraryOptions.PathInfos)
{
- if (string.IsNullOrWhiteSpace(pathInfo.Path) || string.IsNullOrWhiteSpace(pathInfo.NetworkPath))
+ if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out newPath))
{
- continue;
- }
-
- var substitutionResult = SubstitutePathInternal(path, pathInfo.Path, pathInfo.NetworkPath);
- if (substitutionResult.Item2)
- {
- return substitutionResult.Item1;
+ return newPath;
}
}
}
@@ -2800,24 +2800,16 @@ namespace Emby.Server.Implementations.Library
var metadataPath = _configurationManager.Configuration.MetadataPath;
var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath;
- if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
+ if (path.TryReplaceSubPath(metadataPath, metadataNetworkPath, out newPath))
{
- var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath);
- if (metadataSubstitutionResult.Item2)
- {
- return metadataSubstitutionResult.Item1;
- }
+ return newPath;
}
foreach (var map in _configurationManager.Configuration.PathSubstitutions)
{
- if (!string.IsNullOrWhiteSpace(map.From))
+ if (path.TryReplaceSubPath(map.From, map.To, out newPath))
{
- var substitutionResult = SubstitutePathInternal(path, map.From, map.To);
- if (substitutionResult.Item2)
- {
- return substitutionResult.Item1;
- }
+ return newPath;
}
}
@@ -2826,47 +2818,12 @@ namespace Emby.Server.Implementations.Library
public string SubstitutePath(string path, string from, string to)
{
- return SubstitutePathInternal(path, from, to).Item1;
- }
-
- private Tuple SubstitutePathInternal(string path, string from, string to)
- {
- if (string.IsNullOrWhiteSpace(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- if (string.IsNullOrWhiteSpace(from))
+ if (path.TryReplaceSubPath(from, to, out var newPath))
{
- throw new ArgumentNullException(nameof(from));
+ return newPath;
}
- if (string.IsNullOrWhiteSpace(to))
- {
- throw new ArgumentNullException(nameof(to));
- }
-
- from = from.Trim();
- to = to.Trim();
-
- var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
- var changed = false;
-
- if (!string.Equals(newPath, path, StringComparison.Ordinal))
- {
- if (to.IndexOf('/', StringComparison.Ordinal) != -1)
- {
- newPath = newPath.Replace('\\', '/');
- }
- else
- {
- newPath = newPath.Replace('/', '\\');
- }
-
- changed = true;
- }
-
- return new Tuple(newPath, changed);
+ return path;
}
private void SetExtraTypeFromFilename(Video item)
@@ -3001,7 +2958,7 @@ namespace Emby.Server.Implementations.Library
if (collectionType != null)
{
- var path = Path.Combine(virtualFolderPath, collectionType.ToString() + ".collection");
+ var path = Path.Combine(virtualFolderPath, collectionType.ToString().ToLowerInvariant() + ".collection");
File.WriteAllBytes(path, Array.Empty());
}
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index 2070df31e4..c2951dd155 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -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)
{
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index c63eb70179..d0b85f07d4 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(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;
@@ -199,10 +199,15 @@ namespace Emby.Server.Implementations.Library
{
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
}
+ else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
+ {
+ source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
+ source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
+ }
}
}
- return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList();
+ return SortMediaSources(list);
}
public MediaProtocol GetPathProtocol(string path)
@@ -436,7 +441,7 @@ namespace Emby.Server.Implementations.Library
}
}
- private static IEnumerable SortMediaSources(IEnumerable sources)
+ private static List SortMediaSources(IEnumerable sources)
{
return sources.OrderBy(i =>
{
@@ -451,8 +456,9 @@ namespace Emby.Server.Implementations.Library
{
var stream = i.VideoStream;
- return stream == null || stream.Width == null ? 0 : stream.Width.Value;
+ return stream?.Width ?? 0;
})
+ .Where(i => i.Type != MediaSourceType.Placeholder)
.ToList();
}
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 06ff3e611b..770cf6bb02 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -1,7 +1,8 @@
#nullable enable
using System;
-using System.Text.RegularExpressions;
+using System.Diagnostics.CodeAnalysis;
+using MediaBrowser.Common.Providers;
namespace Emby.Server.Implementations.Library
{
@@ -41,11 +42,72 @@ namespace Emby.Server.Implementations.Library
// for imdbid we also accept pattern matching
if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
{
- var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
- return m.Success ? m.Value : null;
+ var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
+ return match ? imdbId.ToString() : null;
}
return null;
}
+
+ ///
+ /// Replaces a sub path with another sub path and normalizes the final path.
+ ///
+ /// The original path.
+ /// The original sub path.
+ /// The new sub path.
+ /// The result of the sub path replacement
+ /// The path after replacing the sub path.
+ /// , or is empty.
+ 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)
+ {
+ return false;
+ }
+
+ char oldDirectorySeparatorChar;
+ char newDirectorySeparatorChar;
+ // True normalization is still not possible https://github.com/dotnet/runtime/issues/2162
+ // The reasoning behind this is that a forward slash likely means it's a Linux path and
+ // so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much).
+ if (newSubPath.Contains('/', StringComparison.Ordinal))
+ {
+ oldDirectorySeparatorChar = '\\';
+ newDirectorySeparatorChar = '/';
+ }
+ else
+ {
+ oldDirectorySeparatorChar = '/';
+ newDirectorySeparatorChar = '\\';
+ }
+
+ path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
+ subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
+
+ // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results
+ // when the sub path matches a similar but in-complete subpath
+ var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar;
+ if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase)
+ || (!oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar))
+ {
+ return false;
+ }
+
+ var newSubPathTrimmed = newSubPath.AsSpan().TrimEnd(newDirectorySeparatorChar);
+ // Ensure that the path with the old subpath removed starts with a leading dir separator
+ int idx = oldSubPathEndsWithSeparator ? subPath.Length - 1 : subPath.Length;
+ newPath = string.Concat(newSubPathTrimmed, path.AsSpan(idx));
+
+ return true;
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index 90b6a8a7db..4ad84579d8 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -201,6 +201,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
continue;
}
+ if (resolvedItem.Files.Count == 0)
+ {
+ continue;
+ }
+
var firstMedia = resolvedItem.Files[0];
var libraryItem = new T
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index 2f5e46038d..6e688693be 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
///
/// The args.
/// `0.
- protected override T Resolve(ItemResolveArgs args)
+ public override T Resolve(ItemResolveArgs args)
{
return ResolveVideo(args, false);
}
@@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// The args.
/// if set to true [parse name].
/// ``0.
- protected TVideoType ResolveVideo(ItemResolveArgs args, bool parseName)
+ protected virtual TVideoType ResolveVideo(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new()
{
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index 86242d1379..0525c7e307 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -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();
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 8ef7172de1..714bc3a84d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -69,6 +69,110 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return result;
}
+ ///
+ /// Resolves the specified args.
+ ///
+ /// The args.
+ /// Video.
+ 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(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
+ }
+
+ if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
+ {
+ return FindMovie
public class EpisodeResolver : BaseVideoResolver
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ public EpisodeResolver(ILibraryManager libraryManager)
+ : base(libraryManager)
+ {
+ }
+
///
/// Resolves the specified args.
///
/// The args.
/// Episode.
- 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().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())
+ if ((season != null ||
+ string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
+ args.HasParent())
+ && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.Contains(parent.Name, StringComparer.OrdinalIgnoreCase)))
{
var episode = ResolveVideo(args, false);
@@ -74,14 +85,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- public EpisodeResolver(ILibraryManager libraryManager)
- : base(libraryManager)
- {
- }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 732bfd94dc..4a9d2cf8ca 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -127,7 +127,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{
if (child.IsDirectory)
{
- if (IsSeasonFolder(child.FullName, isTvContentType, libraryManager))
+ if (IsSeasonFolder(child.FullName, isTvContentType))
{
logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName);
return true;
@@ -160,32 +160,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return false;
}
- ///
- /// Determines whether [is place holder] [the specified path].
- ///
- /// The path.
- /// true if [is place holder] [the specified path]; otherwise, false.
- /// path
- private static bool IsVideoPlaceHolder(string path)
- {
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- var extension = Path.GetExtension(path);
-
- return string.Equals(extension, ".disc", StringComparison.OrdinalIgnoreCase);
- }
-
///
/// Determines whether [is season folder] [the specified path].
///
/// The path.
/// if set to true [is tv content type].
- /// The library manager.
/// true if [is season folder] [the specified path]; otherwise, false.
- private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
+ private static bool IsSeasonFolder(string path, bool isTvContentType)
{
var seasonNumber = SeasonPathParser.Parse(path, isTvContentType, isTvContentType).SeasonNumber;
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 94602582b7..bcdf854ca3 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -12,7 +12,6 @@ using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
-using Microsoft.Extensions.Logging;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index d16275b192..e8caea1964 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -13,8 +13,8 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using Book = MediaBrowser.Controller.Entities.Book;
using AudioBook = MediaBrowser.Controller.Entities.AudioBook;
+using Book = MediaBrowser.Controller.Entities.Book;
namespace Emby.Server.Implementations.Library
{
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index b6b7ea9495..ac041bcf6c 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using System.Threading;
using Jellyfin.Data.Entities;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 341194f239..7a6b1d8b61 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -45,7 +45,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
- using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
onStarted();
@@ -70,7 +71,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
- await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read);
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);
onStarted();
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 13b5a1c552..c9d9cc49af 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -17,7 +17,6 @@ using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
@@ -1856,7 +1855,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
var settings = new XmlWriterSettings
{
@@ -1920,7 +1920,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
var settings = new XmlWriterSettings
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 78a82118ed..44a8cdee49 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IServerApplicationPaths _appPaths;
private readonly TaskCompletionSource _taskCompletionSource = new TaskCompletionSource();
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;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
index 463d0ed0a3..8c27ca76e3 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
@@ -9,55 +9,44 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
internal class EpgChannelData
{
+
+ private readonly Dictionary _channelsById;
+
+ private readonly Dictionary _channelsByNumber;
+
+ private readonly Dictionary _channelsByName;
+
public EpgChannelData(IEnumerable channels)
{
- ChannelsById = new Dictionary(StringComparer.OrdinalIgnoreCase);
- ChannelsByNumber = new Dictionary(StringComparer.OrdinalIgnoreCase);
- ChannelsByName = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ _channelsById = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ _channelsByNumber = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ _channelsByName = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var channel in channels)
{
- ChannelsById[channel.Id] = channel;
+ _channelsById[channel.Id] = channel;
if (!string.IsNullOrEmpty(channel.Number))
{
- ChannelsByNumber[channel.Number] = channel;
+ _channelsByNumber[channel.Number] = channel;
}
var normalizedName = NormalizeName(channel.Name ?? string.Empty);
if (!string.IsNullOrWhiteSpace(normalizedName))
{
- ChannelsByName[normalizedName] = channel;
+ _channelsByName[normalizedName] = channel;
}
}
}
- private Dictionary ChannelsById { get; set; }
-
- private Dictionary ChannelsByNumber { get; set; }
-
- private Dictionary ChannelsByName { get; set; }
-
public ChannelInfo GetChannelById(string id)
- {
- ChannelsById.TryGetValue(id, out var result);
-
- return result;
- }
+ => _channelsById.GetValueOrDefault(id);
public ChannelInfo GetChannelByNumber(string number)
- {
- ChannelsByNumber.TryGetValue(number, out var result);
-
- return result;
- }
+ => _channelsByNumber.GetValueOrDefault(number);
public ChannelInfo GetChannelByName(string name)
- {
- ChannelsByName.TryGetValue(name, out var result);
-
- return result;
- }
+ => _channelsByName.GetValueOrDefault(name);
public static string NormalizeName(string value)
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index 57424f0435..1cac9cb963 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -4,9 +4,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Text;
using System.Text.Json;
-using System.Threading.Tasks;
using MediaBrowser.Common.Json;
using Microsoft.Extensions.Logging;
@@ -17,7 +15,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(
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
index da707fec63..b1259de232 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
@@ -2,7 +2,6 @@
using System;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 6d7c5ac6ee..1926e738f2 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ICryptoProvider _cryptoProvider;
private readonly ConcurrentDictionary _tokens = new ConcurrentDictionary();
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private DateTime _lastErrorResponse;
public SchedulesDirect(
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index 76c8757370..6824aa4423 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
-using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Threading;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 0760e81274..1dcc786874 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -8,10 +8,8 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.Json;
-using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
@@ -19,7 +17,6 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -61,7 +58,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_networkManager = networkManager;
_streamHelper = streamHelper;
- _jsonOptions = JsonDefaults.GetOptions();
+ _jsonOptions = JsonDefaults.Options;
}
public string Name => "HD Homerun";
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index f09338330f..a7fda1d72e 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -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,17 +122,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private static async Task 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.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);
- ParseReturnMessage(buffer, receivedBytes, out string returnVal);
+ int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
- return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase);
+ return VerifyReturnValueOfGetSet(buffer.AsSpan(receivedBytes), "none");
}
finally
{
@@ -166,24 +166,24 @@ 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 _))
+ if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _))
{
continue;
}
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 _))
+ if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _))
{
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
continue;
@@ -191,13 +191,13 @@ 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 _))
+ if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _))
{
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
continue;
@@ -232,12 +232,12 @@ 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 _))
+ if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _))
{
return;
}
@@ -265,17 +265,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.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,249 +283,136 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- private static byte[] CreateGetMessage(int tuner, string name)
+ internal static int WriteGetMessage(Span 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);
+ internal static int WriteSetMessage(Span 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 buffer, ReadOnlySpan 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;
- }
-
- var message = new byte[messageLength];
+ // TODO: variable length: this can be 2 bytes if len > 127
+ // Write length in front of value
+ buffer[0] = Convert.ToByte(len);
- int offset = InsertHeaderAndName(byteName, messageLength, message);
+ // null-terminate
+ buffer[len++] = 0;
- bool flipEndian = BitConverter.IsLittleEndian;
+ return len;
+ }
- 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);
- }
+ private static int WriteHeaderAndPayload(Span buffer, ReadOnlySpan payload)
+ {
+ // Packet type
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, GetSetRequest);
- Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
- offset += 4;
- }
+ // We write the payload length at the end
+ int offset = 4;
- // calculate crc and insert at the end of the message
- var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
- if (flipEndian)
- {
- Array.Reverse(crcBytes);
- }
+ // Tag
+ buffer[offset++] = GetSetName;
- 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 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;
+ // Payload length
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), (ushort)(offset - 4));
- // insert tag name and length
- message[offset++] = GetSetName;
- message[offset++] = Convert.ToByte(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);
- // insert name string
- Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
- offset += byteName.Length;
+ return offset + 4;
+ }
- return offset;
+ internal static bool VerifyReturnValueOfGetSet(ReadOnlySpan buffer, string expected)
+ {
+ return TryGetReturnValueOfGetSet(buffer, out var value)
+ && string.Equals(Encoding.UTF8.GetString(value), expected, StringComparison.OrdinalIgnoreCase);
}
- private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal)
+ internal static bool TryGetReturnValueOfGetSet(ReadOnlySpan buffer, out ReadOnlySpan value)
{
- returnVal = string.Empty;
+ value = ReadOnlySpan.Empty;
- if (numBytes < 4)
+ if (buffer.Length < 8)
{
return false;
}
- var flipEndian = BitConverter.IsLittleEndian;
- int offset = 0;
- byte[] msgTypeBytes = new byte[2];
- Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length);
-
- if (flipEndian)
+ uint crc = BinaryPrimitives.ReadUInt32LittleEndian(buffer[^4..]);
+ if (crc != Crc32.Compute(buffer[..^4]))
{
- Array.Reverse(msgTypeBytes);
+ return false;
}
- var msgType = BitConverter.ToUInt16(msgTypeBytes, 0);
- offset += 2;
-
- if (msgType != GetSetReply)
+ if (BinaryPrimitives.ReadUInt16BigEndian(buffer) != GetSetReply)
{
return false;
}
- byte[] msgLengthBytes = new byte[2];
- Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length);
- if (flipEndian)
+ var msgLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2));
+ if (buffer.Length != 2 + 2 + 4 + msgLength)
{
- Array.Reverse(msgLengthBytes);
+ return false;
}
- var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0);
- offset += 2;
-
- if (numBytes < msgLength + 8)
+ var offset = 4;
+ if (buffer[offset++] != GetSetName)
{
return false;
}
- offset++; // Name Tag
-
- var nameLength = buf[offset++];
+ var nameLength = buffer[offset++];
+ if (buffer.Length < 4 + 1 + offset + nameLength)
+ {
+ return false;
+ }
- // skip the name field to get to value for return
offset += nameLength;
- offset++; // Value Tag
-
- var valueLength = buf[offset++];
-
- 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)
+ if (buffer[offset++] != GetSetValue)
{
- var hash = 0xffffffff;
- for (var i = 0; i < numBytes; i++)
- {
- hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
- }
+ return false;
+ }
- 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;
+ var valueLength = buffer[offset++];
+ if (buffer.Length < 4 + offset + valueLength)
+ {
+ return false;
}
+
+ // remove null terminator
+ value = buffer.Slice(offset, valueLength - 1);
+ return true;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index c4f173c7ac..cc30a516d6 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
-using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
@@ -133,6 +132,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
channel.ImageUrl = value;
}
+ if (attributes.TryGetValue("group-title", out string groupTitle))
+ {
+ channel.ChannelGroup = groupTitle;
+ }
+
channel.Name = GetChannelName(extInf, attributes);
channel.Number = GetChannelNumber(extInf, attributes, mediaUrl);
diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json
index 4ee4eb989f..051d6d009f 100644
--- a/Emby.Server.Implementations/Localization/Core/da.json
+++ b/Emby.Server.Implementations/Localization/Core/da.json
@@ -1,5 +1,5 @@
{
- "Albums": "Albums",
+ "Albums": "Albummer",
"AppDeviceValues": "App: {0}, Enhed: {1}",
"Application": "Applikation",
"Artists": "Kunstnere",
diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json
index 3ff7eddae3..ca615cc8cf 100644
--- a/Emby.Server.Implementations/Localization/Core/eo.json
+++ b/Emby.Server.Implementations/Localization/Core/eo.json
@@ -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"
}
diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json
index 12bcd793e3..11139d32a8 100644
--- a/Emby.Server.Implementations/Localization/Core/gl.json
+++ b/Emby.Server.Implementations/Localization/Core/gl.json
@@ -57,5 +57,27 @@
"DeviceOnlineWithName": "{0} conectouse",
"DeviceOfflineWithName": "{0} desconectouse",
"Default": "Por defecto",
- "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}"
+ "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}",
+ "TaskCleanLogs": "Limpar Carpeta de Rexistros",
+ "TaskCleanActivityLog": "Limpar Rexistro de Actividade",
+ "TasksChannelsCategory": "Canáis de Internet",
+ "TaskUpdatePlugins": "Actualizar Plugins",
+ "User": "Usuario",
+ "Undefined": "Sen definir",
+ "TvShows": "Programas de TV",
+ "System": "Sistema",
+ "Sync": "Sincronizar",
+ "SubtitleDownloadFailureFromForItem": "Fallou a descarga de subtítulos para {1} dende {0}",
+ "StartupEmbyServerIsLoading": "O Servidor Jellyfin está cargando. Por favor, reinténteo en breve.",
+ "Songs": "Cancións",
+ "Shows": "Programas",
+ "ServerNameNeedsToBeRestarted": "{0} precisa ser reiniciado",
+ "ScheduledTaskStartedWithName": "{0} comezou",
+ "ScheduledTaskFailedWithName": "{0} fallou",
+ "ProviderValue": "Provedor: {0}",
+ "PluginUpdatedWithName": "{0} foi actualizado",
+ "PluginUninstalledWithName": "{0} foi desinstalado",
+ "PluginInstalledWithName": "{0} foi instalado",
+ "Playlists": "Listas de reproducción",
+ "Photos": "Fotos"
}
diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json
index a321e35d03..829a29ad42 100644
--- a/Emby.Server.Implementations/Localization/Core/kk.json
+++ b/Emby.Server.Implementations/Localization/Core/kk.json
@@ -109,14 +109,14 @@
"TasksMaintenanceCategory": "Qyzmet körsetu",
"Undefined": "Anyqtalmağan",
"Forced": "Mäjbürlı",
- "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı Internetten ızdeidı.",
+ "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.",
"TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.",
"TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.",
"TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.",
"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."
}
diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json
index d4cb592efc..9920ef4d51 100644
--- a/Emby.Server.Implementations/Localization/Core/lt-LT.json
+++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json
@@ -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"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json
index 5ec8f1e88a..323dcced07 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-BR.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json
@@ -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",
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index 71dd2c7a30..5bf58baf8c 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -50,7 +50,7 @@
"HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ",
"HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ",
"HeaderContinueWatching": "ดูต่อ",
- "HeaderAlbumArtists": "อัลบั้มศิลปิน",
+ "HeaderAlbumArtists": "ศิลปินอัลบั้ม",
"Genres": "ประเภท",
"Folders": "โฟลเดอร์",
"Favorites": "รายการโปรด",
@@ -112,5 +112,6 @@
"System": "ระบบ",
"Sync": "ซิงค์",
"SubtitleDownloadFailureFromForItem": "ไม่สามารถดาวน์โหลดคำบรรยายจาก {0} สำหรับ {1} ได้",
- "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่"
+ "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่",
+ "Default": "ค่าเริ่มต้น"
}
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 3f9e221066..2fdc2b4d91 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -11,7 +11,6 @@ using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Localization
@@ -36,7 +35,7 @@ namespace Emby.Server.Implementations.Localization
private List _cultures;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
///
/// Initializes a new instance of the class.
diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
index c6e931448d..031b5d2e72 100644
--- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -15,7 +15,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.MediaEncoder
@@ -82,11 +81,6 @@ namespace Emby.Server.Implementations.MediaEncoder
return false;
}
- if (video.VideoType == VideoType.Dvd)
- {
- return false;
- }
-
if (video.IsShortcut)
{
return false;
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index 7bc9f0a7e2..3a8296455b 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -34,7 +35,7 @@ namespace Emby.Server.Implementations.Plugins
private readonly ILogger _logger;
private readonly IApplicationHost _appHost;
private readonly ServerConfiguration _config;
- private readonly IList _plugins;
+ private readonly List _plugins;
private readonly Version _minimumVersion;
private IHttpClientFactory? _httpClientFactory;
@@ -70,7 +71,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
};
@@ -94,7 +95,7 @@ namespace Emby.Server.Implementations.Plugins
///
/// Gets the Plugins.
///
- public IList Plugins => _plugins;
+ public IReadOnlyList Plugins => _plugins;
///
/// Returns all the assemblies.
@@ -368,7 +369,7 @@ namespace Emby.Server.Implementations.Plugins
}
///
- public async Task GenerateManifest(PackageInfo packageInfo, Version version, string path)
+ public async Task GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status)
{
if (packageInfo == null)
{
@@ -411,9 +412,9 @@ namespace Emby.Server.Implementations.Plugins
Overview = packageInfo.Overview,
Owner = packageInfo.Owner,
TargetAbi = versionInfo.TargetAbi ?? string.Empty,
- Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp),
+ Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture),
Version = versionInfo.Version,
- Status = PluginStatus.Active,
+ Status = status == PluginStatus.Disabled ? PluginStatus.Disabled : PluginStatus.Active, // Keep disabled state.
AutoUpdate = true,
ImagePath = imagePath
};
@@ -678,7 +679,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;
diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
index 7bed06de36..22739a0081 100644
--- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
+++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
-using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index b302303f88..9c0e927058 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -4,7 +4,6 @@ using System;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -69,7 +68,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
///
/// The options for the json Serializer.
///
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
///
/// Initializes a new instance of the class.
@@ -178,7 +177,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
lock (_lastExecutionResultSyncLock)
{
using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
- JsonSerializer.SerializeAsync(createStream, value, _jsonOptions);
+ using Utf8JsonWriter jsonStream = new Utf8JsonWriter(createStream);
+ JsonSerializer.Serialize(jsonStream, value, _jsonOptions);
}
}
}
@@ -578,7 +578,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
Directory.CreateDirectory(Path.GetDirectoryName(path));
using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
- JsonSerializer.SerializeAsync(createStream, triggers, _jsonOptions);
+ using Utf8JsonWriter jsonWriter = new Utf8JsonWriter(createStream);
+ JsonSerializer.Serialize(jsonWriter, triggers, _jsonOptions);
}
///
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index 649305fd56..2312c85d97 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
@@ -12,9 +12,9 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
-using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
index c384cf4bbe..57d294a408 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
@@ -5,8 +5,8 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Tasks;
namespace Emby.Server.Implementations.ScheduledTasks
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
index a69380cbb7..11a5fb79f4 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
@@ -9,7 +9,6 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
index e470adcf48..51b620404b 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
@@ -6,8 +6,8 @@ using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Library;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Tasks;
namespace Emby.Server.Implementations.ScheduledTasks
{
diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
index 1f68a9c810..60698e803d 100644
--- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
+++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -131,11 +131,11 @@ namespace Emby.Server.Implementations.Sorting
return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y));
}
- private static int GetSpecialCompareValue(Episode item)
+ private static long GetSpecialCompareValue(Episode item)
{
// First sort by season number
// Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough)
- var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000;
+ var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000L;
// Second sort order is if it airs after the season
if (item.AirsAfterSeasonNumber.HasValue)
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 839b62448b..d3f6fa34d8 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
@@ -11,7 +10,6 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.TV;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Series = MediaBrowser.Controller.Entities.TV.Series;
@@ -23,12 +21,14 @@ namespace Emby.Server.Implementations.TV
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
private readonly ILibraryManager _libraryManager;
+ private readonly IServerConfigurationManager _configurationManager;
- public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager)
+ public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager)
{
_userManager = userManager;
_userDataManager = userDataManager;
_libraryManager = libraryManager;
+ _configurationManager = configurationManager;
}
public QueryResult GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
@@ -200,13 +200,10 @@ namespace Emby.Server.Implementations.TV
ParentIndexNumberNotEquals = 0,
DtoOptions = new DtoOptions
{
- Fields = new ItemFields[]
- {
- ItemFields.SortName
- },
+ Fields = new[] { ItemFields.SortName },
EnableImages = false
}
- }).FirstOrDefault();
+ }).Cast().FirstOrDefault();
Func getEpisode = () =>
{
@@ -224,6 +221,43 @@ namespace Emby.Server.Implementations.TV
DtoOptions = dtoOptions
}).Cast().FirstOrDefault();
+ if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
+ {
+ var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ AncestorWithPresentationUniqueKey = null,
+ SeriesPresentationUniqueKey = seriesKey,
+ ParentIndexNumber = 0,
+ IncludeItemTypes = new[] { nameof(Episode) },
+ IsPlayed = false,
+ IsVirtualItem = false,
+ DtoOptions = dtoOptions
+ })
+ .Cast()
+ .Where(episode => episode.AirsBeforeSeasonNumber != null || episode.AirsAfterSeasonNumber != null)
+ .ToList();
+
+ if (lastWatchedEpisode != null)
+ {
+ // Last watched episode is added, because there could be specials that aired before the last watched episode
+ consideredEpisodes.Add(lastWatchedEpisode);
+ }
+
+ if (nextEpisode != null)
+ {
+ consideredEpisodes.Add(nextEpisode);
+ }
+
+ var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, new[] { (ItemSortBy.AiredEpisodeOrder, SortOrder.Ascending) })
+ .Cast();
+ if (lastWatchedEpisode != null)
+ {
+ sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => episode.Id != lastWatchedEpisode.Id).Skip(1);
+ }
+
+ nextEpisode = sortedConsideredEpisodes.FirstOrDefault();
+ }
+
if (nextEpisode != null)
{
var userData = _userDataManager.GetUserData(user, nextEpisode);
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index 4fd7ac0c18..db5265e79d 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -53,12 +53,7 @@ namespace Emby.Server.Implementations.Udp
if (!string.IsNullOrEmpty(localUrl))
{
- var response = new ServerDiscoveryInfo
- {
- Address = localUrl,
- Id = _appHost.SystemId,
- Name = _appHost.FriendlyName
- };
+ var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName);
try
{
@@ -79,7 +74,7 @@ namespace Emby.Server.Implementations.Udp
/// Starts the specified port.
///
/// The port.
- ///
+ /// The cancellation token to cancel operation.
public void Start(int port, CancellationToken cancellationToken)
{
_endpoint = new IPEndPoint(IPAddress.Any, port);
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 7af52ea652..653b1381b9 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -22,6 +22,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Events.Updates;
using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Updates;
using Microsoft.Extensions.Logging;
@@ -92,7 +93,7 @@ namespace Emby.Server.Implementations.Updates
_httpClientFactory = httpClientFactory;
_config = config;
_zipClient = zipClient;
- _jsonSerializerOptions = JsonDefaults.GetOptions();
+ _jsonSerializerOptions = JsonDefaults.Options;
_pluginManager = pluginManager;
}
@@ -194,7 +195,7 @@ namespace Emby.Server.Implementations.Updates
var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber);
if (plugin != null)
{
- await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path);
+ await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path, plugin.Manifest.Status).ConfigureAwait(false);
}
// Remove versions with a target ABI greater then the current application version.
@@ -500,7 +501,8 @@ namespace Emby.Server.Implementations.Updates
var plugins = _pluginManager.Plugins;
foreach (var plugin in plugins)
{
- if (plugin.Manifest?.AutoUpdate == false)
+ // Don't auto update when plugin marked not to, or when it's disabled.
+ if (plugin.Manifest?.AutoUpdate == false || plugin.Manifest?.Status == PluginStatus.Disabled)
{
continue;
}
@@ -515,7 +517,7 @@ namespace Emby.Server.Implementations.Updates
}
}
- private async Task PerformPackageInstallation(InstallationInfo package, CancellationToken cancellationToken)
+ private async Task PerformPackageInstallation(InstallationInfo package, PluginStatus status, CancellationToken cancellationToken)
{
var extension = Path.GetExtension(package.SourceUrl);
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
@@ -567,7 +569,7 @@ namespace Emby.Server.Implementations.Updates
stream.Position = 0;
_zipClient.ExtractAllFromZip(stream, targetDir, true);
- await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir);
+ await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false);
_pluginManager.ImportPluginFrom(targetDir);
}
@@ -576,7 +578,7 @@ namespace Emby.Server.Implementations.Updates
LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version))
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
- await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false);
+ await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false);
_logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version);
return plugin != null;
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index 616fe5b91f..a6e70e72d3 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
{
Id = itemId,
Container = container,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -168,22 +168,22 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
@@ -287,7 +287,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -309,7 +309,7 @@ namespace Jellyfin.Api.Controllers
{
Id = itemId,
Container = container,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -333,22 +333,22 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index e1c9f69f61..049a4bed7b 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -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;
///
/// Initializes a new instance of the class.
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index a2c2ecd666..445733c24d 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -95,9 +95,9 @@ namespace Jellyfin.Api.Controllers
return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin.Instance, i.Item1));
}
- private IEnumerable> GetPluginPages(LocalPlugin? plugin)
+ private IEnumerable> GetPluginPages(LocalPlugin plugin)
{
- if (plugin?.Instance is not IHasWebPages hasWebPages)
+ if (plugin.Instance is not IHasWebPages hasWebPages)
{
return Enumerable.Empty>();
}
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index e375645cf0..c4e75fe859 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -203,7 +203,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -218,14 +218,14 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext context,
+ [FromQuery] EncodingContext? context,
[FromQuery] Dictionary streamOptions,
[FromQuery] bool enableAdaptiveBitrateStreaming = true)
{
var streamingRequest = new HlsVideoRequestDto
{
Id = itemId,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -249,28 +249,28 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex,
- Context = context,
+ Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions,
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
};
@@ -370,7 +370,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -385,14 +385,14 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext context,
+ [FromQuery] EncodingContext? context,
[FromQuery] Dictionary streamOptions,
[FromQuery] bool enableAdaptiveBitrateStreaming = true)
{
var streamingRequest = new HlsAudioRequestDto
{
Id = itemId,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -416,28 +416,28 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex,
- Context = context,
+ Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions,
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
};
@@ -533,7 +533,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -548,14 +548,14 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext context,
+ [FromQuery] EncodingContext? context,
[FromQuery] Dictionary streamOptions)
{
var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new VideoRequestDto
{
Id = itemId,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -579,28 +579,28 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex,
- Context = context,
+ Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions
};
@@ -698,7 +698,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -713,14 +713,14 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext context,
+ [FromQuery] EncodingContext? context,
[FromQuery] Dictionary streamOptions)
{
var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new StreamingRequestDto
{
Id = itemId,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -744,28 +744,28 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex,
- Context = context,
+ Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions
};
@@ -868,7 +868,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -883,14 +883,14 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext context,
+ [FromQuery] EncodingContext? context,
[FromQuery] Dictionary streamOptions)
{
var streamingRequest = new VideoRequestDto
{
Id = itemId,
Container = container,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -914,28 +914,28 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex,
- Context = context,
+ Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions
};
@@ -1040,7 +1040,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -1055,14 +1055,14 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext context,
+ [FromQuery] EncodingContext? context,
[FromQuery] Dictionary streamOptions)
{
var streamingRequest = new StreamingRequestDto
{
Id = itemId,
Container = container,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -1086,28 +1086,28 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex,
- Context = context,
+ Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions
};
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index 25abe73ed0..473bdc523c 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -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);
}
@@ -96,7 +108,9 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Videos/ActiveEncodings")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult StopEncodingProcess([FromQuery] string deviceId, [FromQuery] string playSessionId)
+ public ActionResult StopEncodingProcess(
+ [FromQuery, Required] string deviceId,
+ [FromQuery, Required] string playSessionId)
{
_transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true);
return NoContent();
@@ -128,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;
diff --git a/Jellyfin.Api/Controllers/ImageByNameController.cs b/Jellyfin.Api/Controllers/ImageByNameController.cs
index 198dbc51fc..e1b8080984 100644
--- a/Jellyfin.Api/Controllers/ImageByNameController.cs
+++ b/Jellyfin.Api/Controllers/ImageByNameController.cs
@@ -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
/// A containing the image contents on success, or a if the image could not be found.
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);
}
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index a50d6e46bf..8f7500ac69 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -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);
@@ -392,7 +402,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid itemId,
[FromRoute, Required] ImageType imageType,
[FromRoute, Required] int imageIndex,
- [FromQuery] int newIndex)
+ [FromQuery, Required] int newIndex)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -480,6 +490,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Supply the cache tag from the item object to receive strong caching headers.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. The of the returned image.
@@ -509,8 +521,10 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
[FromQuery] string? tag,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] ImageFormat? format,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] double? percentPlayed,
@@ -539,7 +553,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -560,6 +575,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Supply the cache tag from the item object to receive strong caching headers.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. The of the returned image.
@@ -589,8 +606,10 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
[FromQuery] string? tag,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] ImageFormat? format,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] double? percentPlayed,
@@ -618,7 +637,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -638,6 +658,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Supply the cache tag from the item object to receive strong caching headers.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Determines the output format of the image - original,gif,jpg,png.
@@ -667,8 +689,10 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
[FromRoute, Required] string tag,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromRoute, Required] ImageFormat format,
[FromQuery] bool? addPlayedIndicator,
[FromRoute, Required] double percentPlayed,
@@ -697,7 +721,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -721,6 +746,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -741,7 +768,7 @@ namespace Jellyfin.Api.Controllers
public async Task GetArtistImage(
[FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType,
- [FromQuery] string tag,
+ [FromQuery] string? tag,
[FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
@@ -750,7 +777,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -776,7 +805,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -800,6 +830,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -820,7 +852,7 @@ namespace Jellyfin.Api.Controllers
public async Task GetGenreImage(
[FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType,
- [FromQuery] string tag,
+ [FromQuery] string? tag,
[FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
@@ -829,7 +861,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -855,7 +889,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -880,6 +915,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -900,7 +937,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType,
[FromRoute, Required] int imageIndex,
- [FromQuery] string tag,
+ [FromQuery] string? tag,
[FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
@@ -909,7 +946,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -934,7 +973,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -958,6 +998,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -978,7 +1020,7 @@ namespace Jellyfin.Api.Controllers
public async Task GetMusicGenreImage(
[FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType,
- [FromQuery] string tag,
+ [FromQuery] string? tag,
[FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
@@ -987,7 +1029,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -1013,7 +1057,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -1038,6 +1083,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -1058,7 +1105,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType,
[FromRoute, Required] int imageIndex,
- [FromQuery] string tag,
+ [FromQuery] string? tag,
[FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
@@ -1067,7 +1114,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -1092,7 +1141,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -1116,6 +1166,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -1136,7 +1188,7 @@ namespace Jellyfin.Api.Controllers
public async Task GetPersonImage(
[FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType,
- [FromQuery] string tag,
+ [FromQuery] string? tag,
[FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
@@ -1145,7 +1197,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -1171,7 +1225,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -1196,6 +1251,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -1216,7 +1273,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType,
[FromRoute, Required] int imageIndex,
- [FromQuery] string tag,
+ [FromQuery] string? tag,
[FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
@@ -1225,7 +1282,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -1250,7 +1309,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -1274,6 +1334,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -1303,7 +1365,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -1329,7 +1393,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -1354,6 +1419,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -1383,7 +1450,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -1408,7 +1477,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -1432,6 +1502,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -1461,7 +1533,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -1469,7 +1543,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? imageIndex)
{
var user = _userManager.GetUserById(userId);
- if (user == null)
+ if (user?.ProfileImage == null)
{
return NotFound();
}
@@ -1504,7 +1578,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -1530,6 +1605,8 @@ namespace Jellyfin.Api.Controllers
/// The fixed image width to return.
/// The fixed image height to return.
/// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.
+ /// Width of box to fill.
+ /// Height of box to fill.
/// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.
/// Optional. Add a played indicator.
/// Optional. Blur image.
@@ -1559,7 +1636,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromQuery] bool? cropWhitespace,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
@@ -1601,7 +1680,8 @@ namespace Jellyfin.Api.Controllers
width,
height,
quality,
- cropWhitespace,
+ fillWidth,
+ fillHeight,
addPlayedIndicator,
blur,
backgroundColor,
@@ -1685,7 +1765,8 @@ namespace Jellyfin.Api.Controllers
int? width,
int? height,
int? quality,
- bool? cropWhitespace,
+ int? fillWidth,
+ int? fillHeight,
bool? addPlayedIndicator,
int? blur,
string? backgroundColor,
@@ -1727,8 +1808,6 @@ namespace Jellyfin.Api.Controllers
}
}
- cropWhitespace ??= imageType == ImageType.Logo || imageType == ImageType.Art;
-
var outputFormats = GetOutputFormats(format);
TimeSpan? cacheDuration = null;
@@ -1748,11 +1827,13 @@ namespace Jellyfin.Api.Controllers
item,
itemId,
imageIndex,
+ width,
height,
- maxHeight,
maxWidth,
+ maxHeight,
+ fillWidth,
+ fillHeight,
quality,
- width,
addPlayedIndicator,
percentPlayed,
unplayedCount,
@@ -1760,7 +1841,6 @@ namespace Jellyfin.Api.Controllers
backgroundColor,
foregroundLayer,
imageInfo,
- cropWhitespace.Value,
outputFormats,
cacheDuration,
responseHeaders,
@@ -1779,17 +1859,15 @@ namespace Jellyfin.Api.Controllers
private ImageFormat[] GetClientSupportedFormats()
{
- var acceptTypes = Request.Headers[HeaderNames.Accept];
- var supportedFormats = new List();
- if (acceptTypes.Count > 0)
+ var supportedFormats = Request.Headers.GetCommaSeparatedValues(HeaderNames.Accept);
+ for (var i = 0; i < supportedFormats.Length; i++)
{
- foreach (var type in acceptTypes)
+ // Remove charsets etc. (anything after semi-colon)
+ var type = supportedFormats[i];
+ int index = type.IndexOf(';', StringComparison.Ordinal);
+ if (index != -1)
{
- int index = type.IndexOf(';', StringComparison.Ordinal);
- if (index != -1)
- {
- supportedFormats.Add(type.Substring(0, index));
- }
+ supportedFormats[i] = type.Substring(0, index);
}
}
@@ -1847,11 +1925,13 @@ namespace Jellyfin.Api.Controllers
BaseItem? item,
Guid itemId,
int? index,
+ int? width,
int? height,
- int? maxHeight,
int? maxWidth,
+ int? maxHeight,
+ int? fillWidth,
+ int? fillHeight,
int? quality,
- int? width,
bool? addPlayedIndicator,
double? percentPlayed,
int? unplayedCount,
@@ -1859,7 +1939,6 @@ namespace Jellyfin.Api.Controllers
string? backgroundColor,
string? foregroundLayer,
ItemImageInfo imageInfo,
- bool cropWhitespace,
IReadOnlyCollection supportedFormats,
TimeSpan? cacheDuration,
IDictionary headers,
@@ -1872,7 +1951,6 @@ namespace Jellyfin.Api.Controllers
var options = new ImageProcessingOptions
{
- CropWhiteSpace = cropWhitespace,
Height = height,
ImageIndex = index ?? 0,
Image = imageInfo,
@@ -1880,6 +1958,8 @@ namespace Jellyfin.Api.Controllers
ItemId = itemId,
MaxHeight = maxHeight,
MaxWidth = maxWidth,
+ FillHeight = fillHeight,
+ FillWidth = fillWidth,
Quality = quality ?? 100,
Width = width,
AddPlayedIndicator = addPlayedIndicator ?? false,
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index f061755c30..f232dffaa5 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
}
///
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given album.
///
/// The item id.
/// Optional. Filter by user id, and attach user data.
@@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
}
///
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given playlist.
///
/// The item id.
/// Optional. Filter by user id, and attach user data.
@@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers
}
///
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given genre.
///
/// The genre name.
/// Optional. Filter by user id, and attach user data.
@@ -172,7 +172,7 @@ namespace Jellyfin.Api.Controllers
/// A with the playlist items.
[HttpGet("MusicGenres/{name}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult> GetInstantMixFromMusicGenre(
+ public ActionResult> GetInstantMixFromMusicGenreByName(
[FromRoute, Required] string name,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
@@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
}
///
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given artist.
///
/// The item id.
/// Optional. Filter by user id, and attach user data.
@@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers
}
///
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given genre.
///
/// The item id.
/// Optional. Filter by user id, and attach user data.
@@ -243,7 +243,7 @@ namespace Jellyfin.Api.Controllers
/// A with the playlist items.
[HttpGet("MusicGenres/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult> GetInstantMixFromMusicGenres(
+ public ActionResult> GetInstantMixFromMusicGenreById(
[FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
@@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
}
///
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given item.
///
/// The item id.
/// Optional. Filter by user id, and attach user data.
@@ -300,6 +300,80 @@ namespace Jellyfin.Api.Controllers
return GetResult(items, user, limit, dtoOptions);
}
+ ///
+ /// Creates an instant playlist based on a given artist.
+ ///
+ /// The item id.
+ /// Optional. Filter by user id, and attach user data.
+ /// Optional. The maximum number of records to return.
+ /// Optional. Specify additional fields of information to return in the output.
+ /// Optional. Include image information in output.
+ /// Optional. Include user data.
+ /// Optional. The max number of images to return, per image type.
+ /// Optional. The image types to include in the output.
+ /// Instant playlist returned.
+ /// A with the playlist items.
+ [HttpGet("Artists/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Obsolete("Use GetInstantMixFromArtists")]
+ public ActionResult> 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);
+ }
+
+ ///
+ /// Creates an instant playlist based on a given genre.
+ ///
+ /// The item id.
+ /// Optional. Filter by user id, and attach user data.
+ /// Optional. The maximum number of records to return.
+ /// Optional. Specify additional fields of information to return in the output.
+ /// Optional. Include image information in output.
+ /// Optional. Include user data.
+ /// Optional. The max number of images to return, per image type.
+ /// Optional. The image types to include in the output.
+ /// Instant playlist returned.
+ /// A with the playlist items.
+ [HttpGet("MusicGenres/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Obsolete("Use GetInstantMixFromMusicGenres instead")]
+ public ActionResult> 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 GetResult(List items, User? user, int? limit, DtoOptions dtoOptions)
{
var list = items;
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index dfc68ffcec..dabd4deb79 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -344,11 +344,12 @@ namespace Jellyfin.Api.Controllers
Directory.CreateDirectory(directory);
using (var stream = result.Content)
{
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
await using var fileStream = new FileStream(
fullCachePath,
FileMode.Create,
FileAccess.Write,
- FileShare.Read,
+ FileShare.None,
IODefaults.FileStreamBufferSize,
true);
diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs
index 9e1a398538..a9f4a5a58c 100644
--- a/Jellyfin.Api/Controllers/ItemUpdateController.cs
+++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs
@@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}/ContentType")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string contentType)
+ public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 2c9760f6d0..74cf3b1624 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -246,8 +246,13 @@ namespace Jellyfin.Api.Controllers
folder = _libraryManager.GetUserRootFolder();
}
- if (folder is IHasCollectionType hasCollectionType
- && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
+ string? collectionType = null;
+ if (folder is IHasCollectionType hasCollectionType)
+ {
+ collectionType = hasCollectionType.CollectionType;
+ }
+
+ if (string.Equals(collectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
{
recursive = true;
includeItemTypes = new[] { BaseItemKind.Playlist };
@@ -270,10 +275,11 @@ namespace Jellyfin.Api.Controllers
}
}
- if (!(item is UserRootFolder)
+ if (item is not UserRootFolder
&& !isInEnabledFolder
&& !user.HasPermission(PermissionKind.EnableAllFolders)
- && !user.HasPermission(PermissionKind.EnableAllChannels))
+ && !user.HasPermission(PermissionKind.EnableAllChannels)
+ && !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index db4aa96681..1d4bbe61e8 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -114,7 +114,7 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path));
+ return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true);
}
///
@@ -303,7 +303,7 @@ namespace Jellyfin.Api.Controllers
///
/// Library scan started.
/// A .
- [HttpGet("Library/Refresh")]
+ [HttpPost("Library/Refresh")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task RefreshLibrary()
@@ -590,15 +590,15 @@ namespace Jellyfin.Api.Controllers
///
/// Reports that new movies have been added by an external source.
///
- /// A list of updated media paths.
+ /// The update paths.
/// Report success.
/// A .
[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);
}
@@ -666,7 +666,7 @@ namespace Jellyfin.Api.Controllers
}
// TODO determine non-ASCII validity.
- return PhysicalFile(path, MimeTypes.GetMimeType(path), filename);
+ return PhysicalFile(path, MimeTypes.GetMimeType(path), filename, true);
}
///
@@ -777,7 +777,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetLibraryOptionsInfo(
[FromQuery] string? libraryContentType,
- [FromQuery] bool isNewLibrary)
+ [FromQuery] bool isNewLibrary = false)
{
var result = new LibraryOptionsResultDto();
diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs
index 328efea26e..be9127dd39 100644
--- a/Jellyfin.Api/Controllers/LibraryStructureController.cs
+++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs
@@ -241,23 +241,20 @@ namespace Jellyfin.Api.Controllers
///
/// Updates a media path.
///
- /// The name of the library.
- /// The path info.
+ /// The name of the library and path infos.
/// A .
/// Media path updated.
/// The name of the library may not be empty.
[HttpPost("Paths/Update")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult UpdateMediaPath(
- [FromQuery] string? name,
- [FromBody] MediaPathInfo? pathInfo)
+ public ActionResult UpdateMediaPath([FromBody, Required] UpdateMediaPathRequestDto mediaPathRequestDto)
{
- if (string.IsNullOrWhiteSpace(name))
+ if (string.IsNullOrWhiteSpace(mediaPathRequestDto.Name))
{
- throw new ArgumentNullException(nameof(name));
+ throw new ArgumentNullException(nameof(mediaPathRequestDto), "Name must not be null or empty");
}
- _libraryManager.UpdateMediaPath(name, pathInfo);
+ _libraryManager.UpdateMediaPath(mediaPathRequestDto.Name, mediaPathRequestDto.PathInfo);
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs
index 0ceda6815c..420630cdf4 100644
--- a/Jellyfin.Api/Controllers/NotificationsController.cs
+++ b/Jellyfin.Api/Controllers/NotificationsController.cs
@@ -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
///
/// Sends a notification to all admins.
///
- /// The URL of the notification.
- /// The level of the notification.
- /// The name of the notification.
- /// The description of the notification.
+ /// The notification request.
/// Notification sent.
/// A .
[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();
}
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index ec7b84ff60..f256c8c25c 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -152,7 +152,7 @@ namespace Jellyfin.Api.Controllers
/// A .
[HttpPost("Sessions/Playing/Ping")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult PingPlaybackSession([FromQuery] string playSessionId)
+ public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId)
{
_transcodingJobHelper.PingTranscodingJob(playSessionId, null);
return NoContent();
@@ -202,9 +202,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? mediaSourceId,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] PlayMethod playMethod,
+ [FromQuery] PlayMethod? playMethod,
[FromQuery] string? liveStreamId,
- [FromQuery] string playSessionId,
+ [FromQuery] string? playSessionId,
[FromQuery] bool canSeek = false)
{
var playbackStartInfo = new PlaybackStartInfo
@@ -214,7 +214,7 @@ namespace Jellyfin.Api.Controllers
MediaSourceId = mediaSourceId,
AudioStreamIndex = audioStreamIndex,
SubtitleStreamIndex = subtitleStreamIndex,
- PlayMethod = playMethod,
+ PlayMethod = playMethod ?? PlayMethod.Transcode,
PlaySessionId = playSessionId,
LiveStreamId = liveStreamId
};
@@ -254,10 +254,10 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? audioStreamIndex,
[FromQuery] int? subtitleStreamIndex,
[FromQuery] int? volumeLevel,
- [FromQuery] PlayMethod playMethod,
+ [FromQuery] PlayMethod? playMethod,
[FromQuery] string? liveStreamId,
- [FromQuery] string playSessionId,
- [FromQuery] RepeatMode repeatMode,
+ [FromQuery] string? playSessionId,
+ [FromQuery] RepeatMode? repeatMode,
[FromQuery] bool isPaused = false,
[FromQuery] bool isMuted = false)
{
@@ -271,10 +271,10 @@ namespace Jellyfin.Api.Controllers
AudioStreamIndex = audioStreamIndex,
SubtitleStreamIndex = subtitleStreamIndex,
VolumeLevel = volumeLevel,
- PlayMethod = playMethod,
+ PlayMethod = playMethod ?? PlayMethod.Transcode,
PlaySessionId = playSessionId,
LiveStreamId = liveStreamId,
- RepeatMode = repeatMode
+ RepeatMode = repeatMode ?? RepeatMode.RepeatNone
};
playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId);
@@ -352,7 +352,7 @@ namespace Jellyfin.Api.Controllers
return _userDataRepository.GetUserDataDto(item, user);
}
- private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId)
+ private PlayMethod ValidatePlayMethod(PlayMethod method, string? playSessionId)
{
if (method == PlayMethod.Transcode)
{
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index a5aa9bfcae..adec86a102 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -12,7 +12,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Plugins;
using Microsoft.AspNetCore.Authorization;
@@ -45,7 +44,7 @@ namespace Jellyfin.Api.Controllers
{
_installationManager = installationManager;
_pluginManager = pluginManager;
- _serializerOptions = JsonDefaults.GetOptions();
+ _serializerOptions = JsonDefaults.Options;
_config = config;
}
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index 5284888d82..e226adc646 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -259,7 +259,8 @@ namespace Jellyfin.Api.Controllers
var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
Directory.CreateDirectory(fullCacheDirectory);
- await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true);
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index e2269a2ce2..7bd0b6918f 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -153,6 +153,10 @@ namespace Jellyfin.Api.Controllers
/// The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.
/// The ids of the items to play, comma delimited.
/// The starting position of the first item.
+ /// Optional. The media source id.
+ /// Optional. The index of the audio stream to play.
+ /// Optional. The index of the subtitle stream to play.
+ /// Optional. The start index.
/// Instruction sent to session.
/// A .
[HttpPost("Sessions/{sessionId}/Playing")]
@@ -162,13 +166,21 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string sessionId,
[FromQuery, Required] PlayCommand playCommand,
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds,
- [FromQuery] long? startPositionTicks)
+ [FromQuery] long? startPositionTicks,
+ [FromQuery] string? mediaSourceId,
+ [FromQuery] int? audioStreamIndex,
+ [FromQuery] int? subtitleStreamIndex,
+ [FromQuery] int? startIndex)
{
var playRequest = new PlayRequest
{
ItemIds = itemIds,
StartPositionTicks = startPositionTicks,
- PlayCommand = playCommand
+ PlayCommand = playCommand,
+ MediaSourceId = mediaSourceId,
+ AudioStreamIndex = audioStreamIndex,
+ SubtitleStreamIndex = subtitleStreamIndex,
+ StartIndex = startIndex
};
_sessionManager.SendPlayCommand(
@@ -301,9 +313,7 @@ namespace Jellyfin.Api.Controllers
/// Issues a command to a client to display a message to the user.
///
/// The session id.
- /// The message test.
- /// The message header.
- /// The message timeout. If omitted the user will have to confirm viewing the message.
+ /// The object containing Header, Message Text, and TimeoutMs.
/// Message sent.
/// A .
[HttpPost("Sessions/{sessionId}/Message")]
@@ -311,16 +321,12 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendMessageCommand(
[FromRoute, Required] string sessionId,
- [FromQuery, Required] string text,
- [FromQuery] string? header,
- [FromQuery] long? timeoutMs)
+ [FromBody, Required] MessageCommand command)
{
- var command = new MessageCommand
+ if (string.IsNullOrWhiteSpace(command.Header))
{
- Header = string.IsNullOrEmpty(header) ? "Message from Server" : header,
- TimeoutMs = timeoutMs,
- Text = text
- };
+ command.Header = "Message from Server";
+ }
_sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, command, CancellationToken.None);
diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs
index d9cb34557b..a01a617fc0 100644
--- a/Jellyfin.Api/Controllers/StartupController.cs
+++ b/Jellyfin.Api/Controllers/StartupController.cs
@@ -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);
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index 16a47f2d88..1669a659dc 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -182,6 +182,10 @@ namespace Jellyfin.Api.Controllers
///
/// Gets subtitles in a specified format.
///
+ /// The (route) item id.
+ /// The (route) media source id.
+ /// The (route) subtitle stream index.
+ /// The (route) format of the returned subtitle.
/// The item id.
/// The media source id.
/// The subtitle stream index.
@@ -189,22 +193,32 @@ namespace Jellyfin.Api.Controllers
/// Optional. The end position of the subtitle in ticks.
/// Optional. Whether to copy the timestamps.
/// Optional. Whether to add a VTT time map.
- /// Optional. The start position of the subtitle in ticks.
+ /// The start position of the subtitle in ticks.
/// File returned.
/// A with the subtitle file.
- [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")]
+ [HttpGet("Videos/{routeItemId}/routeMediaSourceId/Subtitles/{routeIndex}/Stream.{routeFormat}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile("text/*")]
public async Task GetSubtitle(
- [FromRoute, Required] Guid itemId,
- [FromRoute, Required] string mediaSourceId,
- [FromRoute, Required] int index,
- [FromRoute, Required] string format,
+ [FromRoute, Required] Guid routeItemId,
+ [FromRoute, Required] string routeMediaSourceId,
+ [FromRoute, Required] int routeIndex,
+ [FromRoute, Required] string routeFormat,
+ [FromQuery, ParameterObsolete] Guid? itemId,
+ [FromQuery, ParameterObsolete] string? mediaSourceId,
+ [FromQuery, ParameterObsolete] int? index,
+ [FromQuery, ParameterObsolete] string? format,
[FromQuery] long? endPositionTicks,
[FromQuery] bool copyTimestamps = false,
[FromQuery] bool addVttTimeMap = false,
[FromQuery] long startPositionTicks = 0)
{
+ // Set parameters to route value if not provided via query.
+ itemId ??= routeItemId;
+ mediaSourceId ??= routeMediaSourceId;
+ index ??= routeIndex;
+ format ??= routeFormat;
+
if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
{
format = "json";
@@ -212,9 +226,9 @@ namespace Jellyfin.Api.Controllers
if (string.IsNullOrEmpty(format))
{
- var item = (Video)_libraryManager.GetItemById(itemId);
+ var item = (Video)_libraryManager.GetItemById(itemId.Value);
- var idString = itemId.ToString("N", CultureInfo.InvariantCulture);
+ var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture);
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false)
.First(i => string.Equals(i.Id, mediaSourceId ?? idString, StringComparison.Ordinal));
@@ -226,7 +240,7 @@ namespace Jellyfin.Api.Controllers
if (string.Equals(format, "vtt", StringComparison.OrdinalIgnoreCase) && addVttTimeMap)
{
- await using Stream stream = await EncodeSubtitles(itemId, mediaSourceId, index, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false);
+ await using Stream stream = await EncodeSubtitles(itemId.Value, mediaSourceId, index.Value, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false);
using var reader = new StreamReader(stream);
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
@@ -238,9 +252,9 @@ namespace Jellyfin.Api.Controllers
return File(
await EncodeSubtitles(
- itemId,
+ itemId.Value,
mediaSourceId,
- index,
+ index.Value,
format,
startPositionTicks,
endPositionTicks,
@@ -251,30 +265,44 @@ namespace Jellyfin.Api.Controllers
///
/// Gets subtitles in a specified format.
///
+ /// The (route) item id.
+ /// The (route) media source id.
+ /// The (route) subtitle stream index.
+ /// The (route) start position of the subtitle in ticks.
+ /// The (route) format of the returned subtitle.
/// The item id.
/// The media source id.
/// The subtitle stream index.
- /// Optional. The start position of the subtitle in ticks.
+ /// The start position of the subtitle in ticks.
/// The format of the returned subtitle.
/// Optional. The end position of the subtitle in ticks.
/// Optional. Whether to copy the timestamps.
/// Optional. Whether to add a VTT time map.
/// File returned.
/// A with the subtitle file.
- [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks}/Stream.{format}")]
+ [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/{routeStartPositionTicks}/Stream.{routeFormat}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile("text/*")]
public Task GetSubtitleWithTicks(
- [FromRoute, Required] Guid itemId,
- [FromRoute, Required] string mediaSourceId,
- [FromRoute, Required] int index,
- [FromRoute, Required] long startPositionTicks,
- [FromRoute, Required] string format,
+ [FromRoute, Required] Guid routeItemId,
+ [FromRoute, Required] string routeMediaSourceId,
+ [FromRoute, Required] int routeIndex,
+ [FromRoute, Required] long routeStartPositionTicks,
+ [FromRoute, Required] string routeFormat,
+ [FromQuery, ParameterObsolete] Guid? itemId,
+ [FromQuery, ParameterObsolete] string? mediaSourceId,
+ [FromQuery, ParameterObsolete] int? index,
+ [FromQuery, ParameterObsolete] long? startPositionTicks,
+ [FromQuery, ParameterObsolete] string? format,
[FromQuery] long? endPositionTicks,
[FromQuery] bool copyTimestamps = false,
[FromQuery] bool addVttTimeMap = false)
{
return GetSubtitle(
+ routeItemId,
+ routeMediaSourceId,
+ routeIndex,
+ routeFormat,
itemId,
mediaSourceId,
index,
@@ -282,7 +310,7 @@ namespace Jellyfin.Api.Controllers
endPositionTicks,
copyTimestamps,
addVttTimeMap,
- startPositionTicks);
+ startPositionTicks ?? routeStartPositionTicks);
}
///
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 5aa033ccf4..dcdd8b3677 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? maxAudioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] bool? enableRemoteMedia,
- [FromQuery] bool breakOnNonKeyFrames,
+ [FromQuery] bool breakOnNonKeyFrames = false,
[FromQuery] bool enableRedirection = true)
{
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
@@ -219,10 +219,10 @@ namespace Jellyfin.Api.Controllers
AudioBitRate = audioBitRate ?? maxStreamingBitrate,
StartTimeTicks = startTimeTicks,
SubtitleMethod = SubtitleDeliveryMethod.Hls,
- RequireAvc = true,
- DeInterlace = true,
- RequireNonAnamorphic = true,
- EnableMpegtsM2TsMode = true,
+ RequireAvc = false,
+ DeInterlace = false,
+ RequireNonAnamorphic = false,
+ EnableMpegtsM2TsMode = false,
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
Context = EncodingContext.Static,
StreamOptions = new Dictionary(),
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 43ee309b76..3c0d2aca16 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -21,6 +21,7 @@ using MediaBrowser.Model.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Controllers
{
@@ -36,6 +37,7 @@ namespace Jellyfin.Api.Controllers
private readonly IDeviceManager _deviceManager;
private readonly IAuthorizationContext _authContext;
private readonly IServerConfigurationManager _config;
+ private readonly ILogger _logger;
///
/// Initializes a new instance of the class.
@@ -46,13 +48,15 @@ namespace Jellyfin.Api.Controllers
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
+ /// Instance of the interface.
public UserController(
IUserManager userManager,
ISessionManager sessionManager,
INetworkManager networkManager,
IDeviceManager deviceManager,
IAuthorizationContext authContext,
- IServerConfigurationManager config)
+ IServerConfigurationManager config,
+ ILogger logger)
{
_userManager = userManager;
_sessionManager = sessionManager;
@@ -60,6 +64,7 @@ namespace Jellyfin.Api.Controllers
_deviceManager = deviceManager;
_authContext = authContext;
_config = config;
+ _logger = logger;
}
///
@@ -118,7 +123,7 @@ namespace Jellyfin.Api.Controllers
return NotFound("User not found");
}
- var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIp());
+ var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIp().ToString());
return result;
}
@@ -204,7 +209,7 @@ namespace Jellyfin.Api.Controllers
DeviceName = auth.Device,
Password = request.Pw,
PasswordSha1 = request.Password,
- RemoteEndPoint = HttpContext.GetNormalizedRemoteIp(),
+ RemoteEndPoint = HttpContext.GetNormalizedRemoteIp().ToString(),
Username = request.Username
}).ConfigureAwait(false);
@@ -291,7 +296,7 @@ namespace Jellyfin.Api.Controllers
user.Username,
request.CurrentPw,
request.CurrentPw,
- HttpContext.GetNormalizedRemoteIp(),
+ HttpContext.GetNormalizedRemoteIp().ToString(),
false).ConfigureAwait(false);
if (success == null)
@@ -483,7 +488,7 @@ namespace Jellyfin.Api.Controllers
await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false);
}
- var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIp());
+ var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIp().ToString());
return result;
}
@@ -498,8 +503,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPasswordRequest)
{
+ var ip = HttpContext.GetNormalizedRemoteIp();
var isLocal = HttpContext.IsLocal()
- || _networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp());
+ || _networkManager.IsInLocalNetwork(ip);
+
+ if (isLocal)
+ {
+ _logger.LogWarning("Password reset proccess initiated from outside the local network with IP: {IP}", ip);
+ }
var result = await _userManager.StartForgotPasswordProcess(forgotPasswordRequest.EnteredUsername, isLocal).ConfigureAwait(false);
@@ -581,7 +592,7 @@ namespace Jellyfin.Api.Controllers
var result = users
.OrderBy(u => u.Username)
- .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIp()));
+ .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIp().ToString()));
return result;
}
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index ba51aa43e2..e95410d024 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -198,7 +198,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -213,7 +213,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext context,
+ [FromQuery] EncodingContext? context,
[FromQuery] Dictionary streamOptions,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
@@ -223,7 +223,7 @@ namespace Jellyfin.Api.Controllers
{
Id = itemId,
Container = container,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -247,28 +247,28 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex,
- Context = context,
+ Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions,
MaxHeight = maxHeight,
MaxWidth = maxWidth,
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 44dc639523..699ca53278 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -217,9 +217,7 @@ namespace Jellyfin.Api.Controllers
return BadRequest("Please supply at least two videos to merge.");
}
- var videosWithVersions = items.Where(i => i.MediaSourceCount > 1).ToList();
-
- var primaryVersion = videosWithVersions.FirstOrDefault();
+ var primaryVersion = items.FirstOrDefault(i => i.MediaSourceCount > 1 && string.IsNullOrEmpty(i.PrimaryVersionId));
if (primaryVersion == null)
{
primaryVersion = items
@@ -364,7 +362,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -379,7 +377,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext context,
+ [FromQuery] EncodingContext? context,
[FromQuery] Dictionary streamOptions)
{
var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
@@ -388,7 +386,7 @@ namespace Jellyfin.Api.Controllers
{
Id = itemId,
Container = container,
- Static = @static ?? true,
+ Static = @static ?? false,
Params = @params,
Tag = tag,
DeviceProfileId = deviceProfileId,
@@ -412,28 +410,28 @@ namespace Jellyfin.Api.Controllers
Level = level,
Framerate = framerate,
MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? true,
+ CopyTimestamps = copyTimestamps ?? false,
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? true,
- DeInterlace = deInterlace ?? true,
- RequireNonAnamorphic = requireNonAnamorphic ?? true,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
CpuCoreLimit = cpuCoreLimit,
LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
VideoCodec = videoCodec,
SubtitleCodec = subtitleCodec,
TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex,
- Context = context,
+ Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions
};
@@ -620,7 +618,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod subtitleMethod,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc,
@@ -635,7 +633,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext context,
+ [FromQuery] EncodingContext? context,
[FromQuery] Dictionary streamOptions)
{
return GetVideoStream(
diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs
index e0c744325f..06173315aa 100644
--- a/Jellyfin.Api/Extensions/DtoExtensions.cs
+++ b/Jellyfin.Api/Extensions/DtoExtensions.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs
index 21ec2d32f9..9c35d1ec10 100644
--- a/Jellyfin.Api/Helpers/AudioHelper.cs
+++ b/Jellyfin.Api/Helpers/AudioHelper.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Net.Http;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos;
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index 16380f0bba..1bb504ad12 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
-using System.Net.Mime;
using System.Security.Claims;
using System.Text;
using System.Threading;
@@ -434,7 +433,7 @@ namespace Jellyfin.Api.Helpers
}
}
- private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream, bool enableAdaptiveBitrateStreaming, string ipAddress)
+ private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream, bool enableAdaptiveBitrateStreaming, IPAddress ipAddress)
{
// Within the local network this will likely do more harm than good.
if (_networkManager.IsInLocalNetwork(ipAddress))
diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
index 89d36ab09f..b0fd59e5e3 100644
--- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
+++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
@@ -46,7 +46,8 @@ namespace Jellyfin.Api.Helpers
if (isHeadRequest)
{
- return new FileContentResult(Array.Empty(), contentType);
+ httpContext.Response.Headers[HeaderNames.ContentType] = contentType;
+ return new OkResult();
}
return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
@@ -68,10 +69,10 @@ namespace Jellyfin.Api.Helpers
{
httpContext.Response.ContentType = contentType;
- // if the request is a head request, return a NoContent result with the same headers as it would with a GET request
+ // if the request is a head request, return an OkResult (200) with the same headers as it would with a GET request
if (isHeadRequest)
{
- return new NoContentResult();
+ return new OkResult();
}
return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
@@ -107,7 +108,8 @@ namespace Jellyfin.Api.Helpers
// Headers only
if (isHeadRequest)
{
- return new FileContentResult(Array.Empty(), contentType);
+ httpContext.Response.Headers[HeaderNames.ContentType] = contentType;
+ return new OkResult();
}
var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath);
diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
index ce6740fc99..295cfaf089 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
+using System.Net;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -179,7 +180,7 @@ namespace Jellyfin.Api.Helpers
bool enableTranscoding,
bool allowVideoStreamCopy,
bool allowAudioStreamCopy,
- string ipAddress)
+ IPAddress ipAddress)
{
var streamBuilder = new StreamBuilder(_mediaEncoder, _logger);
@@ -282,6 +283,7 @@ namespace Jellyfin.Api.Helpers
if (streamInfo != null)
{
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
}
}
}
@@ -307,7 +309,7 @@ namespace Jellyfin.Api.Helpers
{
if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
&& !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
- && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
+ && user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
{
options.ForceDirectStream = true;
}
@@ -326,6 +328,7 @@ namespace Jellyfin.Api.Helpers
if (streamInfo != null)
{
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
}
}
}
@@ -353,6 +356,7 @@ namespace Jellyfin.Api.Helpers
// Do this after the above so that StartPositionTicks is set
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
}
}
else
@@ -390,6 +394,7 @@ namespace Jellyfin.Api.Helpers
// Do this after the above so that StartPositionTicks is set
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
}
}
}
@@ -551,7 +556,7 @@ namespace Jellyfin.Api.Helpers
}
}
- private int? GetMaxBitrate(int? clientMaxBitrate, User user, string ipAddress)
+ private int? GetMaxBitrate(int? clientMaxBitrate, User user, IPAddress ipAddress)
{
var maxBitrate = clientMaxBitrate;
var remoteClientMaxBitrate = user.RemoteClientBitrateLimit ?? 0;
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index 94856e03ed..56585aeab5 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -84,7 +84,7 @@ namespace Jellyfin.Api.Helpers
authorization.Version,
authorization.DeviceId,
authorization.Device,
- request.HttpContext.GetNormalizedRemoteIp(),
+ request.HttpContext.GetNormalizedRemoteIp().ToString(),
user);
if (session == null)
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index d20a02cf5d..e2306aa272 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -508,17 +508,15 @@ namespace Jellyfin.Api.Helpers
private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static)
{
- var headers = request.Headers;
-
if (!string.IsNullOrWhiteSpace(deviceProfileId))
{
state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId);
- }
- else if (!string.IsNullOrWhiteSpace(deviceProfileId))
- {
- var caps = deviceManager.GetCapabilities(deviceProfileId);
- state.DeviceProfile = caps == null ? dlnaManager.GetProfile(headers) : caps.DeviceProfile;
+ if (state.DeviceProfile == null)
+ {
+ var caps = deviceManager.GetCapabilities(deviceProfileId);
+ state.DeviceProfile = caps == null ? dlnaManager.GetProfile(request.Headers) : caps.DeviceProfile;
+ }
}
var profile = state.DeviceProfile;
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 67d0a3b5ab..d6dc5a2dc3 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -15,7 +15,7 @@
-
+
@@ -28,7 +28,6 @@
-
diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs
index 991dbfc502..f936388980 100644
--- a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs
+++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs
@@ -1,4 +1,7 @@
-namespace Jellyfin.Api.Models.LibraryDtos
+using System;
+using System.Collections.Generic;
+
+namespace Jellyfin.Api.Models.LibraryDtos
{
///
/// Media Update Info Dto.
@@ -6,14 +9,8 @@
public class MediaUpdateInfoDto
{
///
- /// Gets or sets media path.
- ///
- public string? Path { get; set; }
-
- ///
- /// Gets or sets media update type.
- /// Created, Modified, Deleted.
+ /// Gets or sets the list of updates.
///
- public string? UpdateType { get; set; }
+ public IReadOnlyList Updates { get; set; } = Array.Empty();
}
}
diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs
new file mode 100644
index 0000000000..852315b92d
--- /dev/null
+++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs
@@ -0,0 +1,19 @@
+namespace Jellyfin.Api.Models.LibraryDtos
+{
+ ///
+ /// The media update info path.
+ ///
+ public class MediaUpdateInfoPathDto
+ {
+ ///
+ /// Gets or sets media path.
+ ///
+ public string? Path { get; set; }
+
+ ///
+ /// Gets or sets media update type.
+ /// Created, Modified, Deleted.
+ ///
+ public string? UpdateType { get; set; }
+ }
+}
diff --git a/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs b/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs
new file mode 100644
index 0000000000..fbd4985f9c
--- /dev/null
+++ b/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel.DataAnnotations;
+using MediaBrowser.Model.Configuration;
+
+namespace Jellyfin.Api.Models.LibraryStructureDto
+{
+ ///
+ /// Update library options dto.
+ ///
+ public class UpdateMediaPathRequestDto
+ {
+ ///
+ /// Gets or sets the library name.
+ ///
+ [Required]
+ public string Name { get; set; } = null!;
+
+ ///
+ /// Gets or sets library folder path information.
+ ///
+ [Required]
+ public MediaPathInfo PathInfo { get; set; } = null!;
+ }
+}
diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
index 588ce717c8..8913180e48 100644
--- a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
+++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Json.Converters;
diff --git a/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs b/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs
new file mode 100644
index 0000000000..2c3a6282f2
--- /dev/null
+++ b/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs
@@ -0,0 +1,30 @@
+using MediaBrowser.Model.Notifications;
+
+namespace Jellyfin.Api.Models.NotificationDtos
+{
+ ///
+ /// The admin notification dto.
+ ///
+ public class AdminNotificationDto
+ {
+ ///
+ /// Gets or sets the notification name.
+ ///
+ public string? Name { get; set; }
+
+ ///
+ /// Gets or sets the notification description.
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// Gets or sets the notification level.
+ ///
+ public NotificationLevel? NotificationLevel { get; set; }
+
+ ///
+ /// Gets or sets the notification url.
+ ///
+ public string? Url { get; set; }
+ }
+}
diff --git a/Jellyfin.Data/DayOfWeekHelper.cs b/Jellyfin.Data/DayOfWeekHelper.cs
index 4e75f4cfd1..b7ba30180e 100644
--- a/Jellyfin.Data/DayOfWeekHelper.cs
+++ b/Jellyfin.Data/DayOfWeekHelper.cs
@@ -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 GetDaysOfWeek(DynamicDayOfWeek day)
+ public static DayOfWeek[] GetDaysOfWeek(DynamicDayOfWeek day)
{
- var days = new List(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 }
+ };
}
}
}
diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs
index 7d1b76a3f8..7974d3addb 100644
--- a/Jellyfin.Data/Entities/AccessSchedule.cs
+++ b/Jellyfin.Data/Entities/AccessSchedule.cs
@@ -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;
}
- ///
- /// Initializes a new instance of the class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected AccessSchedule()
- {
- }
-
///
/// Gets or sets the id of this instance.
///
@@ -42,8 +32,6 @@ namespace Jellyfin.Data.Entities
/// Identity, Indexed, Required.
///
[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.
///
[XmlIgnore]
- [Required]
public Guid UserId { get; protected set; }
///
/// Gets or sets the day of week.
///
/// The day of week.
- [Required]
public DynamicDayOfWeek DayOfWeek { get; set; }
///
/// Gets or sets the start hour.
///
/// The start hour.
- [Required]
public double StartHour { get; set; }
///
/// Gets or sets the end hour.
///
/// The end hour.
- [Required]
public double EndHour { get; set; }
-
- ///
- /// Static create function (for use in LINQ queries, etc.)
- ///
- /// The day of the week.
- /// The start hour.
- /// The end hour.
- /// The associated user's id.
- /// The newly created instance.
- public static AccessSchedule Create(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId)
- {
- return new AccessSchedule(dayOfWeek, startHour, endHour, userId);
- }
}
}
diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs
index e2d5c71879..e4534e8b5e 100644
--- a/Jellyfin.Data/Entities/ActivityLog.cs
+++ b/Jellyfin.Data/Entities/ActivityLog.cs
@@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities
/// The name.
/// The type.
/// The user id.
- /// The log level.
- 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;
- }
-
- ///
- /// Initializes a new instance of the class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected ActivityLog()
- {
+ LogSeverity = LogLevel.Information;
}
///
@@ -59,7 +50,6 @@ namespace Jellyfin.Data.Entities
///
/// Required, Max length = 512.
///
- [Required]
[MaxLength(512)]
[StringLength(512)]
public string Name { get; set; }
@@ -72,7 +62,7 @@ namespace Jellyfin.Data.Entities
///
[MaxLength(512)]
[StringLength(512)]
- public string Overview { get; set; }
+ public string? Overview { get; set; }
///
/// Gets or sets the short overview.
@@ -82,7 +72,7 @@ namespace Jellyfin.Data.Entities
///
[MaxLength(512)]
[StringLength(512)]
- public string ShortOverview { get; set; }
+ public string? ShortOverview { get; set; }
///
/// Gets or sets the type.
@@ -90,7 +80,6 @@ namespace Jellyfin.Data.Entities
///
/// Required, Max length = 256.
///
- [Required]
[MaxLength(256)]
[StringLength(256)]
public string Type { get; set; }
@@ -111,7 +100,7 @@ namespace Jellyfin.Data.Entities
///
[MaxLength(256)]
[StringLength(256)]
- public string ItemId { get; set; }
+ public string? ItemId { get; set; }
///
/// Gets or sets the date created. This should be in UTC.
diff --git a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs
index 511e3b281a..de37fb5446 100644
--- a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs
+++ b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs
@@ -15,22 +15,15 @@ namespace Jellyfin.Data.Entities
/// The user id.
/// The item id.
/// The client.
- /// The preference key.
- /// The preference value.
- public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string preferenceKey, string preferenceValue)
+ /// The preference key.
+ /// The preference value.
+ public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string key, string? value)
{
UserId = userId;
ItemId = itemId;
Client = client;
- Key = preferenceKey;
- Value = preferenceValue;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- protected CustomItemDisplayPreferences()
- {
+ Key = key;
+ Value = value;
}
///
@@ -64,7 +57,6 @@ namespace Jellyfin.Data.Entities
///
/// Required. Max Length = 32.
///
- [Required]
[MaxLength(32)]
[StringLength(32)]
public string Client { get; set; }
@@ -75,7 +67,6 @@ namespace Jellyfin.Data.Entities
///
/// Required.
///
- [Required]
public string Key { get; set; }
///
@@ -84,7 +75,6 @@ namespace Jellyfin.Data.Entities
///
/// Required.
///
- [Required]
- public string Value { get; set; }
+ public string? Value { get; set; }
}
}
diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs
index 1a8ca1da37..64cd6812a4 100644
--- a/Jellyfin.Data/Entities/DisplayPreferences.cs
+++ b/Jellyfin.Data/Entities/DisplayPreferences.cs
@@ -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();
}
- ///
- /// Initializes a new instance of the class.
- ///
- protected DisplayPreferences()
- {
- }
-
///
/// Gets or sets the Id.
///
@@ -74,7 +65,6 @@ namespace Jellyfin.Data.Entities
///
/// Required. Max Length = 32.
///
- [Required]
[MaxLength(32)]
[StringLength(32)]
public string Client { get; set; }
@@ -145,14 +135,14 @@ namespace Jellyfin.Data.Entities
///
[MaxLength(32)]
[StringLength(32)]
- public string DashboardTheme { get; set; }
+ public string? DashboardTheme { get; set; }
///
/// Gets or sets the tv home screen.
///
[MaxLength(32)]
[StringLength(32)]
- public string TvHome { get; set; }
+ public string? TvHome { get; set; }
///
/// Gets or sets the home sections.
diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs
index 878811e59c..b14e22b7b3 100644
--- a/Jellyfin.Data/Entities/Group.cs
+++ b/Jellyfin.Data/Entities/Group.cs
@@ -32,16 +32,6 @@ namespace Jellyfin.Data.Entities
Preferences = new HashSet();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Group()
- {
- }
-
///
/// Gets or sets the id of this group.
///
@@ -56,7 +46,6 @@ namespace Jellyfin.Data.Entities
///
/// Required, Max length = 255.
///
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string Name { get; set; }
diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs
index 0620462602..d03d0f7a86 100644
--- a/Jellyfin.Data/Entities/HomeSection.cs
+++ b/Jellyfin.Data/Entities/HomeSection.cs
@@ -1,5 +1,4 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
+using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities
@@ -15,7 +14,6 @@ namespace Jellyfin.Data.Entities
///
/// Identity. Required.
///
- [Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs
index ab8452e62c..e0c37047d8 100644
--- a/Jellyfin.Data/Entities/ImageInfo.cs
+++ b/Jellyfin.Data/Entities/ImageInfo.cs
@@ -19,16 +19,6 @@ namespace Jellyfin.Data.Entities
LastModified = DateTime.UtcNow;
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected ImageInfo()
- {
- }
-
///
/// Gets or sets the id.
///
@@ -49,7 +39,6 @@ namespace Jellyfin.Data.Entities
///
/// Required.
///
- [Required]
[MaxLength(512)]
[StringLength(512)]
public string Path { get; set; }
diff --git a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs
index 2b25bb25f2..4bfeb2fa30 100644
--- a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs
+++ b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs
@@ -28,13 +28,6 @@ namespace Jellyfin.Data.Entities
RememberIndexing = false;
}
- ///
- /// Initializes a new instance of the class.
- ///
- protected ItemDisplayPreferences()
- {
- }
-
///
/// Gets or sets the Id.
///
@@ -66,7 +59,6 @@ namespace Jellyfin.Data.Entities
///
/// Required. Max Length = 32.
///
- [Required]
[MaxLength(32)]
[StringLength(32)]
public string Client { get; set; }
@@ -106,7 +98,6 @@ namespace Jellyfin.Data.Entities
///
/// Required.
///
- [Required]
[MaxLength(64)]
[StringLength(64)]
public string SortBy { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/Artwork.cs b/Jellyfin.Data/Entities/Libraries/Artwork.cs
index 06cd333301..84a524de2c 100644
--- a/Jellyfin.Data/Entities/Libraries/Artwork.cs
+++ b/Jellyfin.Data/Entities/Libraries/Artwork.cs
@@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// The path.
/// The kind of art.
- /// The owner.
- 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);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Artwork()
- {
}
///
@@ -57,7 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required, Max length = 65535.
///
- [Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Path { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/Book.cs b/Jellyfin.Data/Entities/Libraries/Book.cs
index 2e63f75bd9..aea3d58d58 100644
--- a/Jellyfin.Data/Entities/Libraries/Book.cs
+++ b/Jellyfin.Data/Entities/Libraries/Book.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Initializes a new instance of the class.
///
- public Book()
+ /// The library.
+ public Book(Library library) : base(library)
{
BookMetadata = new HashSet();
Releases = new HashSet();
diff --git a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs
index 4a3d290f01..1ff4327b01 100644
--- a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs
@@ -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
///
/// The title or name of the object.
/// ISO-639-3 3-character language codes.
- /// The book.
- 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();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected BookMetadata()
- {
- }
-
///
/// Gets or sets the ISBN.
///
@@ -51,7 +31,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection Publishers { get; protected set; }
///
- [NotMapped]
public ICollection Companies => Publishers;
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Chapter.cs b/Jellyfin.Data/Entities/Libraries/Chapter.cs
index f503de3794..11f53ae20a 100644
--- a/Jellyfin.Data/Entities/Libraries/Chapter.cs
+++ b/Jellyfin.Data/Entities/Libraries/Chapter.cs
@@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// ISO-639-3 3-character language codes.
/// The start time for this chapter.
- /// The release.
- 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);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Chapter()
- {
}
///
@@ -63,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Name { get; set; }
+ public string? Name { get; set; }
///
/// 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.
///
- [Required]
[MinLength(3)]
[MaxLength(3)]
[StringLength(3)]
diff --git a/Jellyfin.Data/Entities/Libraries/Collection.cs b/Jellyfin.Data/Entities/Libraries/Collection.cs
index 39eded752d..4253b7eccf 100644
--- a/Jellyfin.Data/Entities/Libraries/Collection.cs
+++ b/Jellyfin.Data/Entities/Libraries/Collection.cs
@@ -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
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Name { get; set; }
+ public string? Name { get; set; }
///
[ConcurrencyCheck]
diff --git a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
index f9539964d0..e19362bdf1 100644
--- a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
+++ b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
@@ -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
///
/// Initializes a new instance of the class.
///
- /// The collection.
- /// The previous item.
- /// The next item.
- 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;
- }
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected CollectionItem()
+ /// The library item.
+ public CollectionItem(LibraryItem libraryItem)
{
+ LibraryItem = libraryItem;
}
///
@@ -75,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// TODO check if this properly updated Dependant and has the proper principal relationship.
///
- public virtual CollectionItem Next { get; set; }
+ public virtual CollectionItem? Next { get; set; }
///
/// Gets or sets the previous item in the collection.
@@ -83,7 +53,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// TODO check if this properly updated Dependant and has the proper principal relationship.
///
- public virtual CollectionItem Previous { get; set; }
+ public virtual CollectionItem? Previous { get; set; }
///
public void OnSavingChanges()
diff --git a/Jellyfin.Data/Entities/Libraries/Company.cs b/Jellyfin.Data/Entities/Libraries/Company.cs
index 3b6ed3309a..09050bb522 100644
--- a/Jellyfin.Data/Entities/Libraries/Company.cs
+++ b/Jellyfin.Data/Entities/Libraries/Company.cs
@@ -15,22 +15,10 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Initializes a new instance of the class.
///
- /// The owner of this company.
- public Company(IHasCompanies owner)
+ public Company()
{
- owner?.Companies.Add(this);
-
CompanyMetadata = new HashSet();
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Company()
- {
+ ChildCompanies = new HashSet();
}
///
@@ -57,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection ChildCompanies { get; protected set; }
///
- [NotMapped]
public ICollection Companies => ChildCompanies;
///
diff --git a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
index 8aa0486afa..a29f08c7f6 100644
--- a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries
@@ -13,21 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// The title or name of the object.
/// ISO-639-3 3-character language codes.
- /// The company.
- public CompanyMetadata(string title, string language, Company company) : base(title, language)
- {
- if (company == null)
- {
- throw new ArgumentNullException(nameof(company));
- }
-
- company.CompanyMetadata.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- protected CompanyMetadata()
+ public CompanyMetadata(string title, string language) : base(title, language)
{
}
@@ -39,7 +24,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(65535)]
[StringLength(65535)]
- public string Description { get; set; }
+ public string? Description { get; set; }
///
/// Gets or sets the headquarters.
@@ -49,7 +34,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(255)]
[StringLength(255)]
- public string Headquarters { get; set; }
+ public string? Headquarters { get; set; }
///
/// Gets or sets the country code.
@@ -59,7 +44,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(2)]
[StringLength(2)]
- public string Country { get; set; }
+ public string? Country { get; set; }
///
/// Gets or sets the homepage.
@@ -69,6 +54,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Homepage { get; set; }
+ public string? Homepage { get; set; }
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/CustomItem.cs b/Jellyfin.Data/Entities/Libraries/CustomItem.cs
index 115489c786..88d1a0c258 100644
--- a/Jellyfin.Data/Entities/Libraries/CustomItem.cs
+++ b/Jellyfin.Data/Entities/Libraries/CustomItem.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Initializes a new instance of the class.
///
- public CustomItem()
+ /// The library.
+ public CustomItem(Library library) : base(library)
{
CustomItemMetadata = new HashSet();
Releases = new HashSet();
diff --git a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
index f0daedfbe8..af2393870f 100644
--- a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace Jellyfin.Data.Entities.Libraries
{
///
@@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// The title or name of the object.
/// ISO-639-3 3-character language codes.
- /// The item.
- public CustomItemMetadata(string title, string language, CustomItem item) : base(title, language)
- {
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
-
- item.CustomItemMetadata.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected CustomItemMetadata()
+ public CustomItemMetadata(string title, string language) : base(title, language)
{
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Episode.cs b/Jellyfin.Data/Entities/Libraries/Episode.cs
index 0bdc2d7642..458c7d9f5a 100644
--- a/Jellyfin.Data/Entities/Libraries/Episode.cs
+++ b/Jellyfin.Data/Entities/Libraries/Episode.cs
@@ -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
///
/// Initializes a new instance of the class.
///
- /// The season.
- public Episode(Season season)
+ /// The library.
+ public Episode(Library library) : base(library)
{
- if (season == null)
- {
- throw new ArgumentNullException(nameof(season));
- }
-
- season.Episodes.Add(this);
-
Releases = new HashSet();
EpisodeMetadata = new HashSet();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Episode()
- {
- }
-
///
/// Gets or sets the episode number.
///
diff --git a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
index 7efb840f0b..b0ef11e0f2 100644
--- a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries
@@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// The title or name of the object.
/// ISO-639-3 3-character language codes.
- /// The episode.
- public EpisodeMetadata(string title, string language, Episode episode) : base(title, language)
- {
- if (episode == null)
- {
- throw new ArgumentNullException(nameof(episode));
- }
-
- episode.EpisodeMetadata.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected EpisodeMetadata()
+ public EpisodeMetadata(string title, string language) : base(title, language)
{
}
@@ -42,7 +24,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Outline { get; set; }
+ public string? Outline { get; set; }
///
/// Gets or sets the plot.
@@ -52,7 +34,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(65535)]
[StringLength(65535)]
- public string Plot { get; set; }
+ public string? Plot { get; set; }
///
/// Gets or sets the tagline.
@@ -62,6 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Tagline { get; set; }
+ public string? Tagline { get; set; }
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Genre.cs b/Jellyfin.Data/Entities/Libraries/Genre.cs
index 2a2dbd1a5d..9f3d650283 100644
--- a/Jellyfin.Data/Entities/Libraries/Genre.cs
+++ b/Jellyfin.Data/Entities/Libraries/Genre.cs
@@ -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 class.
///
/// The name.
- /// The metadata.
- 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);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Genre()
- {
}
///
@@ -57,7 +33,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Indexed, Required, Max length = 255.
///
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string Name { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
index d74330c051..d12e011a8b 100644
--- a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
@@ -42,16 +42,6 @@ namespace Jellyfin.Data.Entities.Libraries
Sources = new HashSet();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to being abstract.
- ///
- protected ItemMetadata()
- {
- }
-
///
/// Gets or sets the id.
///
@@ -67,7 +57,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required, Max length = 1024.
///
- [Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Title { get; set; }
@@ -80,7 +69,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string OriginalTitle { get; set; }
+ public string? OriginalTitle { get; set; }
///
/// Gets or sets the sort title.
@@ -90,7 +79,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string SortTitle { get; set; }
+ public string? SortTitle { get; set; }
///
/// 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.
///
- [Required]
[MinLength(3)]
[MaxLength(3)]
[StringLength(3)]
diff --git a/Jellyfin.Data/Entities/Libraries/Library.cs b/Jellyfin.Data/Entities/Libraries/Library.cs
index 4f82a2e2a7..e453849020 100644
--- a/Jellyfin.Data/Entities/Libraries/Library.cs
+++ b/Jellyfin.Data/Entities/Libraries/Library.cs
@@ -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 class.
///
/// The name of the library.
- public Library(string name)
+ /// The path of the library.
+ public Library(string name, string path)
{
- if (string.IsNullOrWhiteSpace(name))
- {
- throw new ArgumentNullException(nameof(name));
- }
-
Name = name;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Library()
- {
+ Path = path;
}
///
@@ -49,7 +35,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required, Max length = 128.
///
- [Required]
[MaxLength(128)]
[StringLength(128)]
public string Name { get; set; }
@@ -60,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required.
///
- [Required]
public string Path { get; set; }
///
diff --git a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs
index a9167aa7fd..67ffad944b 100644
--- a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs
+++ b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs
@@ -20,13 +20,6 @@ namespace Jellyfin.Data.Entities.Libraries
Library = library;
}
- ///
- /// Initializes a new instance of the class.
- ///
- protected LibraryItem()
- {
- }
-
///
/// Gets or sets the id.
///
@@ -51,7 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required.
///
- [Required]
public virtual Library Library { get; set; }
///
diff --git a/Jellyfin.Data/Entities/Libraries/MediaFile.cs b/Jellyfin.Data/Entities/Libraries/MediaFile.cs
index 9924d5728d..f3e2fe6539 100644
--- a/Jellyfin.Data/Entities/Libraries/MediaFile.cs
+++ b/Jellyfin.Data/Entities/Libraries/MediaFile.cs
@@ -19,8 +19,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// The path relative to the LibraryRoot.
/// The file kind.
- /// The release.
- 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();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected MediaFile()
- {
- }
-
///
/// Gets or sets the id.
///
@@ -65,7 +47,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required, Max length = 65535.
///
- [Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Path { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs
index 5b03e260e2..ba21294fc9 100644
--- a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs
+++ b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs
@@ -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 class.
///
/// The number of this stream.
- /// The media file.
- public MediaFileStream(int streamNumber, MediaFile mediaFile)
+ public MediaFileStream(int streamNumber)
{
StreamNumber = streamNumber;
-
- if (mediaFile == null)
- {
- throw new ArgumentNullException(nameof(mediaFile));
- }
-
- mediaFile.MediaFileStreams.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected MediaFileStream()
- {
}
///
diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs
index a18a612bcd..fb2587882d 100644
--- a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs
+++ b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs
@@ -24,16 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries
Name = name;
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected MetadataProvider()
- {
- }
-
///
/// Gets or sets the id.
///
@@ -49,7 +39,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required, Max length = 1024.
///
- [Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
index fcfb35bfac..2a9c904c88 100644
--- a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
+++ b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
@@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the class.
///
/// The provider id.
- /// The metadata entity.
- public MetadataProviderId(string providerId, ItemMetadata itemMetadata)
+ /// The metadata provider.
+ 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);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected MetadataProviderId()
- {
+ MetadataProvider = metadataProvider;
}
///
@@ -57,7 +41,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required, Max length = 255.
///
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string ProviderId { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/Movie.cs b/Jellyfin.Data/Entities/Libraries/Movie.cs
index 08db904fa8..f89cacff44 100644
--- a/Jellyfin.Data/Entities/Libraries/Movie.cs
+++ b/Jellyfin.Data/Entities/Libraries/Movie.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Initializes a new instance of the class.
///
- public Movie()
+ /// The library.
+ public Movie(Library library) : base(library)
{
Releases = new HashSet();
MovieMetadata = new HashSet();
diff --git a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
index aa1501a5cc..fb181dea60 100644
--- a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
@@ -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
///
/// The title or name of the movie.
/// ISO-639-3 3-character language codes.
- /// The movie.
- public MovieMetadata(string title, string language, Movie movie) : base(title, language)
+ public MovieMetadata(string title, string language) : base(title, language)
{
Studios = new HashSet();
-
- movie.MovieMetadata.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected MovieMetadata()
- {
}
///
@@ -43,7 +29,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Outline { get; set; }
+ public string? Outline { get; set; }
///
/// Gets or sets the tagline.
@@ -53,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Tagline { get; set; }
+ public string? Tagline { get; set; }
///
/// Gets or sets the plot.
@@ -63,7 +49,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(65535)]
[StringLength(65535)]
- public string Plot { get; set; }
+ public string? Plot { get; set; }
///
/// Gets or sets the country code.
@@ -73,7 +59,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(2)]
[StringLength(2)]
- public string Country { get; set; }
+ public string? Country { get; set; }
///
/// Gets or sets the studios that produced this movie.
@@ -81,7 +67,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection Studios { get; protected set; }
///
- [NotMapped]
public ICollection Companies => Studios;
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
index 06aff6f457..4049cdac8e 100644
--- a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
+++ b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
@@ -12,7 +12,8 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Initializes a new instance of the class.
///
- public MusicAlbum()
+ /// The library.
+ public MusicAlbum(Library library) : base(library)
{
MusicAlbumMetadata = new HashSet();
Tracks = new HashSet
/// The title or name of the album.
/// ISO-639-3 3-character language codes.
- /// The music album.
- public MusicAlbumMetadata(string title, string language, MusicAlbum album) : base(title, language)
+ public MusicAlbumMetadata(string title, string language) : base(title, language)
{
Labels = new HashSet();
-
- album.MusicAlbumMetadata.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected MusicAlbumMetadata()
- {
}
///
@@ -41,7 +28,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(255)]
[StringLength(255)]
- public string Barcode { get; set; }
+ public string? Barcode { get; set; }
///
/// Gets or sets the label number.
@@ -51,7 +38,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(255)]
[StringLength(255)]
- public string LabelNumber { get; set; }
+ public string? LabelNumber { get; set; }
///
/// Gets or sets the country code.
@@ -61,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(2)]
[StringLength(2)]
- public string Country { get; set; }
+ public string? Country { get; set; }
///
/// Gets or sets a collection containing the labels.
diff --git a/Jellyfin.Data/Entities/Libraries/Person.cs b/Jellyfin.Data/Entities/Libraries/Person.cs
index af4c87b73c..159bd47be7 100644
--- a/Jellyfin.Data/Entities/Libraries/Person.cs
+++ b/Jellyfin.Data/Entities/Libraries/Person.cs
@@ -31,16 +31,6 @@ namespace Jellyfin.Data.Entities.Libraries
Sources = new HashSet();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Person()
- {
- }
-
///
/// Gets or sets the id.
///
@@ -56,7 +46,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required, Max length = 1024.
///
- [Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }
@@ -69,7 +58,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(256)]
[StringLength(256)]
- public string SourceId { get; set; }
+ public string? SourceId { get; set; }
///
/// Gets or sets the date added.
diff --git a/Jellyfin.Data/Entities/Libraries/PersonRole.cs b/Jellyfin.Data/Entities/Libraries/PersonRole.cs
index cd38ee83d0..988aa84bae 100644
--- a/Jellyfin.Data/Entities/Libraries/PersonRole.cs
+++ b/Jellyfin.Data/Entities/Libraries/PersonRole.cs
@@ -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 class.
///
/// The role type.
- /// The metadata.
- public PersonRole(PersonRoleType type, ItemMetadata itemMetadata)
+ /// The person.
+ 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();
Sources = new HashSet();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected PersonRole()
- {
- }
-
///
/// Gets or sets the id.
///
@@ -60,7 +43,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Role { get; set; }
+ public string? Role { get; set; }
///
/// Gets or sets the person's role type.
@@ -80,7 +63,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required.
///
- [Required]
public virtual Person Person { get; set; }
///
diff --git a/Jellyfin.Data/Entities/Libraries/Photo.cs b/Jellyfin.Data/Entities/Libraries/Photo.cs
index 25562ec96f..eb5c962675 100644
--- a/Jellyfin.Data/Entities/Libraries/Photo.cs
+++ b/Jellyfin.Data/Entities/Libraries/Photo.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Initializes a new instance of the class.
///
- public Photo()
+ /// The library.
+ public Photo(Library library) : base(library)
{
PhotoMetadata = new HashSet();
Releases = new HashSet();
diff --git a/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs b/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
index ffc790b574..6c284307d7 100644
--- a/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace Jellyfin.Data.Entities.Libraries
{
///
@@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// The title or name of the photo.
/// ISO-639-3 3-character language codes.
- /// The photo.
- public PhotoMetadata(string title, string language, Photo photo) : base(title, language)
- {
- if (photo == null)
- {
- throw new ArgumentNullException(nameof(photo));
- }
-
- photo.PhotoMetadata.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected PhotoMetadata()
+ public PhotoMetadata(string title, string language) : base(title, language)
{
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Rating.cs b/Jellyfin.Data/Entities/Libraries/Rating.cs
index 98226cd802..6862012a80 100644
--- a/Jellyfin.Data/Entities/Libraries/Rating.cs
+++ b/Jellyfin.Data/Entities/Libraries/Rating.cs
@@ -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 class.
///
/// The value.
- /// The metadata.
- public Rating(double value, ItemMetadata itemMetadata)
+ public Rating(double value)
{
Value = value;
-
- if (itemMetadata == null)
- {
- throw new ArgumentNullException(nameof(itemMetadata));
- }
-
- itemMetadata.Ratings.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Rating()
- {
}
///
@@ -67,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Gets or sets the rating type.
/// If this is null it's the internal user rating.
///
- public virtual RatingSource RatingType { get; set; }
+ public virtual RatingSource? RatingType { get; set; }
///
public void OnSavingChanges()
diff --git a/Jellyfin.Data/Entities/Libraries/RatingSource.cs b/Jellyfin.Data/Entities/Libraries/RatingSource.cs
index 549f418042..ae0d806ff0 100644
--- a/Jellyfin.Data/Entities/Libraries/RatingSource.cs
+++ b/Jellyfin.Data/Entities/Libraries/RatingSource.cs
@@ -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
///
/// The minimum value.
/// The maximum value.
- /// The rating.
- 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;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected RatingSource()
- {
}
///
@@ -56,7 +37,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Name { get; set; }
+ public string? Name { get; set; }
///
/// Gets or sets the minimum value.
@@ -81,7 +62,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Gets or sets the metadata source.
///
- public virtual MetadataProviderId Source { get; set; }
+ public virtual MetadataProviderId? Source { get; set; }
///
public void OnSavingChanges()
diff --git a/Jellyfin.Data/Entities/Libraries/Release.cs b/Jellyfin.Data/Entities/Libraries/Release.cs
index b633e08fb3..21d4039793 100644
--- a/Jellyfin.Data/Entities/Libraries/Release.cs
+++ b/Jellyfin.Data/Entities/Libraries/Release.cs
@@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the class.
///
/// The name of this release.
- /// The owner of this release.
- 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();
Chapters = new HashSet();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Release()
- {
- }
-
///
/// Gets or sets the id.
///
@@ -58,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Required, Max length = 1024.
///
- [Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/Season.cs b/Jellyfin.Data/Entities/Libraries/Season.cs
index eb6674dbc3..04f723a1d1 100644
--- a/Jellyfin.Data/Entities/Libraries/Season.cs
+++ b/Jellyfin.Data/Entities/Libraries/Season.cs
@@ -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
///
/// Initializes a new instance of the class.
///
- /// The series.
- public Season(Series series)
+ /// The library.
+ public Season(Library library) : base(library)
{
- if (series == null)
- {
- throw new ArgumentNullException(nameof(series));
- }
-
- series.Seasons.Add(this);
-
Episodes = new HashSet();
SeasonMetadata = new HashSet();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Season()
- {
- }
-
///
/// Gets or sets the season number.
///
diff --git a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
index 7ce79756b2..da40a075f5 100644
--- a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries
@@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// The title or name of the object.
/// ISO-639-3 3-character language codes.
- /// The season.
- public SeasonMetadata(string title, string language, Season season) : base(title, language)
- {
- if (season == null)
- {
- throw new ArgumentNullException(nameof(season));
- }
-
- season.SeasonMetadata.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected SeasonMetadata()
+ public SeasonMetadata(string title, string language) : base(title, language)
{
}
@@ -42,6 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Outline { get; set; }
+ public string? Outline { get; set; }
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Series.cs b/Jellyfin.Data/Entities/Libraries/Series.cs
index 8c8317d14b..59508831e4 100644
--- a/Jellyfin.Data/Entities/Libraries/Series.cs
+++ b/Jellyfin.Data/Entities/Libraries/Series.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// Initializes a new instance of the class.
///
- public Series()
+ /// The library.
+ public Series(Library library) : base(library)
{
DateAdded = DateTime.UtcNow;
Seasons = new HashSet();
diff --git a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
index 877dbfc69c..cc04d033aa 100644
--- a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
@@ -1,9 +1,7 @@
#pragma warning disable CA2227
-using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
namespace Jellyfin.Data.Entities.Libraries
@@ -18,29 +16,11 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// The title or name of the object.
/// ISO-639-3 3-character language codes.
- /// The series.
- 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();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected SeriesMetadata()
- {
- }
-
///
/// Gets or sets the outline.
///
@@ -49,7 +29,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Outline { get; set; }
+ public string? Outline { get; set; }
///
/// Gets or sets the plot.
@@ -59,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(65535)]
[StringLength(65535)]
- public string Plot { get; set; }
+ public string? Plot { get; set; }
///
/// Gets or sets the tagline.
@@ -69,7 +49,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(1024)]
[StringLength(1024)]
- public string Tagline { get; set; }
+ public string? Tagline { get; set; }
///
/// Gets or sets the country code.
@@ -79,7 +59,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
[MaxLength(2)]
[StringLength(2)]
- public string Country { get; set; }
+ public string? Country { get; set; }
///
/// Gets or sets a collection containing the networks.
@@ -87,7 +67,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection Networks { get; protected set; }
///
- [NotMapped]
public ICollection Companies => Networks;
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Track.cs b/Jellyfin.Data/Entities/Libraries/Track.cs
index 782bfb5ce7..86a3edff83 100644
--- a/Jellyfin.Data/Entities/Libraries/Track.cs
+++ b/Jellyfin.Data/Entities/Libraries/Track.cs
@@ -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
///
/// Initializes a new instance of the class.
///
- /// The album.
- public Track(MusicAlbum album)
+ /// The library.
+ public Track(Library library) : base(library)
{
- if (album == null)
- {
- throw new ArgumentNullException(nameof(album));
- }
-
- album.Tracks.Add(this);
-
Releases = new HashSet();
TrackMetadata = new HashSet();
}
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Track()
- {
- }
-
///
/// Gets or sets the track number.
///
diff --git a/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs b/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
index 321f93bf2e..042d2b90db 100644
--- a/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace Jellyfin.Data.Entities.Libraries
{
///
@@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
///
/// The title or name of the object.
/// ISO-639-3 3-character language codes.
- /// The track.
- public TrackMetadata(string title, string language, Track track) : base(title, language)
- {
- if (track == null)
- {
- throw new ArgumentNullException(nameof(track));
- }
-
- track.TrackMetadata.Add(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected TrackMetadata()
+ public TrackMetadata(string title, string language) : base(title, language)
{
}
}
diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs
index d92e5d9d25..f059dedfa2 100644
--- a/Jellyfin.Data/Entities/Permission.cs
+++ b/Jellyfin.Data/Entities/Permission.cs
@@ -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;
}
- ///
- /// Initializes a new instance of the class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Permission()
- {
- }
-
///
/// Gets or sets the id of this permission.
///
diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs
index 4efddf2a41..a8813ab88d 100644
--- a/Jellyfin.Data/Entities/Preference.cs
+++ b/Jellyfin.Data/Entities/Preference.cs
@@ -23,14 +23,6 @@ namespace Jellyfin.Data.Entities
Value = value ?? throw new ArgumentNullException(nameof(value));
}
- ///
- /// Initializes a new instance of the class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected Preference()
- {
- }
-
///
/// Gets or sets the id of this preference.
///
@@ -54,7 +46,6 @@ namespace Jellyfin.Data.Entities
///
/// Required, Max length = 65535.
///
- [Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Value { get; set; }
diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs
index 362f3b4ebb..74331726cc 100644
--- a/Jellyfin.Data/Entities/User.cs
+++ b/Jellyfin.Data/Entities/User.cs
@@ -51,6 +51,7 @@ namespace Jellyfin.Data.Entities
PasswordResetProviderId = passwordResetProviderId;
AccessSchedules = new HashSet();
+ DisplayPreferences = new HashSet();
ItemDisplayPreferences = new HashSet();
// Groups = new HashSet();
Permissions = new HashSet();
@@ -72,17 +73,6 @@ namespace Jellyfin.Data.Entities
PlayDefaultAudioTrack = true;
SubtitleMode = SubtitlePlaybackMode.Default;
SyncPlayAccess = SyncPlayUserAccessType.CreateAndJoinGroups;
-
- AddDefaultPermissions();
- AddDefaultPreferences();
- }
-
- ///
- /// Initializes a new instance of the class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- ///
- protected User()
- {
}
///
@@ -100,7 +90,6 @@ namespace Jellyfin.Data.Entities
///
/// Required, Max length = 255.
///
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string Username { get; set; }
@@ -113,7 +102,7 @@ namespace Jellyfin.Data.Entities
///
[MaxLength(65535)]
[StringLength(65535)]
- public string Password { get; set; }
+ public string? Password { get; set; }
///
/// Gets or sets the user's easy password, or null if none is set.
@@ -123,7 +112,7 @@ namespace Jellyfin.Data.Entities
///
[MaxLength(65535)]
[StringLength(65535)]
- public string EasyPassword { get; set; }
+ public string? EasyPassword { get; set; }
///
/// Gets or sets a value indicating whether the user must update their password.
@@ -141,7 +130,7 @@ namespace Jellyfin.Data.Entities
///
[MaxLength(255)]
[StringLength(255)]
- public string AudioLanguagePreference { get; set; }
+ public string? AudioLanguagePreference { get; set; }
///
/// Gets or sets the authentication provider id.
@@ -149,7 +138,6 @@ namespace Jellyfin.Data.Entities
///
/// Required, Max length = 255.
///
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string AuthenticationProviderId { get; set; }
@@ -160,7 +148,6 @@ namespace Jellyfin.Data.Entities
///
/// Required, Max length = 255.
///
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string PasswordResetProviderId { get; set; }
@@ -217,7 +204,7 @@ namespace Jellyfin.Data.Entities
///
[MaxLength(255)]
[StringLength(255)]
- public string SubtitleLanguagePreference { get; set; }
+ public string? SubtitleLanguagePreference { get; set; }
///
/// 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 null.
///
// [ForeignKey("UserId")]
- public virtual ImageInfo ProfileImage { get; set; }
+ public virtual ImageInfo? ProfileImage { get; set; }
///
/// Gets or sets the user's display preferences.
@@ -320,8 +307,7 @@ namespace Jellyfin.Data.Entities
///
/// Required.
///
- [Required]
- public virtual DisplayPreferences DisplayPreferences { get; set; }
+ public virtual ICollection DisplayPreferences { get; set; }
///
/// Gets or sets the level of sync play permissions this user has.
@@ -494,18 +480,11 @@ namespace Jellyfin.Data.Entities
return Array.IndexOf(GetPreferenceValues(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;
- }
-
+ ///
+ /// Initializes the default permissions for a user. Should only be called on user creation.
+ ///
// 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()
+ ///
+ /// Initializes the default preferences. Should only be called on user creation.
+ ///
+ public void AddDefaultPreferences()
{
foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast())
{
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;
+ }
}
}
diff --git a/Jellyfin.Data/Interfaces/IHasPermissions.cs b/Jellyfin.Data/Interfaces/IHasPermissions.cs
index 3be72259ad..85ee12ad74 100644
--- a/Jellyfin.Data/Interfaces/IHasPermissions.cs
+++ b/Jellyfin.Data/Interfaces/IHasPermissions.cs
@@ -2,7 +2,7 @@
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
-namespace Jellyfin.Data
+namespace Jellyfin.Data.Interfaces
{
///
/// An abstraction representing an entity that has permissions.
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index a8ac45645f..3b14d33125 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -5,6 +5,9 @@
false
true
true
+ AllEnabledByDefault
+ ../jellyfin.ruleset
+ enable
true
true
true
@@ -24,25 +27,19 @@
GPL-3.0-only
-
- ../jellyfin.ruleset
-
-
-
-
-
+
diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index 466a12e676..ee43c2159a 100644
--- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -11,6 +11,8 @@
true
true
enable
+ AllEnabledByDefault
+ ../jellyfin.ruleset
@@ -18,8 +20,8 @@
-
-
+
+
@@ -30,16 +32,16 @@
+
+
+
+
+
-
-
- ../jellyfin.ruleset
-
-
diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index eab5777d5d..8f0fae3beb 100644
--- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -91,9 +91,6 @@ namespace Jellyfin.Drawing.Skia
}
}
- private static bool IsTransparent(SKColor color)
- => (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0;
-
///
/// Convert a to a .
///
@@ -111,65 +108,6 @@ namespace Jellyfin.Drawing.Skia
};
}
- private static bool IsTransparentRow(SKBitmap bmp, int row)
- {
- for (var i = 0; i < bmp.Width; ++i)
- {
- if (!IsTransparent(bmp.GetPixel(i, row)))
- {
- return false;
- }
- }
-
- return true;
- }
-
- private static bool IsTransparentColumn(SKBitmap bmp, int col)
- {
- for (var i = 0; i < bmp.Height; ++i)
- {
- if (!IsTransparent(bmp.GetPixel(col, i)))
- {
- return false;
- }
- }
-
- return true;
- }
-
- private SKBitmap CropWhiteSpace(SKBitmap bitmap)
- {
- var topmost = 0;
- while (topmost < bitmap.Height && IsTransparentRow(bitmap, topmost))
- {
- topmost++;
- }
-
- int bottommost = bitmap.Height;
- while (bottommost >= 0 && IsTransparentRow(bitmap, bottommost - 1))
- {
- bottommost--;
- }
-
- var leftmost = 0;
- while (leftmost < bitmap.Width && IsTransparentColumn(bitmap, leftmost))
- {
- leftmost++;
- }
-
- var rightmost = bitmap.Width;
- while (rightmost >= 0 && IsTransparentColumn(bitmap, rightmost - 1))
- {
- rightmost--;
- }
-
- var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost);
-
- using var image = SKImage.FromBitmap(bitmap);
- using var subset = image.Subset(newRect);
- return SKBitmap.FromImage(subset);
- }
-
///
/// The path is null.
/// The path is not valid.
@@ -274,8 +212,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;
@@ -312,22 +250,11 @@ namespace Jellyfin.Drawing.Skia
return resultBitmap;
}
- private SKBitmap? GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
- {
- if (cropWhitespace)
- {
- using var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin);
- return bitmap == null ? null : CropWhiteSpace(bitmap);
- }
-
- return Decode(path, forceAnalyzeBitmap, orientation, out origin);
- }
-
- private SKBitmap? GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation)
+ private SKBitmap? GetBitmap(string path, bool autoOrient, ImageOrientation? orientation)
{
if (autoOrient)
{
- var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out var origin);
+ var bitmap = Decode(path, true, orientation, out var origin);
if (bitmap != null && origin != SKEncodedOrigin.TopLeft)
{
@@ -340,16 +267,11 @@ namespace Jellyfin.Drawing.Skia
return bitmap;
}
- return GetBitmap(path, cropWhitespace, false, orientation, out _);
+ return Decode(path, false, orientation, out _);
}
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 +369,7 @@ namespace Jellyfin.Drawing.Skia
}
///
- 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,14 +381,14 @@ 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);
var blur = options.Blur ?? 0;
var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0);
- using var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation);
+ using var bitmap = GetBitmap(inputPath, autoOrient, orientation);
if (bitmap == null)
{
throw new InvalidDataException($"Skia unable to read image {inputPath}");
@@ -474,9 +396,7 @@ namespace Jellyfin.Drawing.Skia
var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height);
- if (!options.CropWhiteSpace
- && options.HasDefaultOptions(inputPath, originalImageSize)
- && !autoOrient)
+ if (options.HasDefaultOptions(inputPath, originalImageSize) && !autoOrient)
{
// Just spit out the original file if all the options are default
return inputPath;
diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs
index 91bf0015fd..faf814c060 100644
--- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs
+++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs
@@ -1,7 +1,6 @@
#pragma warning disable CA1819 // Properties should not return arrays
using System;
-using MediaBrowser.Model.Configuration;
namespace Jellyfin.Networking.Configuration
{
diff --git a/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs b/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs
index e77b17ba92..8cbe398b07 100644
--- a/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs
+++ b/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs
@@ -1,4 +1,3 @@
-using Jellyfin.Networking.Configuration;
using MediaBrowser.Common.Configuration;
namespace Jellyfin.Networking.Configuration
diff --git a/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj
index cbda74361f..63557e91f0 100644
--- a/Jellyfin.Networking/Jellyfin.Networking.csproj
+++ b/Jellyfin.Networking/Jellyfin.Networking.csproj
@@ -5,6 +5,8 @@
true
true
enable
+ AllEnabledByDefault
+ ../jellyfin.ruleset
@@ -13,16 +15,11 @@
-
-
- ../jellyfin.ruleset
-
-
diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs
index 51fcb6d9a0..73e8b2cd72 100644
--- a/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -165,7 +165,7 @@ namespace Jellyfin.Networking.Manager
{
foreach (var item in source)
{
- result.AddItem(item);
+ result.AddItem(item, false);
}
}
@@ -274,7 +274,7 @@ namespace Jellyfin.Networking.Manager
if (_bindExclusions.Count > 0)
{
// Return all the interfaces except the ones specifically excluded.
- return _interfaceAddresses.Exclude(_bindExclusions);
+ return _interfaceAddresses.Exclude(_bindExclusions, false);
}
if (individualInterfaces)
@@ -285,21 +285,32 @@ namespace Jellyfin.Networking.Manager
// No bind address and no exclusions, so listen on all interfaces.
Collection result = new Collection();
- if (IsIP4Enabled)
+ if (IsIP6Enabled && IsIP4Enabled)
+ {
+ // Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any
+ result.AddItem(IPAddress.IPv6Any);
+ }
+ else if (IsIP4Enabled)
{
result.AddItem(IPAddress.Any);
}
-
- if (IsIP6Enabled)
+ else if (IsIP6Enabled)
{
- result.AddItem(IPAddress.IPv6Any);
+ // Cannot use IPv6Any as Kestrel will bind to IPv4 addresses.
+ foreach (var iface in _interfaceAddresses)
+ {
+ if (iface.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ result.AddItem(iface.Address);
+ }
+ }
}
return result;
}
// Remove any excluded bind interfaces.
- return _bindAddresses.Exclude(_bindExclusions);
+ return _bindAddresses.Exclude(_bindExclusions, false);
}
///
@@ -386,15 +397,26 @@ namespace Jellyfin.Networking.Manager
}
// Get the first LAN interface address that isn't a loopback.
- var interfaces = CreateCollection(_interfaceAddresses
- .Exclude(_bindExclusions)
- .Where(IsInLocalNetwork)
- .OrderBy(p => p.Tag));
+ var interfaces = CreateCollection(
+ _interfaceAddresses
+ .Exclude(_bindExclusions, false)
+ .Where(IsInLocalNetwork)
+ .OrderBy(p => p.Tag));
if (interfaces.Count > 0)
{
if (haveSource)
{
+ foreach (var intf in interfaces)
+ {
+ if (intf.Address.Equals(source.Address))
+ {
+ result = FormatIP6String(intf.Address);
+ _logger.LogDebug("{Source}: GetBindInterface: Has found matching interface. {Result}", source, result);
+ return result;
+ }
+ }
+
// Does the request originate in one of the interface subnets?
// (For systems with multiple internal network cards, and multiple subnets)
foreach (var intf in interfaces)
@@ -414,7 +436,7 @@ namespace Jellyfin.Networking.Manager
}
// There isn't any others, so we'll use the loopback.
- result = IsIP6Enabled ? "::" : "127.0.0.1";
+ result = IsIP6Enabled ? "::1" : "127.0.0.1";
_logger.LogWarning("{Source}: GetBindInterface: Loopback {Result} returned.", source, result);
return result;
}
@@ -521,10 +543,10 @@ namespace Jellyfin.Networking.Manager
{
if (filter == null)
{
- return _lanSubnets.Exclude(_excludedSubnets).AsNetworks();
+ return _lanSubnets.Exclude(_excludedSubnets, true).AsNetworks();
}
- return _lanSubnets.Exclude(filter);
+ return _lanSubnets.Exclude(filter, true);
}
///
@@ -555,7 +577,7 @@ namespace Jellyfin.Networking.Manager
&& ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
|| (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6)))
{
- result.AddItem(iface);
+ result.AddItem(iface, false);
}
}
@@ -565,6 +587,29 @@ namespace Jellyfin.Networking.Manager
return false;
}
+ ///
+ public bool HasRemoteAccess(IPAddress remoteIp)
+ {
+ var config = _configurationManager.GetNetworkConfiguration();
+ if (config.EnableRemoteAccess)
+ {
+ // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
+ // If left blank, all remote addresses will be allowed.
+ if (RemoteAddressFilter.Count > 0 && !IsInLocalNetwork(remoteIp))
+ {
+ // remoteAddressFilter is a whitelist or blacklist.
+ return RemoteAddressFilter.ContainsAddress(remoteIp) == !config.IsRemoteIPFilterBlacklist;
+ }
+ }
+ else if (!IsInLocalNetwork(remoteIp))
+ {
+ // Remote not enabled. So everyone should be LAN.
+ return false;
+ }
+
+ return true;
+ }
+
///
/// Reloads all settings and re-initialises the instance.
///
@@ -599,8 +644,18 @@ namespace Jellyfin.Networking.Manager
var address = IPNetAddress.Parse(parts[0]);
var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
address.Tag = index;
- _interfaceAddresses.AddItem(address);
- _interfaceNames.Add(parts[2], Math.Abs(index));
+ _interfaceAddresses.AddItem(address, false);
+ _interfaceNames[parts[2]] = Math.Abs(index);
+ }
+
+ if (IsIP4Enabled)
+ {
+ _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback);
+ }
+
+ if (IsIP6Enabled)
+ {
+ _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback);
}
}
@@ -933,8 +988,8 @@ namespace Jellyfin.Networking.Manager
}
// Read and parse bind addresses and exclusions, removing ones that don't exist.
- _bindAddresses = CreateIPCollection(lanAddresses).Union(_interfaceAddresses);
- _bindExclusions = CreateIPCollection(lanAddresses, true).Union(_interfaceAddresses);
+ _bindAddresses = CreateIPCollection(lanAddresses).ThatAreContainedInNetworks(_interfaceAddresses);
+ _bindExclusions = CreateIPCollection(lanAddresses, true).ThatAreContainedInNetworks(_interfaceAddresses);
_logger.LogInformation("Using bind addresses: {0}", _bindAddresses.AsString());
_logger.LogInformation("Using bind exclusions: {0}", _bindExclusions.AsString());
}
@@ -1017,7 +1072,7 @@ namespace Jellyfin.Networking.Manager
_logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString());
_logger.LogInformation("Defined LAN exclusions : {0}", _excludedSubnets.AsString());
- _logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets).AsNetworks().AsString());
+ _logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets, true).AsNetworks().AsString());
}
}
@@ -1071,7 +1126,7 @@ namespace Jellyfin.Networking.Manager
nw.Tag *= -1;
}
- _interfaceAddresses.AddItem(nw);
+ _interfaceAddresses.AddItem(nw, false);
// Store interface name so we can use the name in Collections.
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
@@ -1092,7 +1147,7 @@ namespace Jellyfin.Networking.Manager
nw.Tag *= -1;
}
- _interfaceAddresses.AddItem(nw);
+ _interfaceAddresses.AddItem(nw, false);
// Store interface name so we can use the name in Collections.
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
@@ -1108,36 +1163,40 @@ namespace Jellyfin.Networking.Manager
}
#pragma warning restore CA1031 // Do not catch general exception types
}
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error in InitialiseInterfaces.");
+ }
- _logger.LogDebug("Discovered {0} interfaces.", _interfaceAddresses.Count);
- _logger.LogDebug("Interfaces addresses : {0}", _interfaceAddresses.AsString());
+ // If for some reason we don't have an interface info, resolve our DNS name.
+ if (_interfaceAddresses.Count == 0)
+ {
+ _logger.LogError("No interfaces information available. Resolving DNS name.");
+ IPHost host = new IPHost(Dns.GetHostName());
+ foreach (var a in host.GetAddresses())
+ {
+ _interfaceAddresses.AddItem(a);
+ }
- // If for some reason we don't have an interface info, resolve our DNS name.
if (_interfaceAddresses.Count == 0)
{
- _logger.LogError("No interfaces information available. Resolving DNS name.");
- IPHost host = new IPHost(Dns.GetHostName());
- foreach (var a in host.GetAddresses())
- {
- _interfaceAddresses.AddItem(a);
- }
-
- if (_interfaceAddresses.Count == 0)
- {
- _logger.LogWarning("No interfaces information available. Using loopback.");
- // Last ditch attempt - use loopback address.
- _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback);
- if (IsIP6Enabled)
- {
- _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback);
- }
- }
+ _logger.LogWarning("No interfaces information available. Using loopback.");
}
}
- catch (NetworkInformationException ex)
+
+ if (IsIP4Enabled)
{
- _logger.LogError(ex, "Error in InitialiseInterfaces.");
+ _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback);
}
+
+ if (IsIP6Enabled)
+ {
+ _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback);
+ }
+
+ _logger.LogDebug("Discovered {0} interfaces.", _interfaceAddresses.Count);
+ _logger.LogDebug("Interfaces addresses : {0}", _interfaceAddresses.AsString());
}
}
@@ -1206,7 +1265,7 @@ namespace Jellyfin.Networking.Manager
private bool MatchesBindInterface(IPObject source, bool isInExternalSubnet, out string result)
{
result = string.Empty;
- var addresses = _bindAddresses.Exclude(_bindExclusions);
+ var addresses = _bindAddresses.Exclude(_bindExclusions, false);
int count = addresses.Count;
if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any)))
@@ -1291,7 +1350,7 @@ namespace Jellyfin.Networking.Manager
result = string.Empty;
// Get the first WAN interface address that isn't a loopback.
var extResult = _interfaceAddresses
- .Exclude(_bindExclusions)
+ .Exclude(_bindExclusions, false)
.Where(p => !IsInLocalNetwork(p))
.OrderBy(p => p.Tag);
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs
index 2f9f44ed67..8b0bd84c66 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs
@@ -29,20 +29,20 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security
}
///
- public async Task OnEvent(GenericEventArgs e)
+ public async Task OnEvent(GenericEventArgs eventArgs)
{
await _activityManager.CreateAsync(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("AuthenticationSucceededWithUserName"),
- e.Argument.User.Name),
+ eventArgs.Argument.User.Name),
"AuthenticationSucceeded",
- e.Argument.User.Id)
+ eventArgs.Argument.User.Id)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("LabelIpAddressValue"),
- e.Argument.SessionInfo.RemoteEndPoint),
+ eventArgs.Argument.SessionInfo.RemoteEndPoint),
}).ConfigureAwait(false);
}
}
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
index 0340248bbd..aa6015caae 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
@@ -86,7 +86,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
return name;
}
- private static string? GetPlaybackNotificationType(string mediaType)
+ private static string GetPlaybackNotificationType(string mediaType)
{
if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
@@ -98,7 +98,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
return NotificationType.VideoPlayback.ToString();
}
- return null;
+ return "Playback";
}
}
}
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs
index 05201a3469..cbc9f30173 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs
@@ -33,10 +33,10 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
}
///
- public async Task OnEvent(TaskCompletionEventArgs e)
+ public async Task OnEvent(TaskCompletionEventArgs eventArgs)
{
- var result = e.Result;
- var task = e.Task;
+ var result = eventArgs.Result;
+ var task = eventArgs.Task;
if (task.ScheduledTask is IConfigurableScheduledTask activityTask
&& !activityTask.IsLogged)
@@ -54,14 +54,14 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
{
var vals = new List();
- if (!string.IsNullOrEmpty(e.Result.ErrorMessage))
+ if (!string.IsNullOrEmpty(eventArgs.Result.ErrorMessage))
{
- vals.Add(e.Result.ErrorMessage);
+ vals.Add(eventArgs.Result.ErrorMessage);
}
- if (!string.IsNullOrEmpty(e.Result.LongErrorMessage))
+ if (!string.IsNullOrEmpty(eventArgs.Result.LongErrorMessage))
{
- vals.Add(e.Result.LongErrorMessage);
+ vals.Add(eventArgs.Result.LongErrorMessage);
}
await _activityManager.CreateAsync(new ActivityLog(
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs
index 91a30069e8..eb7572ac66 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs
@@ -30,13 +30,13 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
}
///
- public async Task OnEvent(PluginUninstalledEventArgs e)
+ public async Task OnEvent(PluginUninstalledEventArgs eventArgs)
{
await _activityManager.CreateAsync(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("PluginUninstalledWithName"),
- e.Argument.Name),
+ eventArgs.Argument.Name),
NotificationType.PluginUninstalled.ToString(),
Guid.Empty))
.ConfigureAwait(false);
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs
index a14911b94a..9beb6f2f25 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs
@@ -30,12 +30,12 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users
}
///
- public async Task OnEvent(UserUpdatedEventArgs e)
+ public async Task OnEvent(UserUpdatedEventArgs eventArgs)
{
await _sessionManager.SendMessageToUserSessions(
- new List { e.Argument.Id },
+ new List { eventArgs.Argument.Id },
SessionMessageType.UserUpdated,
- _userManager.GetUserDto(e.Argument),
+ _userManager.GetUserDto(eventArgs.Argument),
CancellationToken.None).ConfigureAwait(false);
}
}
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 4f24da0ee4..2c6a176b69 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -6,6 +6,8 @@
true
true
enable
+ AllEnabledByDefault
+ ../jellyfin.ruleset
@@ -14,7 +16,6 @@
-
@@ -26,11 +27,13 @@
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs
new file mode 100644
index 0000000000..d332d19f28
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs
@@ -0,0 +1,520 @@
+#pragma warning disable CS1591
+//
+using System;
+using Jellyfin.Server.Implementations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+ [DbContext(typeof(JellyfinDb))]
+ [Migration("20210407110544_NullableCustomPrefValue")]
+ partial class NullableCustomPrefValue
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("jellyfin")
+ .HasAnnotation("ProductVersion", "5.0.3");
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("DayOfWeek")
+ .HasColumnType("INTEGER");
+
+ b.Property("EndHour")
+ .HasColumnType("REAL");
+
+ b.Property("StartHour")
+ .HasColumnType("REAL");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AccessSchedules");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("DateCreated")
+ .HasColumnType("TEXT");
+
+ b.Property("ItemId")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("LogSeverity")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Overview")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("ShortOverview")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("ActivityLogs");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("UserId", "ItemId", "Client", "Key")
+ .IsUnique();
+
+ b.ToTable("CustomItemDisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChromecastVersion")
+ .HasColumnType("INTEGER");
+
+ b.Property("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("DashboardTheme")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("EnableNextVideoInfoOverlay")
+ .HasColumnType("INTEGER");
+
+ b.Property("IndexBy")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("ScrollDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShowBackdrop")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShowSidebar")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkipBackwardLength")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkipForwardLength")
+ .HasColumnType("INTEGER");
+
+ b.Property("TvHome")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("UserId", "ItemId", "Client")
+ .IsUnique();
+
+ b.ToTable("DisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("DisplayPreferencesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DisplayPreferencesId");
+
+ b.ToTable("HomeSection");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
+ {
+ b.Property