Merge branch 'master' into IsRoot_fix

This commit is contained in:
BaronGreenback 2021-05-22 22:01:03 +01:00 committed by GitHub
commit 51fb6e1d2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
692 changed files with 5898 additions and 4353 deletions

View File

@ -1,59 +0,0 @@
parameters:
- name: LinuxImage
type: string
default: "ubuntu-latest"
- name: GeneratorVersion
type: string
default: "5.0.1"
jobs:
- job: GenerateApiClients
displayName: 'Generate Api Clients'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
dependsOn: Test
pool:
vmImage: "${{ parameters.LinuxImage }}"
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download OpenAPI Spec Artifact'
inputs:
source: 'current'
artifact: "OpenAPI Spec"
path: "$(System.ArtifactsDirectory)/openapispec"
runVersion: "latest"
- task: CmdLine@2
displayName: 'Download OpenApi Generator'
inputs:
script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar"
## Authenticate with npm registry
- task: npmAuthenticate@0
inputs:
workingFile: ./.npmrc
customEndpoint: 'jellyfin-bot for NPM'
## Generate npm api client
- task: CmdLine@2
displayName: 'Build stable typescript axios client'
inputs:
script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)"
## Run npm install
- task: Npm@1
displayName: 'Install npm dependencies'
inputs:
command: install
workingDir: ./apiclient/generated/typescript/axios
## Publish npm packages
- task: Npm@1
displayName: 'Publish stable typescript axios client'
inputs:
command: custom
customCommand: publish --access public
publishRegistry: useExternalRegistry
publishEndpoint: 'jellyfin-bot for NPM'
workingDir: ./apiclient/generated/typescript/axios

View File

@ -61,6 +61,3 @@ jobs:
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}: - ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-package.yml - template: azure-pipelines-package.yml
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-api-client.yml

43
.github/label-commenter-config.yml vendored Normal file
View File

@ -0,0 +1,43 @@
comment:
header: Hello @{{ issue.user.login }}
footer: "\
---\n\n
> This is an automated comment created by the [peaceiris/actions-label-commenter]. \
Responding to the bot or mentioning it won't have any effect.\n\n
[peaceiris/actions-label-commenter]: https://github.com/peaceiris/actions-label-commenter
"
labels:
- name: stable backport
labeled:
pr:
body: |
This pull request has been tagged as a stable backport. It will be cherry-picked into the next stable point release.
Please observe the following:
* Any dependent PRs that this PR requires **must** be tagged for stable backporting as well.
* Any issue(s) this PR fixes or closes **should** target the current stable release or a previous stable release to which a fix has not yet entered the current stable release.
* This PR **must** be test cherry-picked against the current release branch (`release-X.Y.z` where X and Y are numbers). It must apply cleanly, or a diff of the expected change must be provided.
To do this, run the following commands from your local copy of the Jellyfin repository:
1. `git checkout master`
1. `git merge --no-ff <myPullRequestBranch>`
1. `git log` -> `commit xxxxxxxxx`, grab hash
1. `git checkout release-X.Y.z` replacing X and Y with the *current* stable version (e.g. `release-10.7.z`)
1. `git cherry-pick -sx -m1 <hash>`
Ensure the `cherry-pick` applies cleanly. If it does not, fix any merge conflicts *preserving as much of the original code as possible*, and make note of the resulting diff.
Test your changes with a build to ensure they are successful. If not, adjust the diff accordingly.
**Do not** push your merges to either branch. Use `git reset --hard HEAD~1` to revert both branches to their original state.
Reply to this PR with a comment beginning "Cherry-pick test completed." and including the merge-conflict-fixing diff(s) if applicable.

View File

@ -2,8 +2,6 @@ name: Automation
on: on:
pull_request: pull_request:
issues:
issue_comment:
jobs: jobs:
main: main:
@ -16,31 +14,31 @@ jobs:
label: stable backport label: stable backport
- name: Remove from 'Current Release' project - name: Remove from 'Current Release' project
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel
continue-on-error: true continue-on-error: true
with: with:
project: Current Release project: Current Release
action: delete action: delete
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Release Next' project - name: Add to 'Release Next' project
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened' if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
continue-on-error: true continue-on-error: true
with: with:
project: Release Next project: Release Next
column: In progress column: In progress
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Current Release' project - name: Add to 'Current Release' project
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel
continue-on-error: true continue-on-error: true
with: with:
project: Current Release project: Current Release
column: In progress column: In progress
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Check number of comments from the team member - name: Check number of comments from the team member
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
@ -48,19 +46,19 @@ jobs:
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)" 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 - name: Move issue to needs triage
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1 if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
continue-on-error: true continue-on-error: true
with: with:
project: Issue Triage for Main Repo project: Issue Triage for Main Repo
column: Needs triage column: Needs triage
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add issue to triage project - name: Add issue to triage project
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: github.event.issue.pull_request == '' && github.event.action == 'opened' if: github.event.issue.pull_request == '' && github.event.action == 'opened'
continue-on-error: true continue-on-error: true
with: with:
project: Issue Triage for Main Repo project: Issue Triage for Main Repo
column: Pending response column: Pending response
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}

22
.github/workflows/label-commenter.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Label Commenter
on:
issues:
types:
- labeled
- unlabeled
pull_request_target:
types:
- labeled
- unlabeled
jobs:
comment:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
ref: master
- name: Label Commenter
uses: peaceiris/actions-label-commenter@v1

View File

@ -14,4 +14,4 @@ jobs:
- uses: eps1lon/actions-label-merge-conflict@v2.0.1 - uses: eps1lon/actions-label-merge-conflict@v2.0.1
with: with:
dirtyLabel: 'merge conflict' dirtyLabel: 'merge conflict'
repoToken: ${{ secrets.GH_TOKEN }} repoToken: ${{ secrets.JF_BOT_TOKEN }}

View File

@ -11,17 +11,17 @@ jobs:
- name: Notify as seen - name: Notify as seen
uses: peter-evans/create-or-update-comment@v1.4.5 uses: peter-evans/create-or-update-comment@v1.4.5
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }} comment-id: ${{ github.event.comment.id }}
reactions: '+1' reactions: '+1'
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
- name: Automatic Rebase - name: Automatic Rebase
uses: cirrus-actions/rebase@1.4 uses: cirrus-actions/rebase@1.4
env: env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}

View File

@ -1,11 +1,11 @@
ARG DOTNET_VERSION=5.0 ARG DOTNET_VERSION=5.0
FROM node:alpine as web-builder FROM node:lts-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master 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 \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& npm ci --no-audit \ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist && mv dist /dist
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder

View File

@ -5,12 +5,12 @@
ARG DOTNET_VERSION=5.0 ARG DOTNET_VERSION=5.0
FROM node:alpine as web-builder FROM node:lts-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master 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 \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& npm ci --no-audit \ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist && mv dist /dist

View File

@ -5,12 +5,12 @@
ARG DOTNET_VERSION=5.0 ARG DOTNET_VERSION=5.0
FROM node:alpine as web-builder FROM node:lts-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master 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 \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& npm ci --no-audit \ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist && mv dist /dist

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
namespace Emby.Dlna.Configuration namespace Emby.Dlna.Configuration

View File

@ -1,4 +1,3 @@
#nullable enable
#pragma warning disable CS1591 #pragma warning disable CS1591
using Emby.Dlna.Configuration; using Emby.Dlna.Configuration;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.IO; using System.IO;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -976,15 +978,28 @@ namespace Emby.Dlna.Didl
return; return;
} }
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg"); // TODO: Remove these default values
var albumArtUrlInfo = GetImageUrl(
imageInfo,
_profile.MaxAlbumArtWidth ?? 10000,
_profile.MaxAlbumArtHeight ?? 10000,
"jpg");
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp); writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
if (!string.IsNullOrEmpty(_profile.AlbumArtPn))
{
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn); writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
writer.WriteString(albumartUrlInfo.url); }
writer.WriteString(albumArtUrlInfo.url);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
// TOOD: Remove these default values // TODO: Remove these default values
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg"); var iconUrlInfo = GetImageUrl(
imageInfo,
_profile.MaxIconWidth ?? 48,
_profile.MaxIconHeight ?? 48,
"jpg");
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url); writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
if (!_profile.EnableAlbumArtInDidl) if (!_profile.EnableAlbumArtInDidl)
@ -1207,8 +1222,7 @@ namespace Emby.Dlna.Didl
if (width.HasValue && height.HasValue) if (width.HasValue && height.HasValue)
{ {
var newSize = DrawingUtils.Resize( var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
width = newSize.Width; width = newSize.Width;
height = newSize.Height; height = newSize.Height;

View File

@ -9,7 +9,7 @@ namespace Emby.Dlna.Didl
{ {
public class StringWriterWithEncoding : StringWriter public class StringWriterWithEncoding : StringWriter
{ {
private readonly Encoding _encoding; private readonly Encoding? _encoding;
public StringWriterWithEncoding() public StringWriterWithEncoding()
{ {

View File

@ -1,4 +1,3 @@
#nullable enable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -111,7 +113,7 @@ namespace Emby.Dlna
if (profile != null) if (profile != null)
{ {
_logger.LogDebug("Found matching device profile: {0}", profile.Name); _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
} }
else else
{ {
@ -138,80 +140,45 @@ namespace Emby.Dlna
_logger.LogInformation(builder.ToString()); _logger.LogInformation(builder.ToString());
} }
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) /// <summary>
/// Attempts to match a device with a profile.
/// Rules:
/// - If the profile field has no value, the field matches irregardless of its contents.
/// - the profile field can be an exact match, or a reg exp.
/// </summary>
/// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
/// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param>
/// <returns><b>True</b> if they match.</returns>
public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
{ {
if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
{ && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
{ && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
return false; && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
} && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
} && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
&& IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{
if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{
if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{
if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelName))
{
if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{
if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
{
if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{
if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
{
return false;
}
}
return true;
} }
private bool IsRegexOrSubstringMatch(string input, string pattern) private bool IsRegexOrSubstringMatch(string input, string pattern)
{ {
if (string.IsNullOrEmpty(pattern))
{
// In profile identification: An empty pattern matches anything.
return true;
}
if (string.IsNullOrEmpty(input))
{
// The profile contains a value, and the device doesn't.
return false;
}
try try
{ {
return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); return input.Equals(pattern, StringComparison.OrdinalIgnoreCase)
|| Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {

View File

@ -21,6 +21,7 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<!-- Code Analyzers--> <!-- Code Analyzers-->

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -188,7 +190,7 @@ namespace Emby.Dlna.PlayTo
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName); _sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
string serverAddress = _appHost.GetSmartApiUrl(info.LocalIpAddress); string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIpAddress);
controller = new PlayToController( controller = new PlayToController(
sessionInfo, sessionInfo,

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.IO; using System.IO;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -46,7 +46,7 @@ namespace Emby.Dlna.PlayTo
{ {
var serviceAction = new ServiceAction var serviceAction = new ServiceAction
{ {
Name = container.GetValue(UPnpNamespaces.Svc + "name"), Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
}; };
var argumentList = serviceAction.ArgumentList; var argumentList = serviceAction.ArgumentList;
@ -68,9 +68,9 @@ namespace Emby.Dlna.PlayTo
return new Argument return new Argument
{ {
Name = container.GetValue(UPnpNamespaces.Svc + "name"), Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
Direction = container.GetValue(UPnpNamespaces.Svc + "direction"), Direction = container.GetValue(UPnpNamespaces.Svc + "direction") ?? string.Empty,
RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") ?? string.Empty
}; };
} }
@ -89,8 +89,8 @@ namespace Emby.Dlna.PlayTo
return new StateVariable return new StateVariable
{ {
Name = container.GetValue(UPnpNamespaces.Svc + "name"), Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"), DataType = container.GetValue(UPnpNamespaces.Svc + "dataType") ?? string.Empty,
AllowedValues = allowedValues AllowedValues = allowedValues
}; };
} }
@ -166,7 +166,7 @@ namespace Emby.Dlna.PlayTo
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString); return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString);
} }
private string BuildArgumentXml(Argument argument, string value, string commandParameter = "") private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "")
{ {
var state = StateVariables.FirstOrDefault(a => string.Equals(a.Name, argument.RelatedStateVariable, StringComparison.OrdinalIgnoreCase)); var state = StateVariables.FirstOrDefault(a => string.Equals(a.Name, argument.RelatedStateVariable, StringComparison.OrdinalIgnoreCase));

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -250,7 +250,8 @@ namespace Emby.Dlna.Server
url = _serverAddress.TrimEnd('/') + "/dlna/" + _serverUdn + "/" + url.TrimStart('/'); url = _serverAddress.TrimEnd('/') + "/dlna/" + _serverUdn + "/" + url.TrimStart('/');
return SecurityElement.Escape(url); // TODO: @bond remove null-coalescing operator when https://github.com/dotnet/runtime/pull/52442 is merged/released
return SecurityElement.Escape(url) ?? string.Empty;
} }
private IEnumerable<DeviceIcon> GetIcons() private IEnumerable<DeviceIcon> GetIcons()

View File

@ -47,7 +47,7 @@ namespace Emby.Dlna.Service
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request) private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
{ {
ControlRequestInfo requestInfo = null; ControlRequestInfo? requestInfo = null;
using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8)) using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
{ {
@ -151,7 +151,7 @@ namespace Emby.Dlna.Service
private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader) private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
{ {
string namespaceURI = null, localName = null; string? namespaceURI = null, localName = null;
await reader.MoveToContentAsync().ConfigureAwait(false); await reader.MoveToContentAsync().ConfigureAwait(false);
await reader.ReadAsync().ConfigureAwait(false); await reader.ReadAsync().ConfigureAwait(false);

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -104,7 +106,7 @@ namespace Emby.Dlna.Ssdp
{ {
Location = e.DiscoveredDevice.DescriptionLocation, Location = e.DiscoveredDevice.DescriptionLocation,
Headers = headers, Headers = headers,
LocalIpAddress = e.LocalIpAddress RemoteIpAddress = e.RemoteIpAddress
}); });
DeviceDiscoveredInternal?.Invoke(this, args); DeviceDiscoveredInternal?.Invoke(this, args);

View File

@ -7,21 +7,21 @@ namespace Emby.Dlna.Ssdp
{ {
public static class SsdpExtensions public static class SsdpExtensions
{ {
public static string GetValue(this XElement container, XName name) public static string? GetValue(this XElement container, XName name)
{ {
var node = container.Element(name); var node = container.Element(name);
return node?.Value; return node?.Value;
} }
public static string GetAttributeValue(this XElement container, XName name) public static string? GetAttributeValue(this XElement container, XName name)
{ {
var node = container.Attribute(name); var node = container.Attribute(name);
return node?.Value; return node?.Value;
} }
public static string GetDescendantValue(this XElement container, XName name) public static string? GetDescendantValue(this XElement container, XName name)
=> container.Descendants(name).FirstOrDefault()?.Value; => container.Descendants(name).FirstOrDefault()?.Value;
} }
} }

View File

@ -1,7 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using MediaBrowser.Common.Extensions;
namespace Emby.Naming.Audio namespace Emby.Naming.Audio
{ {
@ -18,8 +18,8 @@ namespace Emby.Naming.Audio
/// <returns>True if file at path is audio file.</returns> /// <returns>True if file at path is audio file.</returns>
public static bool IsAudioFile(string path, NamingOptions options) public static bool IsAudioFile(string path, NamingOptions options)
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path.AsSpan());
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); return options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
} }
} }
} }

View File

@ -23,11 +23,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\SharedVersion.cs" /> <Compile Include="../SharedVersion.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="../MediaBrowser.Common/MediaBrowser.Common.csproj" />
<ProjectReference Include="../MediaBrowser.Model/MediaBrowser.Model.csproj" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -68,6 +68,11 @@ namespace Emby.Naming.TV
var parsingResult = new EpisodePathParser(_options) var parsingResult = new EpisodePathParser(_options)
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); .Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
if (!parsingResult.Success && !isStub)
{
return null;
}
return new EpisodeInfo(path) return new EpisodeInfo(path)
{ {
Container = container, Container = container,

View File

@ -29,36 +29,33 @@ namespace Emby.Naming.Video
/// <param name="path">Path to file.</param> /// <param name="path">Path to file.</param>
/// <returns>Returns <see cref="ExtraResult"/> object.</returns> /// <returns>Returns <see cref="ExtraResult"/> object.</returns>
public ExtraResult GetExtraInfo(string path) public ExtraResult GetExtraInfo(string path)
{
return _options.VideoExtraRules
.Select(i => GetExtraInfo(path, i))
.FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
}
private ExtraResult GetExtraInfo(string path, ExtraRule rule)
{ {
var result = new ExtraResult(); var result = new ExtraResult();
for (var i = 0; i < _options.VideoExtraRules.Length; i++)
{
var rule = _options.VideoExtraRules[i];
if (rule.MediaType == MediaType.Audio) if (rule.MediaType == MediaType.Audio)
{ {
if (!AudioFileParser.IsAudioFile(path, _options)) if (!AudioFileParser.IsAudioFile(path, _options))
{ {
return result; continue;
} }
} }
else if (rule.MediaType == MediaType.Video) else if (rule.MediaType == MediaType.Video)
{ {
if (!new VideoResolver(_options).IsVideoFile(path)) if (!new VideoResolver(_options).IsVideoFile(path))
{ {
return result; continue;
} }
} }
var pathSpan = path.AsSpan();
if (rule.RuleType == ExtraRuleType.Filename) if (rule.RuleType == ExtraRuleType.Filename)
{ {
var filename = Path.GetFileNameWithoutExtension(path); var filename = Path.GetFileNameWithoutExtension(pathSpan);
if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase)) if (filename.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
{ {
result.ExtraType = rule.ExtraType; result.ExtraType = rule.ExtraType;
result.Rule = rule; result.Rule = rule;
@ -66,9 +63,9 @@ namespace Emby.Naming.Video
} }
else if (rule.RuleType == ExtraRuleType.Suffix) else if (rule.RuleType == ExtraRuleType.Suffix)
{ {
var filename = Path.GetFileNameWithoutExtension(path); var filename = Path.GetFileNameWithoutExtension(pathSpan);
if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0) if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase))
{ {
result.ExtraType = rule.ExtraType; result.ExtraType = rule.ExtraType;
result.Rule = rule; result.Rule = rule;
@ -88,14 +85,20 @@ namespace Emby.Naming.Video
} }
else if (rule.RuleType == ExtraRuleType.DirectoryName) else if (rule.RuleType == ExtraRuleType.DirectoryName)
{ {
var directoryName = Path.GetFileName(Path.GetDirectoryName(path)); var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan));
if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase)) if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
{ {
result.ExtraType = rule.ExtraType; result.ExtraType = rule.ExtraType;
result.Rule = rule; result.Rule = rule;
} }
} }
if (result.ExtraType != null)
{
return result;
}
}
return result; return result;
} }
} }

View File

@ -1,8 +1,8 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using MediaBrowser.Common.Extensions;
namespace Emby.Naming.Video namespace Emby.Naming.Video
{ {
@ -59,15 +59,15 @@ namespace Emby.Naming.Video
} }
bool isStub = false; bool isStub = false;
string? container = null; ReadOnlySpan<char> container = ReadOnlySpan<char>.Empty;
string? stubType = null; string? stubType = null;
if (!isDirectory) if (!isDirectory)
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path.AsSpan());
// Check supported extensions // Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{ {
// It's not supported. Check stub extensions // It's not supported. Check stub extensions
if (!StubResolver.TryResolveFile(path, _options, out stubType)) if (!StubResolver.TryResolveFile(path, _options, out stubType))
@ -86,9 +86,7 @@ namespace Emby.Naming.Video
var extraResult = new ExtraResolver(_options).GetExtraInfo(path); var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
var name = isDirectory var name = Path.GetFileNameWithoutExtension(path);
? Path.GetFileName(path)
: Path.GetFileNameWithoutExtension(path);
int? year = null; int? year = null;
@ -107,7 +105,7 @@ namespace Emby.Naming.Video
return new VideoFileInfo( return new VideoFileInfo(
path: path, path: path,
container: container, container: container.IsEmpty ? null : container.ToString(),
isStub: isStub, isStub: isStub,
name: name, name: name,
year: year, year: year,
@ -126,8 +124,8 @@ namespace Emby.Naming.Video
/// <returns>True if is video file.</returns> /// <returns>True if is video file.</returns>
public bool IsVideoFile(string path) public bool IsVideoFile(string path)
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path.AsSpan());
return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>
@ -137,8 +135,8 @@ namespace Emby.Naming.Video
/// <returns>True if is video file stub.</returns> /// <returns>True if is video file stub.</returns>
public bool IsStubFile(string path) public bool IsStubFile(string path)
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path.AsSpan());
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>

View File

@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.AppBase
CachePath = cacheDirectoryPath; CachePath = cacheDirectoryPath;
WebPath = webDirectoryPath; WebPath = webDirectoryPath;
DataPath = Path.Combine(ProgramDataPath, "data"); _dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName;
} }
/// <summary> /// <summary>
@ -55,11 +55,7 @@ namespace Emby.Server.Implementations.AppBase
/// Gets the folder path to the data directory. /// Gets the folder path to the data directory.
/// </summary> /// </summary>
/// <value>The data directory.</value> /// <value>The data directory.</value>
public string DataPath public string DataPath => _dataPath;
{
get => _dataPath;
private set => _dataPath = Directory.CreateDirectory(value).FullName;
}
/// <inheritdoc /> /// <inheritdoc />
public string VirtualDataPath => "%AppDataPath%"; public string VirtualDataPath => "%AppDataPath%";

View File

@ -1,3 +1,5 @@
#nullable disable
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -1,5 +1,3 @@
#nullable enable
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -335,10 +337,7 @@ namespace Emby.Server.Implementations
{ {
get get
{ {
if (_deviceId == null) _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
{
_deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
}
return _deviceId.Value; return _deviceId.Value;
} }
@ -370,10 +369,7 @@ namespace Emby.Server.Implementations
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
protected object CreateInstanceSafe(Type type) protected object CreateInstanceSafe(Type type)
{ {
if (_creatingInstances == null) _creatingInstances ??= new List<Type>();
{
_creatingInstances = new List<Type>();
}
if (_creatingInstances.IndexOf(type) != -1) if (_creatingInstances.IndexOf(type) != -1)
{ {

View File

@ -1,3 +1,5 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;

View File

@ -82,9 +82,9 @@ namespace Emby.Server.Implementations.Collections
return null; return null;
}) })
.Where(i => i != null) .Where(i => i != null)
.GroupBy(x => x.Id) .GroupBy(x => x!.Id) // We removed the null values
.Select(x => x.First()) .Select(x => x.First())
.ToList(); .ToList()!; // Again... the list doesn't contain any null values
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,3 +1,5 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -164,7 +166,7 @@ namespace Emby.Server.Implementations.Collections
parentFolder.AddChild(collection, CancellationToken.None); parentFolder.AddChild(collection, CancellationToken.None);
if (options.ItemIdList.Length > 0) if (options.ItemIdList.Count > 0)
{ {
await AddToCollectionAsync( await AddToCollectionAsync(
collection.Id, collection.Id,
@ -248,11 +250,7 @@ namespace Emby.Server.Implementations.Collections
if (fireEvent) if (fireEvent)
{ {
ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
{
Collection = collection,
ItemsChanged = itemList
});
} }
} }
} }
@ -304,11 +302,7 @@ namespace Emby.Server.Implementations.Collections
}, },
RefreshPriority.High); RefreshPriority.High);
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
{
Collection = collection,
ItemsChanged = itemList
});
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,3 +1,5 @@
#nullable disable
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;

View File

@ -1,5 +1,3 @@
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography; using System.Security.Cryptography;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -181,11 +183,9 @@ namespace Emby.Server.Implementations.Data
foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
{ {
if (row[1].SQLiteType != SQLiteType.Null) if (row.TryGetString(1, out var columnName))
{ {
var name = row[1].ToString(); columnNames.Add(columnName);
columnNames.Add(name);
} }
} }

View File

@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Data
{ {
public class ManagedConnection : IDisposable public class ManagedConnection : IDisposable
{ {
private SQLiteDatabaseConnection _db; private SQLiteDatabaseConnection? _db;
private readonly SemaphoreSlim _writeLock; private readonly SemaphoreSlim _writeLock;
private bool _disposed = false; private bool _disposed = false;
@ -54,12 +54,12 @@ namespace Emby.Server.Implementations.Data
return _db.RunInTransaction(action, mode); return _db.RunInTransaction(action, mode);
} }
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql) public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql)
{ {
return _db.Query(sql); return _db.Query(sql);
} }
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql, params object[] values) public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql, params object[] values)
{ {
return _db.Query(sql, values); return _db.Query(sql, values);
} }

View File

@ -1,3 +1,4 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Data
}); });
} }
public static Guid ReadGuidFromBlob(this IResultSetValue result) public static Guid ReadGuidFromBlob(this ResultSetValue result)
{ {
return new Guid(result.ToBlob()); return new Guid(result.ToBlob());
} }
@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Data
private static string GetDateTimeKindFormat(DateTimeKind kind) private static string GetDateTimeKindFormat(DateTimeKind kind)
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal; => (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
public static DateTime ReadDateTime(this IResultSetValue result) public static DateTime ReadDateTime(this ResultSetValue result)
{ {
var dateText = result.ToString(); var dateText = result.ToString();
@ -96,49 +97,139 @@ namespace Emby.Server.Implementations.Data
DateTimeStyles.None).ToUniversalTime(); DateTimeStyles.None).ToUniversalTime();
} }
public static DateTime? TryReadDateTime(this IResultSetValue result) public static bool TryReadDateTime(this IReadOnlyList<ResultSetValue> reader, int index, out DateTime result)
{ {
var dateText = result.ToString(); var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
var dateText = item.ToString();
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult)) if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult))
{ {
return dateTimeResult.ToUniversalTime(); result = dateTimeResult.ToUniversalTime();
return true;
} }
return null; result = default;
return false;
} }
public static bool IsDBNull(this IReadOnlyList<IResultSetValue> result, int index) public static bool TryGetGuid(this IReadOnlyList<ResultSetValue> reader, int index, out Guid result)
{ {
return result[index].SQLiteType == SQLiteType.Null; var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
} }
public static string GetString(this IReadOnlyList<IResultSetValue> result, int index) result = item.ReadGuidFromBlob();
return true;
}
public static bool IsDbNull(this ResultSetValue result)
{
return result.SQLiteType == SQLiteType.Null;
}
public static string GetString(this IReadOnlyList<ResultSetValue> result, int index)
{ {
return result[index].ToString(); return result[index].ToString();
} }
public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index) public static bool TryGetString(this IReadOnlyList<ResultSetValue> reader, int index, out string result)
{
result = null;
var item = reader[index];
if (item.IsDbNull())
{
return false;
}
result = item.ToString();
return true;
}
public static bool GetBoolean(this IReadOnlyList<ResultSetValue> result, int index)
{ {
return result[index].ToBool(); return result[index].ToBool();
} }
public static int GetInt32(this IReadOnlyList<IResultSetValue> result, int index) public static bool TryGetBoolean(this IReadOnlyList<ResultSetValue> reader, int index, out bool result)
{ {
return result[index].ToInt(); var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
} }
public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index) result = item.ToBool();
return true;
}
public static bool TryGetInt32(this IReadOnlyList<ResultSetValue> reader, int index, out int result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToInt();
return true;
}
public static long GetInt64(this IReadOnlyList<ResultSetValue> result, int index)
{ {
return result[index].ToInt64(); return result[index].ToInt64();
} }
public static float GetFloat(this IReadOnlyList<IResultSetValue> result, int index) public static bool TryGetInt64(this IReadOnlyList<ResultSetValue> reader, int index, out long result)
{ {
return result[index].ToFloat(); var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
} }
public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index) result = item.ToInt64();
return true;
}
public static bool TryGetSingle(this IReadOnlyList<ResultSetValue> reader, int index, out float result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToFloat();
return true;
}
public static bool TryGetDouble(this IReadOnlyList<ResultSetValue> reader, int index, out double result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToDouble();
return true;
}
public static Guid GetGuid(this IReadOnlyList<ResultSetValue> result, int index)
{ {
return result[index].ReadGuidFromBlob(); return result[index].ReadGuidFromBlob();
} }
@ -350,7 +441,7 @@ namespace Emby.Server.Implementations.Data
} }
} }
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement) public static IEnumerable<IReadOnlyList<ResultSetValue>> ExecuteQuery(this IStatement statement)
{ {
while (statement.MoveNext()) while (statement.MoveNext())
{ {

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -348,16 +350,16 @@ namespace Emby.Server.Implementations.Data
/// Read a row from the specified reader into the provided userData object. /// Read a row from the specified reader into the provided userData object.
/// </summary> /// </summary>
/// <param name="reader"></param> /// <param name="reader"></param>
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader) private UserItemData ReadRow(IReadOnlyList<ResultSetValue> reader)
{ {
var userData = new UserItemData(); var userData = new UserItemData();
userData.Key = reader[0].ToString(); userData.Key = reader[0].ToString();
// userData.UserId = reader[1].ReadGuidFromBlob(); // userData.UserId = reader[1].ReadGuidFromBlob();
if (reader[2].SQLiteType != SQLiteType.Null) if (reader.TryGetDouble(2, out var rating))
{ {
userData.Rating = reader[2].ToDouble(); userData.Rating = rating;
} }
userData.Played = reader[3].ToBool(); userData.Played = reader[3].ToBool();
@ -365,19 +367,19 @@ namespace Emby.Server.Implementations.Data
userData.IsFavorite = reader[5].ToBool(); userData.IsFavorite = reader[5].ToBool();
userData.PlaybackPositionTicks = reader[6].ToInt64(); userData.PlaybackPositionTicks = reader[6].ToInt64();
if (reader[7].SQLiteType != SQLiteType.Null) if (reader.TryReadDateTime(7, out var lastPlayedDate))
{ {
userData.LastPlayedDate = reader[7].TryReadDateTime(); userData.LastPlayedDate = lastPlayedDate;
} }
if (reader[8].SQLiteType != SQLiteType.Null) if (reader.TryGetInt32(8, out var audioStreamIndex))
{ {
userData.AudioStreamIndex = reader[8].ToInt(); userData.AudioStreamIndex = audioStreamIndex;
} }
if (reader[9].SQLiteType != SQLiteType.Null) if (reader.TryGetInt32(9, out var subtitleStreamIndex))
{ {
userData.SubtitleStreamIndex = reader[9].ToInt(); userData.SubtitleStreamIndex = subtitleStreamIndex;
} }
return userData; return userData;

View File

@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Data
/// This holds all the types in the running assemblies /// This holds all the types in the running assemblies
/// so that we can de-serialize properly when we don't have strong types. /// so that we can de-serialize properly when we don't have strong types.
/// </summary> /// </summary>
private readonly ConcurrentDictionary<string, Type> _typeMap = new ConcurrentDictionary<string, Type>(); private readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
/// <summary> /// <summary>
/// Gets the type. /// Gets the type.
@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Data
/// <param name="typeName">Name of the type.</param> /// <param name="typeName">Name of the type.</param>
/// <returns>Type.</returns> /// <returns>Type.</returns>
/// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception> /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
public Type GetType(string typeName) public Type? GetType(string typeName)
{ {
if (string.IsNullOrEmpty(typeName)) if (string.IsNullOrEmpty(typeName))
{ {
@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Data
/// </summary> /// </summary>
/// <param name="typeName">Name of the type.</param> /// <param name="typeName">Name of the type.</param>
/// <returns>Type.</returns> /// <returns>Type.</returns>
private Type LookupType(string typeName) private Type? LookupType(string typeName)
{ {
return AppDomain.CurrentDomain.GetAssemblies() return AppDomain.CurrentDomain.GetAssemblies()
.Select(a => a.GetType(typeName)) .Select(a => a.GetType(typeName))

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -665,10 +667,7 @@ namespace Emby.Server.Implementations.Dto
var tag = GetImageCacheTag(item, image); var tag = GetImageCacheTag(item, image);
if (!string.IsNullOrEmpty(image.BlurHash)) if (!string.IsNullOrEmpty(image.BlurHash))
{ {
if (dto.ImageBlurHashes == null) dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
if (!dto.ImageBlurHashes.ContainsKey(image.Type)) if (!dto.ImageBlurHashes.ContainsKey(image.Type))
{ {
@ -702,10 +701,7 @@ namespace Emby.Server.Implementations.Dto
if (hashes.Count > 0) if (hashes.Count > 0)
{ {
if (dto.ImageBlurHashes == null) dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
dto.ImageBlurHashes[imageType] = hashes; dto.ImageBlurHashes[imageType] = hashes;
} }
@ -898,10 +894,7 @@ namespace Emby.Server.Implementations.Dto
dto.Taglines = new string[] { item.Tagline }; dto.Taglines = new string[] { item.Tagline };
} }
if (dto.Taglines == null) dto.Taglines ??= Array.Empty<string>();
{
dto.Taglines = Array.Empty<string>();
}
} }
dto.Type = item.GetBaseItemKind(); dto.Type = item.GetBaseItemKind();

View File

@ -27,11 +27,11 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" />
<PackageReference Include="Mono.Nat" Version="3.0.1" /> <PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
<PackageReference Include="sharpcompress" Version="0.28.1" /> <PackageReference Include="sharpcompress" Version="0.28.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.0.1" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" /> <PackageReference Include="DotNet.Glob" Version="3.1.2" />
</ItemGroup> </ItemGroup>
@ -44,6 +44,7 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors> <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 --> <!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
<NoWarn>AD0001</NoWarn> <NoWarn>AD0001</NoWarn>
<AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode> <AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode>

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,5 +1,3 @@
#nullable enable
using System; using System;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -2,8 +2,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{ {
if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached)) if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached))
{ {
return (AuthorizationInfo)cached; return (AuthorizationInfo)cached!; // Cache should never contain null
} }
return GetAuthorization(requestContext); return GetAuthorization(requestContext);
@ -55,15 +55,15 @@ namespace Emby.Server.Implementations.HttpServer.Security
} }
private AuthorizationInfo GetAuthorizationInfoFromDictionary( private AuthorizationInfo GetAuthorizationInfoFromDictionary(
in Dictionary<string, string> auth, in Dictionary<string, string>? auth,
in IHeaderDictionary headers, in IHeaderDictionary headers,
in IQueryCollection queryString) in IQueryCollection queryString)
{ {
string deviceId = null; string? deviceId = null;
string device = null; string? device = null;
string client = null; string? client = null;
string version = null; string? version = null;
string token = null; string? token = null;
if (auth != null) if (auth != null)
{ {
@ -206,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary> /// </summary>
/// <param name="httpReq">The HTTP req.</param> /// <param name="httpReq">The HTTP req.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns> /// <returns>Dictionary{System.StringSystem.String}.</returns>
private Dictionary<string, string> GetAuthorizationDictionary(HttpContext httpReq) private Dictionary<string, string>? GetAuthorizationDictionary(HttpContext httpReq)
{ {
var auth = httpReq.Request.Headers["X-Emby-Authorization"]; var auth = httpReq.Request.Headers["X-Emby-Authorization"];
@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
auth = httpReq.Request.Headers[HeaderNames.Authorization]; auth = httpReq.Request.Headers[HeaderNames.Authorization];
} }
return GetAuthorization(auth); return GetAuthorization(auth.Count > 0 ? auth[0] : null);
} }
/// <summary> /// <summary>
@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary> /// </summary>
/// <param name="httpReq">The HTTP req.</param> /// <param name="httpReq">The HTTP req.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns> /// <returns>Dictionary{System.StringSystem.String}.</returns>
private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq) private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq)
{ {
var auth = httpReq.Headers["X-Emby-Authorization"]; var auth = httpReq.Headers["X-Emby-Authorization"];
@ -232,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
auth = httpReq.Headers[HeaderNames.Authorization]; auth = httpReq.Headers[HeaderNames.Authorization];
} }
return GetAuthorization(auth); return GetAuthorization(auth.Count > 0 ? auth[0] : null);
} }
/// <summary> /// <summary>
@ -240,43 +240,43 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary> /// </summary>
/// <param name="authorizationHeader">The authorization header.</param> /// <param name="authorizationHeader">The authorization header.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns> /// <returns>Dictionary{System.StringSystem.String}.</returns>
private Dictionary<string, string> GetAuthorization(string authorizationHeader) private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader)
{ {
if (authorizationHeader == null) if (authorizationHeader == null)
{ {
return null; return null;
} }
var parts = authorizationHeader.Split(' ', 2); var firstSpace = authorizationHeader.IndexOf(' ');
// There should be at least to parts // There should be at least two parts
if (parts.Length != 2) if (firstSpace == -1)
{ {
return null; return null;
} }
var acceptedNames = new[] { "MediaBrowser", "Emby" }; var name = authorizationHeader[..firstSpace];
// It has to be a digest request if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase)
if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase)) && !name.Equals("Emby", StringComparison.OrdinalIgnoreCase))
{ {
return null; return null;
} }
// Remove uptil the first space authorizationHeader = authorizationHeader[(firstSpace + 1)..];
authorizationHeader = parts[1];
parts = authorizationHeader.Split(',');
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in parts) foreach (var item in authorizationHeader.Split(','))
{ {
var param = item.Trim().Split('=', 2); var trimmedItem = item.Trim();
var firstEqualsSign = trimmedItem.IndexOf('=');
if (param.Length == 2) if (firstEqualsSign > 0)
{ {
var value = NormalizeValue(param[1].Trim('"')); var key = trimmedItem[..firstEqualsSign].ToString();
result[param[0]] = value; var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString());
result[key] = value;
} }
} }

View File

@ -36,14 +36,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
return GetSession((HttpContext)requestContext); return GetSession((HttpContext)requestContext);
} }
public User GetUser(HttpContext requestContext) public User? GetUser(HttpContext requestContext)
{ {
var session = GetSession(requestContext); var session = GetSession(requestContext);
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
} }
public User GetUser(object requestContext) public User? GetUser(object requestContext)
{ {
return GetUser(((HttpRequest)requestContext).HttpContext); return GetUser(((HttpRequest)requestContext).HttpContext);
} }

View File

@ -1,5 +1,3 @@
#nullable enable
using System; using System;
using System.Buffers; using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -2,11 +2,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Runtime.InteropServices;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
@ -24,7 +23,7 @@ namespace Emby.Server.Implementations.IO
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>(); private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
private readonly string _tempPath; private readonly string _tempPath;
private readonly bool _isEnvironmentCaseInsensitive; private static readonly bool _isEnvironmentCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public ManagedFileSystem( public ManagedFileSystem(
ILogger<ManagedFileSystem> logger, ILogger<ManagedFileSystem> logger,
@ -32,8 +31,6 @@ namespace Emby.Server.Implementations.IO
{ {
Logger = logger; Logger = logger;
_tempPath = applicationPaths.TempDirectory; _tempPath = applicationPaths.TempDirectory;
_isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows;
} }
public virtual void AddShortcutHandler(IShortcutHandler handler) public virtual void AddShortcutHandler(IShortcutHandler handler)
@ -55,7 +52,7 @@ namespace Emby.Server.Implementations.IO
} }
var extension = Path.GetExtension(filename); var extension = Path.GetExtension(filename);
return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
} }
/// <summary> /// <summary>
@ -64,7 +61,7 @@ namespace Emby.Server.Implementations.IO
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">filename</exception> /// <exception cref="ArgumentNullException">filename</exception>
public virtual string ResolveShortcut(string filename) public virtual string? ResolveShortcut(string filename)
{ {
if (string.IsNullOrEmpty(filename)) if (string.IsNullOrEmpty(filename))
{ {
@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.IO
} }
var extension = Path.GetExtension(filename); var extension = Path.GetExtension(filename);
var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
return handler?.Resolve(filename); return handler?.Resolve(filename);
} }
@ -263,8 +260,6 @@ namespace Emby.Server.Implementations.IO
result.Exists = false; result.Exists = false;
} }
} }
result.DirectoryName = fileInfo.DirectoryName;
} }
result.CreationTimeUtc = GetCreationTimeUtc(info); result.CreationTimeUtc = GetCreationTimeUtc(info);
@ -303,16 +298,37 @@ namespace Emby.Server.Implementations.IO
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">The filename is null.</exception> /// <exception cref="ArgumentNullException">The filename is null.</exception>
public virtual string GetValidFilename(string filename) public string GetValidFilename(string filename)
{ {
var builder = new StringBuilder(filename); var invalid = Path.GetInvalidFileNameChars();
var first = filename.IndexOfAny(invalid);
foreach (var c in Path.GetInvalidFileNameChars()) if (first == -1)
{ {
builder = builder.Replace(c, ' '); // Fast path for clean strings
return filename;
} }
return builder.ToString(); return string.Create(
filename.Length,
(filename, invalid, first),
(chars, state) =>
{
state.filename.AsSpan().CopyTo(chars);
chars[state.first++] = ' ';
var len = chars.Length;
foreach (var c in state.invalid)
{
for (int i = state.first; i < len; i++)
{
if (chars[i] == c)
{
chars[i] = ' ';
}
}
}
});
} }
/// <summary> /// <summary>
@ -585,7 +601,7 @@ namespace Emby.Server.Implementations.IO
return GetFiles(path, null, false, recursive); return GetFiles(path, null, false, recursive);
} }
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false) public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{ {
var enumerationOptions = GetEnumerationOptions(recursive); var enumerationOptions = GetEnumerationOptions(recursive);
@ -639,7 +655,7 @@ namespace Emby.Server.Implementations.IO
return GetFilePaths(path, null, false, recursive); return GetFilePaths(path, null, false, recursive);
} }
public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{ {
var enumerationOptions = GetEnumerationOptions(recursive); var enumerationOptions = GetEnumerationOptions(recursive);
@ -684,20 +700,5 @@ namespace Emby.Server.Implementations.IO
AttributesToSkip = 0 AttributesToSkip = 0
}; };
} }
private static void RunProcess(string path, string args, string workingDirectory)
{
using (var process = Process.Start(new ProcessStartInfo
{
Arguments = args,
FileName = path,
CreateNoWindow = true,
WorkingDirectory = workingDirectory,
WindowStyle = ProcessWindowStyle.Normal
}))
{
process.WaitForExit();
}
}
} }
} }

View File

@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO
public string Extension => ".mblink"; public string Extension => ".mblink";
public string Resolve(string shortcutPath) public string? Resolve(string shortcutPath)
{ {
if (string.IsNullOrEmpty(shortcutPath)) if (string.IsNullOrEmpty(shortcutPath))
{ {

View File

@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.IO
{ {
public class StreamHelper : IStreamHelper public class StreamHelper : IStreamHelper
{ {
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken)
{ {
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try try

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#nullable enable
namespace Emby.Server.Implementations namespace Emby.Server.Implementations
{ {

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -191,7 +193,7 @@ namespace Emby.Server.Implementations.Images
InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray() InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray()
}; };
if (options.InputPaths.Length == 0) if (options.InputPaths.Count == 0)
{ {
return null; return null;
} }

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
@ -29,9 +31,7 @@ namespace Emby.Server.Implementations.Images
{ {
var subItem = i.Item2; var subItem = i.Item2;
var episode = subItem as Episode; if (subItem is Episode episode)
if (episode != null)
{ {
var series = episode.Series; var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary)) if (series != null && series.HasImage(ImageType.Primary))

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,5 +1,3 @@
#nullable enable
using System; using System;
using System.Linq; using System.Linq;
using DotNet.Globbing; using DotNet.Globbing;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -48,6 +50,7 @@ using MediaBrowser.Providers.MediaInfo;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
using Genre = MediaBrowser.Controller.Entities.Genre; using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person; using Person = MediaBrowser.Controller.Entities.Person;
using VideoResolver = Emby.Naming.Video.VideoResolver; using VideoResolver = Emby.Naming.Video.VideoResolver;
@ -175,10 +178,7 @@ namespace Emby.Server.Implementations.Library
{ {
lock (_rootFolderSyncLock) lock (_rootFolderSyncLock)
{ {
if (_rootFolder == null) _rootFolder ??= CreateRootFolder();
{
_rootFolder = CreateRootFolder();
}
} }
} }
@ -558,7 +558,6 @@ namespace Emby.Server.Implementations.Library
var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService) var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
{ {
Parent = parent, Parent = parent,
Path = fullPath,
FileInfo = fileInfo, FileInfo = fileInfo,
CollectionType = collectionType, CollectionType = collectionType,
LibraryOptions = libraryOptions LibraryOptions = libraryOptions
@ -684,7 +683,7 @@ namespace Emby.Server.Implementations.Library
foreach (var item in items) foreach (var item in items)
{ {
ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService); ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
} }
items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));
@ -1163,7 +1162,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(percent * 100); progress.Report(percent * 100);
} }
_itemRepository.UpdateInheritedValues(cancellationToken); _itemRepository.UpdateInheritedValues();
progress.Report(100); progress.Report(100);
} }
@ -2517,7 +2516,7 @@ namespace Emby.Server.Implementations.Library
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
{ {
var series = episode.Series; var series = episode.Series;
bool? isAbsoluteNaming = series == null ? false : string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); bool? isAbsoluteNaming = series != null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase);
if (!isAbsoluteNaming.Value) if (!isAbsoluteNaming.Value)
{ {
// In other words, no filter applied // In other words, no filter applied
@ -2529,9 +2528,23 @@ namespace Emby.Server.Implementations.Library
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
// TODO nullable - what are we trying to do there with empty episodeInfo? // TODO nullable - what are we trying to do there with empty episodeInfo?
var episodeInfo = episode.IsFileProtocol EpisodeInfo episodeInfo = null;
? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo(episode.Path) if (episode.IsFileProtocol)
: new Naming.TV.EpisodeInfo(episode.Path); {
episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming);
// Resolve from parent folder if it's not the Season folder
if (episodeInfo == null && episode.Parent.GetType() == typeof(Folder))
{
episodeInfo = resolver.Resolve(episode.Parent.Path, true, null, null, isAbsoluteNaming);
if (episodeInfo != null)
{
// add the container
episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.');
}
}
}
episodeInfo ??= new EpisodeInfo(episode.Path);
try try
{ {
@ -2880,6 +2893,12 @@ namespace Emby.Server.Implementations.Library
} }
public void UpdatePeople(BaseItem item, List<PersonInfo> people) public void UpdatePeople(BaseItem item, List<PersonInfo> people)
{
UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult();
}
/// <inheritdoc />
public async Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken)
{ {
if (!item.SupportsPeople) if (!item.SupportsPeople)
{ {
@ -2887,6 +2906,8 @@ namespace Emby.Server.Implementations.Library
} }
_itemRepository.UpdatePeople(item.Id, people); _itemRepository.UpdatePeople(item.Id, people);
await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false);
} }
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex) public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
@ -2990,6 +3011,58 @@ namespace Emby.Server.Implementations.Library
} }
} }
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
{
var personsToSave = new List<BaseItem>();
foreach (var person in people)
{
cancellationToken.ThrowIfCancellationRequested();
var itemUpdateType = ItemUpdateType.MetadataDownload;
var saveEntity = false;
var personEntity = GetPerson(person.Name);
// if PresentationUniqueKey is empty it's likely a new item.
if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey))
{
personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
saveEntity = true;
}
foreach (var id in person.ProviderIds)
{
if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase))
{
personEntity.SetProviderId(id.Key, id.Value);
saveEntity = true;
}
}
if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
{
personEntity.SetImage(
new ItemImageInfo
{
Path = person.ImageUrl,
Type = ImageType.Primary
},
0);
saveEntity = true;
itemUpdateType = ItemUpdateType.ImageUpdate;
}
if (saveEntity)
{
personsToSave.Add(personEntity);
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
}
}
CreateItems(personsToSave, null, CancellationToken.None);
}
private void StartScanInBackground() private void StartScanInBackground()
{ {
Task.Run(() => Task.Run(() =>

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -590,18 +592,9 @@ namespace Emby.Server.Implementations.Library
public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
{ {
var info = _openStreams.Values.FirstOrDefault(i => var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
{
var liveStream = i as ILiveStream;
if (liveStream != null)
{
return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
}
return false; return Task.FromResult(info.Value as IDirectStreamProvider);
});
return Task.FromResult(info as IDirectStreamProvider);
} }
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -100,8 +102,7 @@ namespace Emby.Server.Implementations.Library
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions) public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
{ {
var genre = item as MusicGenre; if (item is MusicGenre genre)
if (genre != null)
{ {
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions); return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
} }

View File

@ -1,5 +1,3 @@
#nullable enable
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using MediaBrowser.Common.Providers; using MediaBrowser.Common.Providers;
@ -96,8 +94,14 @@ namespace Emby.Server.Implementations.Library
// We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results // 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 // when the sub path matches a similar but in-complete subpath
var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar; var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar;
if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase) if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase))
|| (!oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar)) {
return false;
}
if (path.Length > subPath.Length
&& !oldSubPathEndsWithSeparator
&& path[subPath.Length] != newDirectorySeparatorChar)
{ {
return false; return false;
} }

View File

@ -18,11 +18,10 @@ namespace Emby.Server.Implementations.Library
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <param name="parent">The parent.</param> /// <param name="parent">The parent.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
/// <param name="directoryService">The directory service.</param> /// <param name="directoryService">The directory service.</param>
/// <exception cref="ArgumentException">Item must have a path</exception> /// <exception cref="ArgumentException">Item must have a path.</exception>
public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService) public static void SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService)
{ {
// This version of the below method has no ItemResolveArgs, so we have to require the path already being set // This version of the below method has no ItemResolveArgs, so we have to require the path already being set
if (string.IsNullOrEmpty(item.Path)) if (string.IsNullOrEmpty(item.Path))
@ -43,9 +42,14 @@ namespace Emby.Server.Implementations.Library
// Make sure DateCreated and DateModified have values // Make sure DateCreated and DateModified have values
var fileInfo = directoryService.GetFile(item.Path); var fileInfo = directoryService.GetFile(item.Path);
SetDateCreated(item, fileSystem, fileInfo); if (fileInfo == null)
{
throw new FileNotFoundException("Can't find item path.", item.Path);
}
EnsureName(item, item.Path, fileInfo); SetDateCreated(item, fileInfo);
EnsureName(item, fileInfo);
} }
/// <summary> /// <summary>
@ -72,9 +76,9 @@ namespace Emby.Server.Implementations.Library
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
// Make sure the item has a name // Make sure the item has a name
EnsureName(item, item.Path, args.FileInfo); EnsureName(item, args.FileInfo);
item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) ||
item.GetParents().Any(i => i.IsLocked); item.GetParents().Any(i => i.IsLocked);
// Make sure DateCreated and DateModified have values // Make sure DateCreated and DateModified have values
@ -84,28 +88,15 @@ namespace Emby.Server.Implementations.Library
/// <summary> /// <summary>
/// Ensures the name. /// Ensures the name.
/// </summary> /// </summary>
private static void EnsureName(BaseItem item, string fullPath, FileSystemMetadata fileInfo) private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo)
{ {
// If the subclass didn't supply a name, add it here // If the subclass didn't supply a name, add it here
if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(fullPath)) if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path))
{ {
var fileName = fileInfo == null ? Path.GetFileName(fullPath) : fileInfo.Name; item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name);
item.Name = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory);
} }
} }
/// <summary>
/// Gets the display name.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isDirectory">if set to <c>true</c> [is directory].</param>
/// <returns>System.String.</returns>
private static string GetDisplayName(string path, bool isDirectory)
{
return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path);
}
/// <summary> /// <summary>
/// Ensures DateCreated and DateModified have values. /// Ensures DateCreated and DateModified have values.
/// </summary> /// </summary>
@ -114,21 +105,6 @@ namespace Emby.Server.Implementations.Library
/// <param name="args">The args.</param> /// <param name="args">The args.</param>
private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args) private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args)
{ {
if (fileSystem == null)
{
throw new ArgumentNullException(nameof(fileSystem));
}
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
// See if a different path came out of the resolver than what went in // See if a different path came out of the resolver than what went in
if (!fileSystem.AreEqual(args.Path, item.Path)) if (!fileSystem.AreEqual(args.Path, item.Path))
{ {
@ -136,7 +112,7 @@ namespace Emby.Server.Implementations.Library
if (childData != null) if (childData != null)
{ {
SetDateCreated(item, fileSystem, childData); SetDateCreated(item, childData);
} }
else else
{ {
@ -144,17 +120,17 @@ namespace Emby.Server.Implementations.Library
if (fileData.Exists) if (fileData.Exists)
{ {
SetDateCreated(item, fileSystem, fileData); SetDateCreated(item, fileData);
} }
} }
} }
else else
{ {
SetDateCreated(item, fileSystem, args.FileInfo); SetDateCreated(item, args.FileInfo);
} }
} }
private static void SetDateCreated(BaseItem item, IFileSystem fileSystem, FileSystemMetadata info) private static void SetDateCreated(BaseItem item, FileSystemMetadata? info)
{ {
var config = BaseItem.ConfigurationManager.GetMetadataConfiguration(); var config = BaseItem.ConfigurationManager.GetMetadataConfiguration();
@ -163,7 +139,7 @@ namespace Emby.Server.Implementations.Library
// directoryService.getFile may return null // directoryService.getFile may return null
if (info != null) if (info != null)
{ {
var dateCreated = fileSystem.GetCreationTimeUtc(info); var dateCreated = info.CreationTimeUtc;
if (dateCreated.Equals(DateTime.MinValue)) if (dateCreated.Equals(DateTime.MinValue))
{ {

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -1,3 +1,5 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

View File

@ -1,3 +1,5 @@
#nullable disable
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;

View File

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -165,13 +167,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void SetVideoType(Video video, VideoFileInfo videoInfo) protected void SetVideoType(Video video, VideoFileInfo videoInfo)
{ {
var extension = Path.GetExtension(video.Path); var extension = Path.GetExtension(video.Path.AsSpan());
video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) || video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase)
string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ? || extension.Equals(".img", StringComparison.OrdinalIgnoreCase)
VideoType.Iso : ? VideoType.Iso
VideoType.VideoFile; : VideoType.VideoFile;
video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase); video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase);
video.IsPlaceHolder = videoInfo.IsStub; video.IsPlaceHolder = videoInfo.IsStub;
if (videoInfo.IsStub) if (videoInfo.IsStub)
@ -193,11 +195,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
{ {
if (video.VideoType == VideoType.Iso) if (video.VideoType == VideoType.Iso)
{ {
if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1) if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase))
{ {
video.IsoType = IsoType.Dvd; video.IsoType = IsoType.Dvd;
} }
else if (video.Path.IndexOf("bluray", StringComparison.OrdinalIgnoreCase) != -1) else if (video.Path.Contains("bluray", StringComparison.OrdinalIgnoreCase))
{ {
video.IsoType = IsoType.BluRay; video.IsoType = IsoType.BluRay;
} }

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