diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml
index 8d0737b66c..e58a2bdc7e 100644
--- a/.ci/azure-pipelines-abi.yml
+++ b/.ci/azure-pipelines-abi.yml
@@ -7,7 +7,7 @@ parameters:
default: "ubuntu-latest"
- name: DotNetSdkVersion
type: string
- default: 5.0.103
+ default: 5.0.302
jobs:
- job: CompatibilityCheck
diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml
deleted file mode 100644
index 0e944e6f47..0000000000
--- a/.ci/azure-pipelines-api-client.yml
+++ /dev/null
@@ -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
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
index 4bc72f9eb0..d2c087c14d 100644
--- a/.ci/azure-pipelines-main.yml
+++ b/.ci/azure-pipelines-main.yml
@@ -1,7 +1,7 @@
parameters:
LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
- DotNetSdkVersion: 5.0.103
+ DotNetSdkVersion: 5.0.302
jobs:
- job: Build
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml
index 7838b3b026..7ec4cdad1d 100644
--- a/.ci/azure-pipelines-test.yml
+++ b/.ci/azure-pipelines-test.yml
@@ -10,7 +10,7 @@ parameters:
default: "tests/**/*Tests.csproj"
- name: DotNetSdkVersion
type: string
- default: 5.0.103
+ default: 5.0.302
jobs:
- job: Test
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index 6430503f9a..4e8b6557b9 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -6,7 +6,7 @@ variables:
- name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
- name: DotNetSdkVersion
- value: 5.0.103
+ value: 5.0.302
pr:
autoCancel: true
@@ -61,6 +61,3 @@ jobs:
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- 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
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index d67e1c98bf..c1d49778e3 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -14,9 +14,11 @@ assignees: ''
- OS: [e.g. Debian, Windows]
- Virtualization: [e.g. Docker, KVM, LXC]
- Clients: [Browser, Android, Fire Stick, etc.]
- - Browser: [e.g. Firefox 72, Chrome 80, Safari 13]
- - Jellyfin Version: [e.g. 10.4.3, nightly 20191231]
+ - Browser: [e.g. Firefox 91, Chrome 93, Safari 13]
+ - Jellyfin Version: [e.g. 10.7.6, unstable 20191231]
+ - FFmpeg Version: [e.g. 4.3.2-Jellyfin]
- Playback: [Direct Play, Remux, Direct Stream, Transcode]
+ - Hardware Acceleration: [e.g. none, VAAPI, NVENC, etc.]
- Installed Plugins: [e.g. none, Fanart, Anime, etc.]
- Reverse Proxy: [e.g. none, nginx, apache, etc.]
- Base URL: [e.g. none, yes: /example]
@@ -33,7 +35,13 @@ assignees: ''
**Expected behavior**
-**Logs**
+**Server Logs**
+
+
+**FFmpeg Logs**
+
+
+**Browser Console Logs**
**Screenshots**
diff --git a/.github/stale.yml b/.github/stale.yml
index 05892c44dc..cba9c33b2a 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -17,9 +17,13 @@ staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
-
+
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or nightlies, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
+
+# Disable automatic closing of pull requests
+pulls:
+ daysUntilClose: false
diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml
index db34693cc7..20294843d5 100644
--- a/.github/workflows/automation.yml
+++ b/.github/workflows/automation.yml
@@ -1,46 +1,56 @@
name: Automation
on:
- pull_request:
- issues:
+ push:
+ branches:
+ - master
+ pull_request_target:
issue_comment:
jobs:
- main:
+ label:
+ name: Labeling
runs-on: ubuntu-latest
+ if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- - name: Does PR has the stable backport label?
- uses: Dreamcodeio/does-pr-has-label@v1.2
- id: checkLabel
+ - name: Apply label
+ uses: eps1lon/actions-label-merge-conflict@v2.0.1
+ if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
with:
- label: stable backport
+ dirtyLabel: 'merge conflict'
+ repoToken: ${{ secrets.JF_BOT_TOKEN }}
+ project:
+ name: Project board
+ runs-on: ubuntu-latest
+ if: ${{ github.repository == 'jellyfin/jellyfin' }}
+ steps:
- name: Remove from 'Current Release' project
- uses: alex-page/github-project-automation-plus@v0.5.1
- if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel
+ uses: alex-page/github-project-automation-plus@v0.8.1
+ if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
project: Current Release
action: delete
- repo-token: ${{ secrets.GH_TOKEN }}
+ repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Release Next' project
- uses: alex-page/github-project-automation-plus@v0.5.1
+ uses: alex-page/github-project-automation-plus@v0.8.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 }}
+ repo-token: ${{ secrets.JF_BOT_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
+ uses: alex-page/github-project-automation-plus@v0.8.1
+ if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
project: Current Release
column: In progress
- repo-token: ${{ secrets.GH_TOKEN }}
+ repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Check number of comments from the team member
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
@@ -48,19 +58,19 @@ jobs:
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
+ uses: alex-page/github-project-automation-plus@v0.8.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 }}
+ repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add issue to triage project
- uses: alex-page/github-project-automation-plus@v0.5.1
+ uses: alex-page/github-project-automation-plus@v0.8.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 }}
+ repo-token: ${{ secrets.JF_BOT_TOKEN }}
diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml
new file mode 100644
index 0000000000..e0b91ecee6
--- /dev/null
+++ b/.github/workflows/commands.yml
@@ -0,0 +1,119 @@
+name: Commands
+on:
+ issue_comment:
+ types:
+ - created
+ - edited
+ pull_request_target:
+ types:
+ - labeled
+ - synchronize
+
+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.JF_BOT_TOKEN }}
+ comment-id: ${{ github.event.comment.id }}
+ reactions: '+1'
+
+ - name: Checkout the latest code
+ uses: actions/checkout@v2
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ fetch-depth: 0
+
+ - name: Automatic Rebase
+ uses: cirrus-actions/rebase@1.4
+ env:
+ GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
+
+ check-backport:
+ name: Check Backport
+ if: ${{ ( github.event.issue.pull_request && contains(github.event.comment.body, '@jellyfin-bot check backport') ) || github.event.label.name == 'stable backport' || contains(github.event.pull_request.labels.*.name, 'stable backport' ) }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Notify as seen
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ if: ${{ github.event.comment != null }}
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ comment-id: ${{ github.event.comment.id }}
+ reactions: eyes
+
+ - name: Checkout the latest code
+ uses: actions/checkout@v2
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ fetch-depth: 0
+
+ - name: Notify as running
+ id: comment_running
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ if: ${{ github.event.comment != null }}
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ issue-number: ${{ github.event.issue.number }}
+ body: |
+ Running backport tests...
+
+ - name: Perform test backport
+ id: run_tests
+ run: |
+ set +o errexit
+ git config --global user.name "Jellyfin Bot"
+ git config --global user.email "team@jellyfin.org"
+ CURRENT_BRANCH="origin/${GITHUB_HEAD_REF}"
+ git checkout master
+ git merge --no-ff ${CURRENT_BRANCH}
+ MERGE_COMMIT_HASH=$( git log -q -1 | head -1 | awk '{ print $2 }' )
+ git fetch --all
+ CURRENT_STABLE=$( git branch -r | grep 'origin/release' | sort -rV | head -1 | awk -F '/' '{ print $NF }' )
+ stable_branch="Current stable release branch: ${CURRENT_STABLE}"
+ echo ${stable_branch}
+ echo ::set-output name=branch::${stable_branch}
+ git checkout -t origin/${CURRENT_STABLE} -b ${CURRENT_STABLE}
+ git cherry-pick -sx -m1 ${MERGE_COMMIT_HASH} &>output.txt
+ retcode=$?
+ cat output.txt | grep -v 'hint:'
+ output="$( grep -v 'hint:' output.txt )"
+ output="${output//'%'/'%25'}"
+ output="${output//$'\n'/'%0A'}"
+ output="${output//$'\r'/'%0D'}"
+ echo ::set-output name=output::$output
+ exit ${retcode}
+
+ - name: Notify with result success
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ if: ${{ github.event.comment != null && success() }}
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ comment-id: ${{ steps.comment_running.outputs.comment-id }}
+ body: |
+ ${{ steps.run_tests.outputs.branch }}
+ Output from `git cherry-pick`:
+
+ ---
+
+ ${{ steps.run_tests.outputs.output }}
+ reactions: hooray
+
+ - name: Notify with result failure
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ if: ${{ github.event.comment != null && failure() }}
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ comment-id: ${{ steps.comment_running.outputs.comment-id }}
+ body: |
+ ${{ steps.run_tests.outputs.branch }}
+ Output from `git cherry-pick`:
+
+ ---
+
+ ${{ steps.run_tests.outputs.output }}
+ reactions: confused
diff --git a/.github/workflows/merge-conflicts.yml b/.github/workflows/merge-conflicts.yml
deleted file mode 100644
index ce808617a1..0000000000
--- a/.github/workflows/merge-conflicts.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-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
deleted file mode 100644
index 3172ec0d9f..0000000000
--- a/.github/workflows/rebase.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-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/.gitignore b/.gitignore
index 7cd3d0068a..c2ae76c1e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -268,6 +268,7 @@ doc/
# Deployment artifacts
dist
*.exe
+*.dll
# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts
@@ -277,3 +278,6 @@ web/
web-src.*
MediaBrowser.WebDashboard/jellyfin-web
apiclient/generated
+
+# Omnisharp crash logs
+mono_crash.*.json
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 7a763a46c1..cb52cafedf 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -46,6 +46,7 @@
- [fruhnow](https://github.com/fruhnow)
- [geilername](https://github.com/geilername)
- [gnattu](https://github.com/gnattu)
+ - [GodTamIt](https://github.com/GodTamIt)
- [grafixeyehero](https://github.com/grafixeyehero)
- [h1nk](https://github.com/h1nk)
- [hawken93](https://github.com/hawken93)
@@ -70,6 +71,7 @@
- [marius-luca-87](https://github.com/marius-luca-87)
- [mark-monteiro](https://github.com/mark-monteiro)
- [Matt07211](https://github.com/Matt07211)
+ - [Maxr1998](https://github.com/Maxr1998)
- [mcarlton00](https://github.com/mcarlton00)
- [mitchfizz05](https://github.com/mitchfizz05)
- [MrTimscampi](https://github.com/MrTimscampi)
@@ -110,7 +112,7 @@
- [sorinyo2004](https://github.com/sorinyo2004)
- [sparky8251](https://github.com/sparky8251)
- [spookbits](https://github.com/spookbits)
- - [ssenart] (https://github.com/ssenart)
+ - [ssenart](https://github.com/ssenart)
- [stanionascu](https://github.com/stanionascu)
- [stevehayles](https://github.com/stevehayles)
- [SuperSandro2000](https://github.com/SuperSandro2000)
@@ -146,6 +148,7 @@
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
- [skyfrk](https://github.com/skyfrk)
- [ianjazz246](https://github.com/ianjazz246)
+ - [peterspenler](https://github.com/peterspenler)
# Emby Contributors
@@ -210,3 +213,5 @@
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh)
+ - [lbenini](https://github.com/lbenini)
+ - [gnuyent](https://github.com/gnuyent)
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000000..b899999efb
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,14 @@
+
+
+
+
+ enable
+ true
+ $(MSBuildThisFileDirectory)/jellyfin.ruleset
+
+
+
+ AllEnabledByDefault
+
+
+
diff --git a/Dockerfile b/Dockerfile
index ebe5eb00c6..3190fec5c5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,22 +1,14 @@
ARG DOTNET_VERSION=5.0
-FROM node:alpine as web-builder
+FROM node:lts-alpine as web-builder
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 - \
&& cd jellyfin-web-* \
- && npm ci --no-audit \
+ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
-WORKDIR /repo
-COPY . .
-ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# because of changes in docker and systemd we need to not build in parallel at the moment
-# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
-
-FROM debian:buster-slim
+FROM debian:bullseye-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
@@ -25,9 +17,6 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
-COPY --from=builder /jellyfin /jellyfin
-COPY --from=web-builder /dist /jellyfin/jellyfin-web
-
# https://github.com/intel/compute-runtime/releases
ARG GMMLIB_VERSION=20.3.2
ARG IGC_VERSION=1.0.5435
@@ -73,6 +62,19 @@ ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
+WORKDIR /repo
+COPY . .
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+# because of changes in docker and systemd we need to not build in parallel at the moment
+# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
+
+FROM app
+
+COPY --from=builder /jellyfin /jellyfin
+COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
diff --git a/Dockerfile.arm b/Dockerfile.arm
index d63dbee758..dcd006ff83 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -5,27 +5,16 @@
ARG DOTNET_VERSION=5.0
-FROM node:alpine as web-builder
+FROM node:lts-alpine as web-builder
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 - \
&& cd jellyfin-web-* \
- && npm ci --no-audit \
+ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist
-
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
-WORKDIR /repo
-COPY . .
-ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# Discard objs - may cause failures if exists
-RUN find . -type d -name obj | xargs -r rm -r
-# Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
-
-
FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM arm32v7/debian:buster-slim
+FROM arm32v7/debian:bullseye-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
@@ -61,14 +50,25 @@ RUN apt-get update \
&& chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
-COPY --from=builder /jellyfin /jellyfin
-COPY --from=web-builder /dist /jellyfin/jellyfin-web
-
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
+WORKDIR /repo
+COPY . .
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+# Discard objs - may cause failures if exists
+RUN find . -type d -name obj | xargs -r rm -r
+# Build
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
+
+FROM app
+
+COPY --from=builder /jellyfin /jellyfin
+COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index e95999f2a6..7311c6b9ff 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -5,26 +5,16 @@
ARG DOTNET_VERSION=5.0
-FROM node:alpine as web-builder
+FROM node:lts-alpine as web-builder
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 - \
&& cd jellyfin-web-* \
- && npm ci --no-audit \
+ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist
-
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
-WORKDIR /repo
-COPY . .
-ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# Discard objs - may cause failures if exists
-RUN find . -type d -name obj | xargs -r rm -r
-# Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
-
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM arm64v8/debian:buster-slim
+FROM arm64v8/debian:bullseye-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
@@ -50,14 +40,25 @@ RUN apt-get update && apt-get install --no-install-recommends --no-install-sugge
&& chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
-COPY --from=builder /jellyfin /jellyfin
-COPY --from=web-builder /dist /jellyfin/jellyfin-web
-
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
+WORKDIR /repo
+COPY . .
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+# Discard objs - may cause failures if exists
+RUN find . -type d -name obj | xargs -r rm -r
+# Build
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
+
+FROM app
+
+COPY --from=builder /jellyfin /jellyfin
+COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj
index 7bbd9acf82..b8301e2f27 100644
--- a/DvdLib/DvdLib.csproj
+++ b/DvdLib/DvdLib.csproj
@@ -13,7 +13,8 @@
net5.0
false
true
- true
+ AllDisabledByDefault
+ disable
diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs
index e63a858605..91fac4bef5 100644
--- a/Emby.Dlna/Configuration/DlnaOptions.cs
+++ b/Emby.Dlna/Configuration/DlnaOptions.cs
@@ -72,7 +72,7 @@ namespace Emby.Dlna.Configuration
///
/// Gets or sets the default user account that the dlna server uses.
///
- public string DefaultUserId { get; set; }
+ public string? DefaultUserId { get; set; }
///
/// Gets or sets a value indicating whether playTo device profiles should be created.
diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs
index fc02e17515..3ca43052a4 100644
--- a/Emby.Dlna/ConfigurationExtension.cs
+++ b/Emby.Dlna/ConfigurationExtension.cs
@@ -1,4 +1,3 @@
-#nullable enable
#pragma warning disable CS1591
using Emby.Dlna.Configuration;
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
index 2f3107450c..9020dea994 100644
--- a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
+++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
@@ -138,7 +138,7 @@ namespace Emby.Dlna.ContentDirectory
///
/// The .
/// The .
- private User GetUser(DeviceProfile profile)
+ private User? GetUser(DeviceProfile profile)
{
if (!string.IsNullOrEmpty(profile.UserId))
{
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 90ba601b4a..ac336e5dcc 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -286,21 +288,14 @@ namespace Emby.Dlna.ContentDirectory
/// The xml feature list.
private static string WriteFeatureListXml()
{
- // TODO: clean this up
- var builder = new StringBuilder();
-
- builder.Append("");
- builder.Append("");
-
- builder.Append("");
- builder.Append("");
- builder.Append("");
- builder.Append("");
- builder.Append("");
-
- builder.Append("");
-
- return builder.ToString();
+ return ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + "";
}
///
diff --git a/Emby.Dlna/ContentDirectory/ServerItem.cs b/Emby.Dlna/ContentDirectory/ServerItem.cs
index 34244000c1..ff30e6e4af 100644
--- a/Emby.Dlna/ContentDirectory/ServerItem.cs
+++ b/Emby.Dlna/ContentDirectory/ServerItem.cs
@@ -17,7 +17,7 @@ namespace Emby.Dlna.ContentDirectory
{
Item = item;
- if (item is IItemByName && !(item is Folder))
+ if (item is IItemByName && item is not Folder)
{
StubType = Dlna.ContentDirectory.StubType.Folder;
}
diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs
index 4ea4e4e48c..8ee6325e9e 100644
--- a/Emby.Dlna/ControlRequest.cs
+++ b/Emby.Dlna/ControlRequest.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.IO;
diff --git a/Emby.Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs
index d827eef26c..8b09588424 100644
--- a/Emby.Dlna/ControlResponse.cs
+++ b/Emby.Dlna/ControlResponse.cs
@@ -6,9 +6,11 @@ namespace Emby.Dlna
{
public class ControlResponse
{
- public ControlResponse()
+ public ControlResponse(string xml, bool isSuccessful)
{
Headers = new Dictionary();
+ Xml = xml;
+ IsSuccessful = isSuccessful;
}
public IDictionary Headers { get; }
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 66ae07329e..c000784997 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -746,7 +748,7 @@ namespace Emby.Dlna.Didl
AddValue(writer, "upnp", "publisher", studio, NsUpnp);
}
- if (!(item is Folder))
+ if (item is not Folder)
{
if (filter.Contains("dc:description"))
{
@@ -976,15 +978,28 @@ namespace Emby.Dlna.Didl
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.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
- writer.WriteString(albumartUrlInfo.url);
+ if (!string.IsNullOrEmpty(_profile.AlbumArtPn))
+ {
+ writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
+ }
+
+ writer.WriteString(albumArtUrlInfo.url);
writer.WriteFullEndElement();
- // TOOD: Remove these default values
- var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
+ // TODO: Remove these default values
+ var iconUrlInfo = GetImageUrl(
+ imageInfo,
+ _profile.MaxIconWidth ?? 48,
+ _profile.MaxIconHeight ?? 48,
+ "jpg");
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
if (!_profile.EnableAlbumArtInDidl)
@@ -1207,8 +1222,7 @@ namespace Emby.Dlna.Didl
if (width.HasValue && height.HasValue)
{
- var newSize = DrawingUtils.Resize(
- new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
+ var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
width = newSize.Width;
height = newSize.Height;
diff --git a/Emby.Dlna/Didl/StringWriterWithEncoding.cs b/Emby.Dlna/Didl/StringWriterWithEncoding.cs
index 2b86ea333f..b66f53ece2 100644
--- a/Emby.Dlna/Didl/StringWriterWithEncoding.cs
+++ b/Emby.Dlna/Didl/StringWriterWithEncoding.cs
@@ -9,7 +9,7 @@ namespace Emby.Dlna.Didl
{
public class StringWriterWithEncoding : StringWriter
{
- private readonly Encoding _encoding;
+ private readonly Encoding? _encoding;
public StringWriterWithEncoding()
{
diff --git a/Emby.Dlna/DlnaConfigurationFactory.cs b/Emby.Dlna/DlnaConfigurationFactory.cs
index 4c6ca869aa..6cc6b73a0c 100644
--- a/Emby.Dlna/DlnaConfigurationFactory.cs
+++ b/Emby.Dlna/DlnaConfigurationFactory.cs
@@ -1,4 +1,3 @@
-#nullable enable
#pragma warning disable CS1591
using System.Collections.Generic;
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index 3417076dc1..af70793ccf 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -12,9 +11,9 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Emby.Dlna.Profiles;
using Emby.Dlna.Server;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
@@ -94,12 +93,14 @@ namespace Emby.Dlna
}
}
+ ///
public DeviceProfile GetDefaultProfile()
{
return new DefaultProfile();
}
- public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
+ ///
+ public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
{
if (deviceInfo == null)
{
@@ -109,13 +110,13 @@ namespace Emby.Dlna
var profile = GetProfiles()
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
- if (profile != null)
+ if (profile == null)
{
- _logger.LogDebug("Found matching device profile: {0}", profile.Name);
+ LogUnmatchedProfile(deviceInfo);
}
else
{
- LogUnmatchedProfile(deviceInfo);
+ _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
}
return profile;
@@ -138,80 +139,45 @@ namespace Emby.Dlna
_logger.LogInformation(builder.ToString());
}
- private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
+ ///
+ /// 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.
+ ///
+ /// The of the device.
+ /// The of the profile.
+ /// True if they match.
+ public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
{
- if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
- {
- if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
- {
- return false;
- }
- }
-
- 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;
+ return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
+ && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
+ && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
+ && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
+ && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
+ && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
+ && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
+ && IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
}
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
{
- 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)
{
@@ -220,7 +186,8 @@ namespace Emby.Dlna
}
}
- public DeviceProfile GetProfile(IHeaderDictionary headers)
+ ///
+ public DeviceProfile? GetProfile(IHeaderDictionary headers)
{
if (headers == null)
{
@@ -228,15 +195,13 @@ namespace Emby.Dlna
}
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
-
- if (profile != null)
+ if (profile == null)
{
- _logger.LogDebug("Found matching device profile: {0}", profile.Name);
+ _logger.LogDebug("No matching device profile found. {@Headers}", headers);
}
else
{
- var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value)));
- _logger.LogDebug("No matching device profile found. {0}", headerString);
+ _logger.LogDebug("Found matching device profile: {0}", profile.Name);
}
return profile;
@@ -286,19 +251,19 @@ namespace Emby.Dlna
return xmlFies
.Select(i => ParseProfileFile(i, type))
.Where(i => i != null)
- .ToList();
+ .ToList()!; // We just filtered out all the nulls
}
catch (IOException)
{
- return new List();
+ return Array.Empty();
}
}
- private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
+ private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
{
lock (_profiles)
{
- if (_profiles.TryGetValue(path, out Tuple profileTuple))
+ if (_profiles.TryGetValue(path, out Tuple? profileTuple))
{
return profileTuple.Item2;
}
@@ -326,7 +291,8 @@ namespace Emby.Dlna
}
}
- public DeviceProfile GetProfile(string id)
+ ///
+ public DeviceProfile? GetProfile(string id)
{
if (string.IsNullOrEmpty(id))
{
@@ -355,6 +321,7 @@ namespace Emby.Dlna
}
}
+ ///
public IEnumerable GetProfileInfos()
{
return GetProfileInfosInternal().Select(i => i.Info);
@@ -362,17 +329,14 @@ namespace Emby.Dlna
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
{
- return new InternalProfileInfo
- {
- Path = file.FullName,
-
- Info = new DeviceProfileInfo
+ return new InternalProfileInfo(
+ new DeviceProfileInfo
{
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
Name = _fileSystem.GetFileNameWithoutExtension(file),
Type = type
- }
- };
+ },
+ file.FullName);
}
private async Task ExtractSystemProfilesAsync()
@@ -392,7 +356,8 @@ namespace Emby.Dlna
systemProfilesPath,
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
- using (var stream = _assembly.GetManifestResourceStream(name))
+ // The stream should exist as we just got its name from GetManifestResourceNames
+ using (var stream = _assembly.GetManifestResourceStream(name)!)
{
var fileInfo = _fileSystem.GetFileInfo(path);
@@ -413,6 +378,7 @@ namespace Emby.Dlna
Directory.CreateDirectory(UserProfilesPath);
}
+ ///
public void DeleteProfile(string id)
{
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
@@ -430,6 +396,7 @@ namespace Emby.Dlna
}
}
+ ///
public void CreateProfile(DeviceProfile profile)
{
profile = ReserializeProfile(profile);
@@ -445,6 +412,7 @@ namespace Emby.Dlna
SaveProfile(profile, path, DeviceProfileType.User);
}
+ ///
public void UpdateProfile(DeviceProfile profile)
{
profile = ReserializeProfile(profile);
@@ -503,9 +471,11 @@ namespace Emby.Dlna
var json = JsonSerializer.Serialize(profile, _jsonOptions);
- return JsonSerializer.Deserialize(json, _jsonOptions);
+ // Output can't be null if the input isn't null
+ return JsonSerializer.Deserialize(json, _jsonOptions)!;
}
+ ///
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{
var profile = GetDefaultProfile();
@@ -515,6 +485,7 @@ namespace Emby.Dlna
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
}
+ ///
public ImageStream GetIcon(string filename)
{
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
@@ -532,9 +503,15 @@ namespace Emby.Dlna
private class InternalProfileInfo
{
- internal DeviceProfileInfo Info { get; set; }
+ internal InternalProfileInfo(DeviceProfileInfo info, string path)
+ {
+ Info = info;
+ Path = path;
+ }
- internal string Path { get; set; }
+ internal DeviceProfileInfo Info { get; }
+
+ internal string Path { get; }
}
}
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index 480621dd70..970c16d2e6 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -20,7 +20,7 @@
net5.0
false
true
- true
+ AllDisabledByDefault
@@ -30,10 +30,6 @@
-
- ../jellyfin.ruleset
-
-
diff --git a/Emby.Dlna/EventSubscriptionResponse.cs b/Emby.Dlna/EventSubscriptionResponse.cs
index 1b1bd426c5..635d2c47a1 100644
--- a/Emby.Dlna/EventSubscriptionResponse.cs
+++ b/Emby.Dlna/EventSubscriptionResponse.cs
@@ -6,8 +6,10 @@ namespace Emby.Dlna
{
public class EventSubscriptionResponse
{
- public EventSubscriptionResponse()
+ public EventSubscriptionResponse(string content, string contentType)
{
+ Content = content;
+ ContentType = contentType;
Headers = new Dictionary();
}
diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs
index ff81e83b5e..3c91360904 100644
--- a/Emby.Dlna/Eventing/DlnaEventManager.cs
+++ b/Emby.Dlna/Eventing/DlnaEventManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -49,11 +51,7 @@ namespace Emby.Dlna.Eventing
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
}
- return new EventSubscriptionResponse
- {
- Content = string.Empty,
- ContentType = "text/plain"
- };
+ return new EventSubscriptionResponse(string.Empty, "text/plain");
}
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
@@ -101,20 +99,12 @@ namespace Emby.Dlna.Eventing
_subscriptions.TryRemove(subscriptionId, out _);
- return new EventSubscriptionResponse
- {
- Content = string.Empty,
- ContentType = "text/plain"
- };
+ return new EventSubscriptionResponse(string.Empty, "text/plain");
}
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
{
- var response = new EventSubscriptionResponse
- {
- Content = string.Empty,
- ContentType = "text/plain"
- };
+ var response = new EventSubscriptionResponse(string.Empty, "text/plain");
response.Headers["SID"] = subscriptionId;
response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString;
diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs
index 40d73ee0e5..4fd7f81695 100644
--- a/Emby.Dlna/Eventing/EventSubscription.cs
+++ b/Emby.Dlna/Eventing/EventSubscription.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index bdfe430cf3..5d252d8dc4 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -25,11 +27,9 @@ using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
-using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
using Rssdp;
using Rssdp.Infrastructure;
-using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Dlna.Main
{
@@ -202,8 +202,8 @@ namespace Emby.Dlna.Main
{
if (_communicationsServer == null)
{
- var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
- OperatingSystem.Id == OperatingSystemId.Linux;
+ var enableMultiSocketBinding = OperatingSystem.IsWindows() ||
+ OperatingSystem.IsLinux();
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
{
@@ -266,7 +266,12 @@ namespace Emby.Dlna.Main
try
{
- _publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost)
+ _publisher = new SsdpDevicePublisher(
+ _communicationsServer,
+ _networkManager,
+ MediaBrowser.Common.System.OperatingSystem.Name,
+ Environment.OSVersion.VersionString,
+ _config.GetDlnaConfiguration().SendOnlyMatchedHost)
{
LogFunction = LogMessage,
SupportPnpRootDevice = false
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index abd99bbc3c..11fcd81cff 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -368,6 +370,42 @@ namespace Emby.Dlna.PlayTo
RestartTimer(true);
}
+ /*
+ * SetNextAvTransport is used to specify to the DLNA device what is the next track to play.
+ * Without that information, the next track command on the device does not work.
+ */
+ public async Task SetNextAvTransport(string url, string header, string metaData, CancellationToken cancellationToken = default)
+ {
+ var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
+
+ url = url.Replace("&", "&", StringComparison.Ordinal);
+
+ _logger.LogDebug("{PropertyName} - SetNextAvTransport Uri: {Url} DlnaHeaders: {Header}", Properties.Name, url, header);
+
+ var command = avCommands.ServiceActions.FirstOrDefault(c => string.Equals(c.Name, "SetNextAVTransportURI", StringComparison.OrdinalIgnoreCase));
+ if (command == null)
+ {
+ return;
+ }
+
+ var dictionary = new Dictionary
+ {
+ { "NextURI", url },
+ { "NextURIMetaData", CreateDidlMeta(metaData) }
+ };
+
+ var service = GetAvTransportService();
+
+ if (service == null)
+ {
+ throw new InvalidOperationException("Unable to find service");
+ }
+
+ var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
+ await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
private static string CreateDidlMeta(string value)
{
if (string.IsNullOrEmpty(value))
@@ -1222,10 +1260,7 @@ namespace Emby.Dlna.PlayTo
return;
}
- PlaybackStart?.Invoke(this, new PlaybackStartEventArgs
- {
- MediaInfo = mediaInfo
- });
+ PlaybackStart?.Invoke(this, new PlaybackStartEventArgs(mediaInfo));
}
private void OnPlaybackProgress(UBaseObject mediaInfo)
@@ -1235,27 +1270,17 @@ namespace Emby.Dlna.PlayTo
return;
}
- PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs
- {
- MediaInfo = mediaInfo
- });
+ PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs(mediaInfo));
}
private void OnPlaybackStop(UBaseObject mediaInfo)
{
- PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
- {
- MediaInfo = mediaInfo
- });
+ PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs(mediaInfo));
}
private void OnMediaChanged(UBaseObject old, UBaseObject newMedia)
{
- MediaChanged?.Invoke(this, new MediaChangedEventArgs
- {
- OldMediaInfo = old,
- NewMediaInfo = newMedia
- });
+ MediaChanged?.Invoke(this, new MediaChangedEventArgs(old, newMedia));
}
///
diff --git a/Emby.Dlna/PlayTo/DeviceInfo.cs b/Emby.Dlna/PlayTo/DeviceInfo.cs
index d3daab9e0a..2acfff4eb6 100644
--- a/Emby.Dlna/PlayTo/DeviceInfo.cs
+++ b/Emby.Dlna/PlayTo/DeviceInfo.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.Collections.Generic;
diff --git a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
index dabd079afd..0f7a524d62 100644
--- a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
+++ b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
using System;
@@ -6,6 +6,12 @@ namespace Emby.Dlna.PlayTo
{
public class MediaChangedEventArgs : EventArgs
{
+ public MediaChangedEventArgs(UBaseObject oldMediaInfo, UBaseObject newMediaInfo)
+ {
+ OldMediaInfo = oldMediaInfo;
+ NewMediaInfo = newMediaInfo;
+ }
+
public UBaseObject OldMediaInfo { get; set; }
public UBaseObject NewMediaInfo { get; set; }
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index ee09cc65a9..0e49fd2c02 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -102,6 +104,22 @@ namespace Emby.Dlna.PlayTo
_deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft;
}
+ /*
+ * Send a message to the DLNA device to notify what is the next track in the playlist.
+ */
+ private async Task SendNextTrackMessage(int currentPlayListItemIndex, CancellationToken cancellationToken)
+ {
+ if (currentPlayListItemIndex >= 0 && currentPlayListItemIndex < _playlist.Count - 1)
+ {
+ // The current playing item is indeed in the play list and we are not yet at the end of the playlist.
+ var nextItemIndex = currentPlayListItemIndex + 1;
+ var nextItem = _playlist[nextItemIndex];
+
+ // Send the SetNextAvTransport message.
+ await _device.SetNextAvTransport(nextItem.StreamUrl, GetDlnaHeaders(nextItem), nextItem.Didl, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
private void OnDeviceUnavailable()
{
try
@@ -156,6 +174,15 @@ namespace Emby.Dlna.PlayTo
var newItemProgress = GetProgressInfo(streamInfo);
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
+
+ // Send a message to the DLNA device to notify what is the next track in the playlist.
+ var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId == streamInfo.ItemId);
+ if (currentItemIndex >= 0)
+ {
+ _currentPlaylistIndex = currentItemIndex;
+ }
+
+ await SendNextTrackMessage(currentItemIndex, CancellationToken.None);
}
catch (Exception ex)
{
@@ -425,6 +452,11 @@ namespace Emby.Dlna.PlayTo
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex);
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
+
+ // Send a message to the DLNA device to notify what is the next track in the play list.
+ var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
+ await SendNextTrackMessage(newItemIndex, CancellationToken.None);
+
return;
}
@@ -623,6 +655,9 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
+ // Send a message to the DLNA device to notify what is the next track in the play list.
+ await SendNextTrackMessage(index, cancellationToken);
+
var streamInfo = currentitem.StreamInfo;
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
{
@@ -736,6 +771,10 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
+ // Send a message to the DLNA device to notify what is the next track in the play list.
+ var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
+ await SendNextTrackMessage(newItemIndex, CancellationToken.None);
+
if (EnableClientSideSeek(newItem.StreamInfo))
{
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
@@ -761,6 +800,10 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
+ // Send a message to the DLNA device to notify what is the next track in the play list.
+ var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
+ await SendNextTrackMessage(newItemIndex, CancellationToken.None);
+
if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0)
{
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index 8272e505a0..7927f5f8f9 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -171,7 +173,9 @@ namespace Emby.Dlna.PlayTo
uuid = uri.ToString().GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
- var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null);
+ var sessionInfo = await _sessionManager
+ .LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null)
+ .ConfigureAwait(false);
var controller = sessionInfo.SessionControllers.OfType().FirstOrDefault();
@@ -188,7 +192,7 @@ namespace Emby.Dlna.PlayTo
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
- string serverAddress = _appHost.GetSmartApiUrl(info.LocalIpAddress);
+ string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIpAddress);
controller = new PlayToController(
sessionInfo,
diff --git a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs
index d14617c8a0..c95d8b1e84 100644
--- a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs
+++ b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs
@@ -6,6 +6,11 @@ namespace Emby.Dlna.PlayTo
{
public class PlaybackProgressEventArgs : EventArgs
{
+ public PlaybackProgressEventArgs(UBaseObject mediaInfo)
+ {
+ MediaInfo = mediaInfo;
+ }
+
public UBaseObject MediaInfo { get; set; }
}
}
diff --git a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs
index 3f8d552636..619c861ed9 100644
--- a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs
+++ b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs
@@ -6,6 +6,11 @@ namespace Emby.Dlna.PlayTo
{
public class PlaybackStartEventArgs : EventArgs
{
+ public PlaybackStartEventArgs(UBaseObject mediaInfo)
+ {
+ MediaInfo = mediaInfo;
+ }
+
public UBaseObject MediaInfo { get; set; }
}
}
diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
index deeb47918d..d0ec250591 100644
--- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
+++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
@@ -6,6 +6,11 @@ namespace Emby.Dlna.PlayTo
{
public class PlaybackStoppedEventArgs : EventArgs
{
+ public PlaybackStoppedEventArgs(UBaseObject mediaInfo)
+ {
+ MediaInfo = mediaInfo;
+ }
+
public UBaseObject MediaInfo { get; set; }
}
}
diff --git a/Emby.Dlna/PlayTo/PlaylistItem.cs b/Emby.Dlna/PlayTo/PlaylistItem.cs
index 85846166cf..5056e69ae7 100644
--- a/Emby.Dlna/PlayTo/PlaylistItem.cs
+++ b/Emby.Dlna/PlayTo/PlaylistItem.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna;
diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
index e28840a896..6574913032 100644
--- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
+++ b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.IO;
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index d9f1ce4907..f14f73bb6f 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs
index cbcf66e45c..b58669355d 100644
--- a/Emby.Dlna/PlayTo/TransportCommands.cs
+++ b/Emby.Dlna/PlayTo/TransportCommands.cs
@@ -46,7 +46,7 @@ namespace Emby.Dlna.PlayTo
{
var serviceAction = new ServiceAction
{
- Name = container.GetValue(UPnpNamespaces.Svc + "name"),
+ Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
};
var argumentList = serviceAction.ArgumentList;
@@ -68,9 +68,9 @@ namespace Emby.Dlna.PlayTo
return new Argument
{
- Name = container.GetValue(UPnpNamespaces.Svc + "name"),
- Direction = container.GetValue(UPnpNamespaces.Svc + "direction"),
- RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable")
+ Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
+ Direction = container.GetValue(UPnpNamespaces.Svc + "direction") ?? string.Empty,
+ RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") ?? string.Empty
};
}
@@ -89,8 +89,8 @@ namespace Emby.Dlna.PlayTo
return new StateVariable
{
- Name = container.GetValue(UPnpNamespaces.Svc + "name"),
- DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"),
+ Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
+ DataType = container.GetValue(UPnpNamespaces.Svc + "dataType") ?? string.Empty,
AllowedValues = allowedValues
};
}
@@ -166,7 +166,7 @@ namespace Emby.Dlna.PlayTo
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));
diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs
index 0d9478e42e..02d2da58d6 100644
--- a/Emby.Dlna/PlayTo/uBaseObject.cs
+++ b/Emby.Dlna/PlayTo/uBaseObject.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
index 09525aae4e..3f3dfccd3a 100644
--- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs
+++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
@@ -250,7 +250,8 @@ namespace Emby.Dlna.Server
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 GetIcons()
diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs
index fda8346f9e..581e4a2861 100644
--- a/Emby.Dlna/Service/BaseControlHandler.cs
+++ b/Emby.Dlna/Service/BaseControlHandler.cs
@@ -6,9 +6,9 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
+using Diacritics.Extensions;
using Emby.Dlna.Didl;
using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Extensions;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.Service
@@ -47,7 +47,7 @@ namespace Emby.Dlna.Service
private async Task ProcessControlRequestInternalAsync(ControlRequest request)
{
- ControlRequestInfo requestInfo = null;
+ ControlRequestInfo? requestInfo = null;
using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
{
@@ -95,11 +95,7 @@ namespace Emby.Dlna.Service
var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=", StringComparison.Ordinal);
- var controlResponse = new ControlResponse
- {
- Xml = xml,
- IsSuccessful = true
- };
+ var controlResponse = new ControlResponse(xml, true);
controlResponse.Headers.Add("EXT", string.Empty);
@@ -151,7 +147,7 @@ namespace Emby.Dlna.Service
private async Task ParseBodyTagAsync(XmlReader reader)
{
- string namespaceURI = null, localName = null;
+ string? namespaceURI = null, localName = null;
await reader.MoveToContentAsync().ConfigureAwait(false);
await reader.ReadAsync().ConfigureAwait(false);
diff --git a/Emby.Dlna/Service/ControlErrorHandler.cs b/Emby.Dlna/Service/ControlErrorHandler.cs
index f2b5dd9ca8..3e2cd6d2e4 100644
--- a/Emby.Dlna/Service/ControlErrorHandler.cs
+++ b/Emby.Dlna/Service/ControlErrorHandler.cs
@@ -46,11 +46,7 @@ namespace Emby.Dlna.Service
writer.WriteEndDocument();
}
- return new ControlResponse
- {
- Xml = builder.ToString(),
- IsSuccessful = false
- };
+ return new ControlResponse(builder.ToString(), false);
}
}
}
diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
index d13871add8..391dda1479 100644
--- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -104,7 +106,7 @@ namespace Emby.Dlna.Ssdp
{
Location = e.DiscoveredDevice.DescriptionLocation,
Headers = headers,
- LocalIpAddress = e.LocalIpAddress
+ RemoteIpAddress = e.RemoteIpAddress
});
DeviceDiscoveredInternal?.Invoke(this, args);
diff --git a/Emby.Dlna/Ssdp/SsdpExtensions.cs b/Emby.Dlna/Ssdp/SsdpExtensions.cs
index e7a52f168f..d00eb02b46 100644
--- a/Emby.Dlna/Ssdp/SsdpExtensions.cs
+++ b/Emby.Dlna/Ssdp/SsdpExtensions.cs
@@ -7,21 +7,21 @@ namespace Emby.Dlna.Ssdp
{
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);
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);
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;
}
}
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj
index 5c5afe1c6e..baf350c6f1 100644
--- a/Emby.Drawing/Emby.Drawing.csproj
+++ b/Emby.Drawing/Emby.Drawing.csproj
@@ -9,8 +9,7 @@
net5.0
false
true
- true
- enable
+ AllDisabledByDefault
@@ -30,8 +29,4 @@
-
- ../jellyfin.ruleset
-
-
diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs
index 8b47dd12e4..2b610ec796 100644
--- a/Emby.Naming/Audio/AudioFileParser.cs
+++ b/Emby.Naming/Audio/AudioFileParser.cs
@@ -1,7 +1,7 @@
using System;
using System.IO;
-using System.Linq;
using Emby.Naming.Common;
+using Jellyfin.Extensions;
namespace Emby.Naming.Audio
{
@@ -18,8 +18,8 @@ namespace Emby.Naming.Audio
/// True if file at path is audio file.
public static bool IsAudioFile(string path, NamingOptions options)
{
- var extension = Path.GetExtension(path);
- return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+ var extension = Path.GetExtension(path.AsSpan());
+ return options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs
index 15702ff2ca..acd8905af6 100644
--- a/Emby.Naming/AudioBook/AudioBookInfo.cs
+++ b/Emby.Naming/AudioBook/AudioBookInfo.cs
@@ -15,7 +15,7 @@ namespace Emby.Naming.AudioBook
/// List of files composing the actual audiobook.
/// List of extra files.
/// Alternative version of files.
- public AudioBookInfo(string name, int? year, List files, List extras, List alternateVersions)
+ public AudioBookInfo(string name, int? year, IReadOnlyList files, IReadOnlyList extras, IReadOnlyList alternateVersions)
{
Name = name;
Year = year;
@@ -39,18 +39,18 @@ namespace Emby.Naming.AudioBook
/// Gets or sets the files.
///
/// The files.
- public List Files { get; set; }
+ public IReadOnlyList Files { get; set; }
///
/// Gets or sets the extras.
///
/// The extras.
- public List Extras { get; set; }
+ public IReadOnlyList Extras { get; set; }
///
/// Gets or sets the alternate versions.
///
/// The alternate versions.
- public List AlternateVersions { get; set; }
+ public IReadOnlyList AlternateVersions { get; set; }
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs
index ca53228903..1e4a8d2edc 100644
--- a/Emby.Naming/AudioBook/AudioBookListResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs
@@ -87,7 +87,7 @@ namespace Emby.Naming.AudioBook
foreach (var audioFile in group)
{
var name = Path.GetFileNameWithoutExtension(audioFile.Path);
- if (name.Equals("audiobook") ||
+ if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase) ||
name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) ||
name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase))
{
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 22a3e8bb49..915ce42cc9 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -137,7 +137,7 @@ namespace Emby.Naming.Common
CleanStrings = new[]
{
- @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
+ @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
@"(\[.*\])"
};
@@ -277,14 +277,14 @@ namespace Emby.Naming.Common
IsNamed = true
},
- new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")
+ new EpisodeExpression(@"[\\\/\._ \[\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\.[1-9])(?![0-9]))?)([^\\\/]*)$")
{
SupportsAbsoluteEpisodeNumbers = true
},
// Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names
// [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name
- new EpisodeExpression(@".*?(\[.*?\])+.*?(?[\w\s]+?)[\s_]*-[\s_]*(?[0-9]+).*$")
+ new EpisodeExpression(@".*[\\\/]?.*?(\[.*?\])+.*?(?[-\w\s]+?)[\s_]*-[\s_]*(?[0-9]+).*$")
{
IsNamed = true
},
@@ -305,6 +305,12 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming
+ // "Episode 16", "Episode 16 - Title"
+ new EpisodeExpression(@"[Ee]pisode (?[0-9]+)(-(?[0-9]+))?[^\\\/]*$")
+ {
+ IsNamed = true
+ },
+
new EpisodeExpression(@".*(\\|\/)[sS]?(?[0-9]+)[xX](?[0-9]+)[^\\\/]*$")
{
IsNamed = true
@@ -362,12 +368,6 @@ namespace Emby.Naming.Common
IsOptimistic = true,
IsNamed = true
},
- // "Episode 16", "Episode 16 - Title"
- new EpisodeExpression(@".*[\\\/][^\\\/]* (?[0-9]{1,3})(-(?[0-9]{2,3}))*[^\\\/]*$")
- {
- IsOptimistic = true,
- IsNamed = true
- }
};
EpisodeWithoutSeasonExpressions = new[]
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index 63116f3680..07d879e96a 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -1,4 +1,4 @@
-
+
@@ -9,12 +9,11 @@
net5.0
false
true
- true
true
true
true
snupkg
- enable
+ AllDisabledByDefault
@@ -23,11 +22,12 @@
-
+
-
+
+
@@ -49,8 +49,4 @@
-
- ../jellyfin.ruleset
-
-
diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs
index f7df587864..5e952e47b7 100644
--- a/Emby.Naming/TV/EpisodeResolver.cs
+++ b/Emby.Naming/TV/EpisodeResolver.cs
@@ -16,7 +16,7 @@ namespace Emby.Naming.TV
///
/// Initializes a new instance of the class.
///
- /// object containing VideoFileExtensions and passed to , , and .
+ /// object containing VideoFileExtensions and passed to , and .
public EpisodeResolver(NamingOptions options)
{
_options = options;
@@ -62,12 +62,16 @@ namespace Emby.Naming.TV
container = extension.TrimStart('.');
}
- var flags = new FlagParser(_options).GetFlags(path);
- var format3DResult = new Format3DParser(_options).Parse(flags);
+ var format3DResult = Format3DParser.Parse(path, _options);
var parsingResult = new EpisodePathParser(_options)
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
+ if (!parsingResult.Success && !isStub)
+ {
+ return null;
+ }
+
return new EpisodeInfo(path)
{
Container = container,
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index 1d3b36a1ad..a32af002cc 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -1,6 +1,5 @@
using System;
using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Audio;
using Emby.Naming.Common;
@@ -29,72 +28,75 @@ namespace Emby.Naming.Video
/// Path to file.
/// Returns object.
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();
- if (rule.MediaType == MediaType.Audio)
+ for (var i = 0; i < _options.VideoExtraRules.Length; i++)
{
- if (!AudioFileParser.IsAudioFile(path, _options))
+ var rule = _options.VideoExtraRules[i];
+ if (rule.MediaType == MediaType.Audio)
+ {
+ if (!AudioFileParser.IsAudioFile(path, _options))
+ {
+ continue;
+ }
+ }
+ else if (rule.MediaType == MediaType.Video)
+ {
+ if (!VideoResolver.IsVideoFile(path, _options))
+ {
+ continue;
+ }
+ }
+
+ var pathSpan = path.AsSpan();
+ if (rule.RuleType == ExtraRuleType.Filename)
+ {
+ var filename = Path.GetFileNameWithoutExtension(pathSpan);
+
+ if (filename.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
+ }
+ else if (rule.RuleType == ExtraRuleType.Suffix)
+ {
+ var filename = Path.GetFileNameWithoutExtension(pathSpan);
+
+ if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
+ }
+ else if (rule.RuleType == ExtraRuleType.Regex)
+ {
+ var filename = Path.GetFileName(path);
+
+ var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
+
+ if (regex.IsMatch(filename))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
+ }
+ else if (rule.RuleType == ExtraRuleType.DirectoryName)
+ {
+ var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan));
+ if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
+ }
+
+ if (result.ExtraType != null)
{
return result;
}
}
- else if (rule.MediaType == MediaType.Video)
- {
- if (!new VideoResolver(_options).IsVideoFile(path))
- {
- return result;
- }
- }
-
- if (rule.RuleType == ExtraRuleType.Filename)
- {
- var filename = Path.GetFileNameWithoutExtension(path);
-
- if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase))
- {
- result.ExtraType = rule.ExtraType;
- result.Rule = rule;
- }
- }
- else if (rule.RuleType == ExtraRuleType.Suffix)
- {
- var filename = Path.GetFileNameWithoutExtension(path);
-
- if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0)
- {
- result.ExtraType = rule.ExtraType;
- result.Rule = rule;
- }
- }
- else if (rule.RuleType == ExtraRuleType.Regex)
- {
- var filename = Path.GetFileName(path);
-
- var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
-
- if (regex.IsMatch(filename))
- {
- result.ExtraType = rule.ExtraType;
- result.Rule = rule;
- }
- }
- else if (rule.RuleType == ExtraRuleType.DirectoryName)
- {
- var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
- if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
- {
- result.ExtraType = rule.ExtraType;
- result.Rule = rule;
- }
- }
return result;
}
diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs
deleted file mode 100644
index 439de18138..0000000000
--- a/Emby.Naming/Video/FlagParser.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-using System.IO;
-using Emby.Naming.Common;
-
-namespace Emby.Naming.Video
-{
- ///
- /// Parses list of flags from filename based on delimiters.
- ///
- public class FlagParser
- {
- private readonly NamingOptions _options;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// object containing VideoFlagDelimiters.
- public FlagParser(NamingOptions options)
- {
- _options = options;
- }
-
- ///
- /// Calls GetFlags function with _options.VideoFlagDelimiters parameter.
- ///
- /// Path to file.
- /// List of found flags.
- public string[] GetFlags(string path)
- {
- return GetFlags(path, _options.VideoFlagDelimiters);
- }
-
- ///
- /// Parses flags from filename based on delimiters.
- ///
- /// Path to file.
- /// Delimiters used to extract flags.
- /// List of found flags.
- public string[] GetFlags(string path, char[] delimiters)
- {
- if (string.IsNullOrEmpty(path))
- {
- return Array.Empty();
- }
-
- // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
-
- var file = Path.GetFileName(path);
-
- return file.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
- }
- }
-}
diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs
index 4fd5d78ba7..0890899894 100644
--- a/Emby.Naming/Video/Format3DParser.cs
+++ b/Emby.Naming/Video/Format3DParser.cs
@@ -1,45 +1,37 @@
using System;
-using System.Linq;
using Emby.Naming.Common;
namespace Emby.Naming.Video
{
///
- /// Parste 3D format related flags.
+ /// Parse 3D format related flags.
///
- public class Format3DParser
+ public static class Format3DParser
{
- private readonly NamingOptions _options;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// object containing VideoFlagDelimiters and passes options to .
- public Format3DParser(NamingOptions options)
- {
- _options = options;
- }
+ // Static default result to save on allocation costs.
+ private static readonly Format3DResult _defaultResult = new (false, null);
///
/// Parse 3D format related flags.
///
/// Path to file.
+ /// The naming options.
/// Returns object.
- public Format3DResult Parse(string path)
+ public static Format3DResult Parse(ReadOnlySpan path, NamingOptions namingOptions)
{
- int oldLen = _options.VideoFlagDelimiters.Length;
- var delimiters = new char[oldLen + 1];
- _options.VideoFlagDelimiters.CopyTo(delimiters, 0);
+ int oldLen = namingOptions.VideoFlagDelimiters.Length;
+ Span delimiters = stackalloc char[oldLen + 1];
+ namingOptions.VideoFlagDelimiters.AsSpan().CopyTo(delimiters);
delimiters[oldLen] = ' ';
- return Parse(new FlagParser(_options).GetFlags(path, delimiters));
+ return Parse(path, delimiters, namingOptions);
}
- internal Format3DResult Parse(string[] videoFlags)
+ private static Format3DResult Parse(ReadOnlySpan path, ReadOnlySpan delimiters, NamingOptions namingOptions)
{
- foreach (var rule in _options.Format3DRules)
+ foreach (var rule in namingOptions.Format3DRules)
{
- var result = Parse(videoFlags, rule);
+ var result = Parse(path, rule, delimiters);
if (result.Is3D)
{
@@ -47,51 +39,43 @@ namespace Emby.Naming.Video
}
}
- return new Format3DResult();
+ return _defaultResult;
}
- private static Format3DResult Parse(string[] videoFlags, Format3DRule rule)
+ private static Format3DResult Parse(ReadOnlySpan path, Format3DRule rule, ReadOnlySpan delimiters)
{
- var result = new Format3DResult();
+ bool is3D = false;
+ string? format3D = null;
- if (string.IsNullOrEmpty(rule.PrecedingToken))
+ // If there's no preceding token we just consider it found
+ var foundPrefix = string.IsNullOrEmpty(rule.PrecedingToken);
+ while (path.Length > 0)
{
- result.Format3D = new[] { rule.Token }.FirstOrDefault(i => videoFlags.Contains(i, StringComparer.OrdinalIgnoreCase));
- result.Is3D = !string.IsNullOrEmpty(result.Format3D);
-
- if (result.Is3D)
+ var index = path.IndexOfAny(delimiters);
+ if (index == -1)
{
- result.Tokens.Add(rule.Token);
- }
- }
- else
- {
- var foundPrefix = false;
- string? format = null;
-
- foreach (var flag in videoFlags)
- {
- if (foundPrefix)
- {
- result.Tokens.Add(rule.PrecedingToken);
-
- if (string.Equals(rule.Token, flag, StringComparison.OrdinalIgnoreCase))
- {
- format = flag;
- result.Tokens.Add(rule.Token);
- }
-
- break;
- }
-
- foundPrefix = string.Equals(flag, rule.PrecedingToken, StringComparison.OrdinalIgnoreCase);
+ index = path.Length - 1;
}
- result.Is3D = foundPrefix && !string.IsNullOrEmpty(format);
- result.Format3D = format;
+ var currentSlice = path[..index];
+ path = path[(index + 1)..];
+
+ if (!foundPrefix)
+ {
+ foundPrefix = currentSlice.Equals(rule.PrecedingToken, StringComparison.OrdinalIgnoreCase);
+ continue;
+ }
+
+ is3D = foundPrefix && currentSlice.Equals(rule.Token, StringComparison.OrdinalIgnoreCase);
+
+ if (is3D)
+ {
+ format3D = rule.Token;
+ break;
+ }
}
- return result;
+ return is3D ? new Format3DResult(true, format3D) : _defaultResult;
}
}
}
diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs
index ac935f2030..aac959c133 100644
--- a/Emby.Naming/Video/Format3DResult.cs
+++ b/Emby.Naming/Video/Format3DResult.cs
@@ -1,5 +1,3 @@
-using System.Collections.Generic;
-
namespace Emby.Naming.Video
{
///
@@ -10,27 +8,24 @@ namespace Emby.Naming.Video
///
/// Initializes a new instance of the class.
///
- public Format3DResult()
+ /// A value indicating whether the parsed string contains 3D tokens.
+ /// The 3D format. Value might be null if [is3D] is false.
+ public Format3DResult(bool is3D, string? format3D)
{
- Tokens = new List();
+ Is3D = is3D;
+ Format3D = format3D;
}
///
- /// Gets or sets a value indicating whether [is3 d].
+ /// Gets a value indicating whether [is3 d].
///
/// true if [is3 d]; otherwise, false.
- public bool Is3D { get; set; }
+ public bool Is3D { get; }
///
- /// Gets or sets the format3 d.
+ /// Gets the format3 d.
///
/// The format3 d.
- public string? Format3D { get; set; }
-
- ///
- /// Gets or sets the tokens.
- ///
- /// The tokens.
- public List Tokens { get; set; }
+ public string? Format3D { get; }
}
}
diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs
index 550c429614..36f65a5624 100644
--- a/Emby.Naming/Video/StackResolver.cs
+++ b/Emby.Naming/Video/StackResolver.cs
@@ -85,10 +85,8 @@ namespace Emby.Naming.Video
/// Enumerable of videos.
public IEnumerable Resolve(IEnumerable files)
{
- var resolver = new VideoResolver(_options);
-
var list = files
- .Where(i => i.IsDirectory || resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName))
+ .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options))
.OrderBy(i => i.FullName)
.ToList();
diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs
index 1457db7378..481773ff60 100644
--- a/Emby.Naming/Video/VideoFileInfo.cs
+++ b/Emby.Naming/Video/VideoFileInfo.cs
@@ -1,3 +1,4 @@
+using System;
using MediaBrowser.Model.Entities;
namespace Emby.Naming.Video
@@ -106,9 +107,9 @@ namespace Emby.Naming.Video
/// Gets the file name without extension.
///
/// The file name without extension.
- public string FileNameWithoutExtension => !IsDirectory
- ? System.IO.Path.GetFileNameWithoutExtension(Path)
- : System.IO.Path.GetFileName(Path);
+ public ReadOnlySpan FileNameWithoutExtension => !IsDirectory
+ ? System.IO.Path.GetFileNameWithoutExtension(Path.AsSpan())
+ : System.IO.Path.GetFileName(Path.AsSpan());
///
public override string ToString()
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 7b6a1705ba..ed7d511a39 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -12,31 +12,19 @@ namespace Emby.Naming.Video
///
/// Resolves alternative versions and extras from list of video files.
///
- public class VideoListResolver
+ public static class VideoListResolver
{
- private readonly NamingOptions _options;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// object containing CleanStringRegexes and VideoFlagDelimiters and passes options to and .
- public VideoListResolver(NamingOptions options)
- {
- _options = options;
- }
-
///
/// Resolves alternative versions and extras from list of video files.
///
/// List of related video files.
+ /// The naming options.
/// Indication we should consider multi-versions of content.
/// Returns enumerable of which groups files together when related.
- public IEnumerable Resolve(List files, bool supportMultiVersion = true)
+ public static IEnumerable Resolve(IEnumerable files, NamingOptions namingOptions, bool supportMultiVersion = true)
{
- var videoResolver = new VideoResolver(_options);
-
var videoInfos = files
- .Select(i => videoResolver.Resolve(i.FullName, i.IsDirectory))
+ .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))
.OfType()
.ToList();
@@ -46,7 +34,7 @@ namespace Emby.Naming.Video
.Where(i => i.ExtraType == null)
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
- var stackResult = new StackResolver(_options)
+ var stackResult = new StackResolver(namingOptions)
.Resolve(nonExtras).ToList();
var remainingFiles = videoInfos
@@ -59,23 +47,17 @@ namespace Emby.Naming.Video
{
var info = new VideoInfo(stack.Name)
{
- Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack))
+ Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions))
.OfType()
.ToList()
};
info.Year = info.Files[0].Year;
- var extraBaseNames = new List { stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0]) };
-
- var extras = GetExtras(remainingFiles, extraBaseNames);
+ var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters);
if (extras.Count > 0)
{
- remainingFiles = remainingFiles
- .Except(extras)
- .ToList();
-
info.Extras = extras;
}
@@ -88,15 +70,12 @@ namespace Emby.Naming.Video
foreach (var media in standaloneMedia)
{
- var info = new VideoInfo(media.Name) { Files = new List { media } };
+ var info = new VideoInfo(media.Name) { Files = new[] { media } };
info.Year = info.Files[0].Year;
- var extras = GetExtras(remainingFiles, new List { media.FileNameWithoutExtension });
-
- remainingFiles = remainingFiles
- .Except(extras.Concat(new[] { media }))
- .ToList();
+ remainingFiles.Remove(media);
+ var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters);
info.Extras = extras;
@@ -105,8 +84,7 @@ namespace Emby.Naming.Video
if (supportMultiVersion)
{
- list = GetVideosGroupedByVersion(list)
- .ToList();
+ list = GetVideosGroupedByVersion(list, namingOptions);
}
// If there's only one resolved video, use the folder name as well to find extras
@@ -114,19 +92,14 @@ namespace Emby.Naming.Video
{
var info = list[0];
var videoPath = list[0].Files[0].Path;
- var parentPath = Path.GetDirectoryName(videoPath);
+ var parentPath = Path.GetDirectoryName(videoPath.AsSpan());
- if (!string.IsNullOrEmpty(parentPath))
+ if (!parentPath.IsEmpty)
{
var folderName = Path.GetFileName(parentPath);
- if (!string.IsNullOrEmpty(folderName))
+ if (!folderName.IsEmpty)
{
- var extras = GetExtras(remainingFiles, new List { folderName });
-
- remainingFiles = remainingFiles
- .Except(extras)
- .ToList();
-
+ var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters);
extras.AddRange(info.Extras);
info.Extras = extras;
}
@@ -164,96 +137,168 @@ namespace Emby.Naming.Video
// Whatever files are left, just add them
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
{
- Files = new List { i },
+ Files = new[] { i },
Year = i.Year
}));
return list;
}
- private IEnumerable GetVideosGroupedByVersion(List videos)
+ private static List GetVideosGroupedByVersion(List videos, NamingOptions namingOptions)
{
if (videos.Count == 0)
{
return videos;
}
- var list = new List();
+ var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path.AsSpan()));
- var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path));
-
- if (!string.IsNullOrEmpty(folderName)
- && folderName.Length > 1
- && videos.All(i => i.Files.Count == 1
- && IsEligibleForMultiVersion(folderName, i.Files[0].Path))
- && HaveSameYear(videos))
+ if (folderName.Length <= 1 || !HaveSameYear(videos))
{
- var ordered = videos.OrderBy(i => i.Name).ToList();
-
- list.Add(ordered[0]);
-
- var alternateVersionsLen = ordered.Count - 1;
- var alternateVersions = new VideoFileInfo[alternateVersionsLen];
- for (int i = 0; i < alternateVersionsLen; i++)
- {
- alternateVersions[i] = ordered[i + 1].Files[0];
- }
-
- list[0].AlternateVersions = alternateVersions;
- list[0].Name = folderName;
- var extras = ordered.Skip(1).SelectMany(i => i.Extras).ToList();
- extras.AddRange(list[0].Extras);
- list[0].Extras = extras;
-
- return list;
+ return videos;
}
- return videos;
- }
-
- private bool HaveSameYear(List videos)
- {
- return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2;
- }
-
- private bool IsEligibleForMultiVersion(string folderName, string testFilePath)
- {
- string testFilename = Path.GetFileNameWithoutExtension(testFilePath);
- if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
+ // Cannot use Span inside local functions and delegates thus we cannot use LINQ here nor merge with the above [if]
+ for (var i = 0; i < videos.Count; i++)
{
- // Remove the folder name before cleaning as we don't care about cleaning that part
- if (folderName.Length <= testFilename.Length)
+ var video = videos[i];
+ if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
{
- testFilename = testFilename.Substring(folderName.Length).Trim();
+ return videos;
}
-
- if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
- {
- testFilename = cleanName.Trim().ToString();
- }
-
- // The CleanStringParser should have removed common keywords etc.
- return string.IsNullOrEmpty(testFilename)
- || testFilename[0] == '-'
- || Regex.IsMatch(testFilename, @"^\[([^]]*)\]");
}
- return false;
- }
+ // The list is created and overwritten in the caller, so we are allowed to do in-place sorting
+ videos.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal));
- private List GetExtras(IEnumerable remainingFiles, List baseNames)
- {
- foreach (var name in baseNames.ToList())
+ var list = new List
{
- var trimmedName = name.TrimEnd().TrimEnd(_options.VideoFlagDelimiters).TrimEnd();
- baseNames.Add(trimmedName);
+ videos[0]
+ };
+
+ var alternateVersionsLen = videos.Count - 1;
+ var alternateVersions = new VideoFileInfo[alternateVersionsLen];
+ var extras = new List(list[0].Extras);
+ for (int i = 0; i < alternateVersionsLen; i++)
+ {
+ var video = videos[i + 1];
+ alternateVersions[i] = video.Files[0];
+ extras.AddRange(video.Extras);
}
- return remainingFiles
- .Where(i => i.ExtraType != null)
- .Where(i => baseNames.Any(b =>
- i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
- .ToList();
+ list[0].AlternateVersions = alternateVersions;
+ list[0].Name = folderName.ToString();
+ list[0].Extras = extras;
+
+ return list;
+ }
+
+ private static bool HaveSameYear(IReadOnlyList videos)
+ {
+ if (videos.Count == 1)
+ {
+ return true;
+ }
+
+ var firstYear = videos[0].Year ?? -1;
+ for (var i = 1; i < videos.Count; i++)
+ {
+ if ((videos[i].Year ?? -1) != firstYear)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool IsEligibleForMultiVersion(ReadOnlySpan folderName, string testFilePath, NamingOptions namingOptions)
+ {
+ var testFilename = Path.GetFileNameWithoutExtension(testFilePath.AsSpan());
+ if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // Remove the folder name before cleaning as we don't care about cleaning that part
+ if (folderName.Length <= testFilename.Length)
+ {
+ testFilename = testFilename[folderName.Length..].Trim();
+ }
+
+ // There are no span overloads for regex unfortunately
+ var tmpTestFilename = testFilename.ToString();
+ if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
+ {
+ tmpTestFilename = cleanName.Trim().ToString();
+ }
+
+ // The CleanStringParser should have removed common keywords etc.
+ return string.IsNullOrEmpty(tmpTestFilename)
+ || testFilename[0] == '-'
+ || Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
+ }
+
+ private static ReadOnlySpan TrimFilenameDelimiters(ReadOnlySpan name, ReadOnlySpan videoFlagDelimiters)
+ {
+ return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
+ }
+
+ private static bool StartsWith(ReadOnlySpan fileName, ReadOnlySpan baseName, ReadOnlySpan trimmedBaseName)
+ {
+ if (baseName.IsEmpty)
+ {
+ return false;
+ }
+
+ return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase)
+ || (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase));
+ }
+
+ ///
+ /// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles].
+ ///
+ /// The list of remaining filenames.
+ /// The base name to use for the comparison.
+ /// The video flag delimiters.
+ /// A list of video extras for [baseName].
+ private static List ExtractExtras(IList remainingFiles, ReadOnlySpan baseName, ReadOnlySpan videoFlagDelimiters)
+ {
+ return ExtractExtras(remainingFiles, baseName, ReadOnlySpan.Empty, videoFlagDelimiters);
+ }
+
+ ///
+ /// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles].
+ ///
+ /// The list of remaining filenames.
+ /// The first base name to use for the comparison.
+ /// The second base name to use for the comparison.
+ /// The video flag delimiters.
+ /// A list of video extras for [firstBaseName] and [secondBaseName].
+ private static List ExtractExtras(IList remainingFiles, ReadOnlySpan firstBaseName, ReadOnlySpan secondBaseName, ReadOnlySpan videoFlagDelimiters)
+ {
+ var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters);
+ var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters);
+
+ var result = new List();
+ for (var pos = remainingFiles.Count - 1; pos >= 0; pos--)
+ {
+ var file = remainingFiles[pos];
+ if (file.ExtraType == null)
+ {
+ continue;
+ }
+
+ var filename = file.FileNameWithoutExtension;
+ if (StartsWith(filename, firstBaseName, trimmedFirstBaseName)
+ || StartsWith(filename, secondBaseName, trimmedSecondBaseName))
+ {
+ result.Add(file);
+ remainingFiles.RemoveAt(pos);
+ }
+ }
+
+ return result;
}
}
}
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index 79a6da8f7b..3b1d906c64 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -1,46 +1,36 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
-using System.Linq;
using Emby.Naming.Common;
+using Jellyfin.Extensions;
namespace Emby.Naming.Video
{
///
/// Resolves from file path.
///
- public class VideoResolver
+ public static class VideoResolver
{
- private readonly NamingOptions _options;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// object containing VideoFileExtensions, StubFileExtensions, CleanStringRegexes and CleanDateTimeRegexes
- /// and passes options in , , and .
- public VideoResolver(NamingOptions options)
- {
- _options = options;
- }
-
///
/// Resolves the directory.
///
/// The path.
+ /// The naming options.
/// VideoFileInfo.
- public VideoFileInfo? ResolveDirectory(string? path)
+ public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions)
{
- return Resolve(path, true);
+ return Resolve(path, true, namingOptions);
}
///
/// Resolves the file.
///
/// The path.
+ /// The naming options.
/// VideoFileInfo.
- public VideoFileInfo? ResolveFile(string? path)
+ public static VideoFileInfo? ResolveFile(string? path, NamingOptions namingOptions)
{
- return Resolve(path, false);
+ return Resolve(path, false, namingOptions);
}
///
@@ -48,10 +38,11 @@ namespace Emby.Naming.Video
///
/// The path.
/// if set to true [is folder].
+ /// The naming options.
/// Whether or not the name should be parsed for info.
/// VideoFileInfo.
/// path is null.
- public VideoFileInfo? Resolve(string? path, bool isDirectory, bool parseName = true)
+ public static VideoFileInfo? Resolve(string? path, bool isDirectory, NamingOptions namingOptions, bool parseName = true)
{
if (string.IsNullOrEmpty(path))
{
@@ -59,18 +50,18 @@ namespace Emby.Naming.Video
}
bool isStub = false;
- string? container = null;
+ ReadOnlySpan container = ReadOnlySpan.Empty;
string? stubType = null;
if (!isDirectory)
{
- var extension = Path.GetExtension(path);
+ var extension = Path.GetExtension(path.AsSpan());
// Check supported extensions
- if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+ if (!namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
// It's not supported. Check stub extensions
- if (!StubResolver.TryResolveFile(path, _options, out stubType))
+ if (!StubResolver.TryResolveFile(path, namingOptions, out stubType))
{
return null;
}
@@ -81,25 +72,22 @@ namespace Emby.Naming.Video
container = extension.TrimStart('.');
}
- var flags = new FlagParser(_options).GetFlags(path);
- var format3DResult = new Format3DParser(_options).Parse(flags);
+ var format3DResult = Format3DParser.Parse(path, namingOptions);
- var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
+ var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path);
- var name = isDirectory
- ? Path.GetFileName(path)
- : Path.GetFileNameWithoutExtension(path);
+ var name = Path.GetFileNameWithoutExtension(path);
int? year = null;
if (parseName)
{
- var cleanDateTimeResult = CleanDateTime(name);
+ var cleanDateTimeResult = CleanDateTime(name, namingOptions);
name = cleanDateTimeResult.Name;
year = cleanDateTimeResult.Year;
if (extraResult.ExtraType == null
- && TryCleanString(name, out ReadOnlySpan newName))
+ && TryCleanString(name, namingOptions, out ReadOnlySpan newName))
{
name = newName.ToString();
}
@@ -107,7 +95,7 @@ namespace Emby.Naming.Video
return new VideoFileInfo(
path: path,
- container: container,
+ container: container.IsEmpty ? null : container.ToString(),
isStub: isStub,
name: name,
year: year,
@@ -123,43 +111,47 @@ namespace Emby.Naming.Video
/// Determines if path is video file based on extension.
///
/// Path to file.
+ /// The naming options.
/// True if is video file.
- public bool IsVideoFile(string path)
+ public static bool IsVideoFile(string path, NamingOptions namingOptions)
{
- var extension = Path.GetExtension(path);
- return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+ var extension = Path.GetExtension(path.AsSpan());
+ return namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
///
/// Determines if path is video file stub based on extension.
///
/// Path to file.
+ /// The naming options.
/// True if is video file stub.
- public bool IsStubFile(string path)
+ public static bool IsStubFile(string path, NamingOptions namingOptions)
{
- var extension = Path.GetExtension(path);
- return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+ var extension = Path.GetExtension(path.AsSpan());
+ return namingOptions.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
///
/// Tries to clean name of clutter.
///
/// Raw name.
+ /// The naming options.
/// Clean name.
/// True if cleaning of name was successful.
- public bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan newName)
+ public static bool TryCleanString([NotNullWhen(true)] string? name, NamingOptions namingOptions, out ReadOnlySpan newName)
{
- return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
+ return CleanStringParser.TryClean(name, namingOptions.CleanStringRegexes, out newName);
}
///
/// Tries to get name and year from raw name.
///
/// Raw name.
+ /// The naming options.
/// Returns with name and optional year.
- public CleanDateTimeResult CleanDateTime(string name)
+ public static CleanDateTimeResult CleanDateTime(string name, NamingOptions namingOptions)
{
- return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes);
+ return CleanDateTimeParser.Clean(name, namingOptions.CleanDateTimeRegexes);
}
}
}
diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj
index 5a2aea6423..5edcf2f295 100644
--- a/Emby.Notifications/Emby.Notifications.csproj
+++ b/Emby.Notifications/Emby.Notifications.csproj
@@ -9,10 +9,6 @@
net5.0
false
true
- true
- enable
- AllEnabledByDefault
- ../jellyfin.ruleset
diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs
index 7433d3c8ae..e8ae14ff22 100644
--- a/Emby.Notifications/NotificationEntryPoint.cs
+++ b/Emby.Notifications/NotificationEntryPoint.cs
@@ -77,7 +77,6 @@ namespace Emby.Notifications
{
_libraryManager.ItemAdded += OnLibraryManagerItemAdded;
_appHost.HasPendingRestartChanged += OnAppHostHasPendingRestartChanged;
- _appHost.HasUpdateAvailableChanged += OnAppHostHasUpdateAvailableChanged;
_activityManager.EntryCreated += OnActivityManagerEntryCreated;
return Task.CompletedTask;
@@ -132,25 +131,6 @@ namespace Emby.Notifications
return _config.GetConfiguration("notifications");
}
- private async void OnAppHostHasUpdateAvailableChanged(object? sender, EventArgs e)
- {
- if (!_appHost.HasUpdateAvailable)
- {
- return;
- }
-
- var type = NotificationType.ApplicationUpdateAvailable.ToString();
-
- var notification = new NotificationRequest
- {
- Description = "Please see jellyfin.org for details.",
- NotificationType = type,
- Name = _localization.GetLocalizedString("NewVersionIsAvailable")
- };
-
- await SendNotification(notification, null).ConfigureAwait(false);
- }
-
private void OnLibraryManagerItemAdded(object? sender, ItemChangeEventArgs e)
{
if (!FilterItem(e.Item))
@@ -325,7 +305,6 @@ namespace Emby.Notifications
_libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
_appHost.HasPendingRestartChanged -= OnAppHostHasPendingRestartChanged;
- _appHost.HasUpdateAvailableChanged -= OnAppHostHasUpdateAvailableChanged;
_activityManager.EntryCreated -= OnActivityManagerEntryCreated;
_disposed = true;
diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index 2b66181599..00b2f0f94c 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -22,10 +22,6 @@
net5.0
false
true
- true
- enable
- AllEnabledByDefault
- ../jellyfin.ruleset
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
index 660bbb2deb..6edfad575a 100644
--- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
+++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
@@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.AppBase
CachePath = cacheDirectoryPath;
WebPath = webDirectoryPath;
- DataPath = Path.Combine(ProgramDataPath, "data");
+ _dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName;
}
///
@@ -55,11 +55,7 @@ namespace Emby.Server.Implementations.AppBase
/// Gets the folder path to the data directory.
///
/// The data directory.
- public string DataPath
- {
- get => _dataPath;
- private set => _dataPath = Directory.CreateDirectory(value).FullName;
- }
+ public string DataPath => _dataPath;
///
public string VirtualDataPath => "%AppDataPath%";
diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
index 4f72c8ce15..d385356345 100644
--- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
+++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -23,6 +25,11 @@ namespace Emby.Server.Implementations.AppBase
private readonly ConcurrentDictionary _configurations = new ConcurrentDictionary();
+ ///
+ /// The _configuration sync lock.
+ ///
+ private readonly object _configurationSyncLock = new object();
+
private ConfigurationStore[] _configurationStores = Array.Empty();
private IConfigurationFactory[] _configurationFactories = Array.Empty();
@@ -31,11 +38,6 @@ namespace Emby.Server.Implementations.AppBase
///
private bool _configurationLoaded;
- ///
- /// The _configuration sync lock.
- ///
- private readonly object _configurationSyncLock = new object();
-
///
/// The _configuration.
///
@@ -297,25 +299,29 @@ namespace Emby.Server.Implementations.AppBase
///
public object GetConfiguration(string key)
{
- return _configurations.GetOrAdd(key, k =>
- {
- var file = GetConfigurationFile(key);
-
- var configurationInfo = _configurationStores
- .FirstOrDefault(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase));
-
- if (configurationInfo == null)
+ return _configurations.GetOrAdd(
+ key,
+ (k, configurationManager) =>
{
- throw new ResourceNotFoundException("Configuration with key " + key + " not found.");
- }
+ var file = configurationManager.GetConfigurationFile(k);
- var configurationType = configurationInfo.ConfigurationType;
+ var configurationInfo = Array.Find(
+ configurationManager._configurationStores,
+ i => string.Equals(i.Key, k, StringComparison.OrdinalIgnoreCase));
- lock (_configurationSyncLock)
- {
- return LoadConfiguration(file, configurationType);
- }
- });
+ if (configurationInfo == null)
+ {
+ throw new ResourceNotFoundException("Configuration with key " + k + " not found.");
+ }
+
+ var configurationType = configurationInfo.ConfigurationType;
+
+ lock (configurationManager._configurationSyncLock)
+ {
+ return configurationManager.LoadConfiguration(file, configurationType);
+ }
+ },
+ this);
}
private object LoadConfiguration(string path, Type configurationType)
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
index 29bac66340..0308a68e42 100644
--- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System;
using System.IO;
using System.Linq;
@@ -35,7 +33,8 @@ namespace Emby.Server.Implementations.AppBase
}
catch (Exception)
{
- configuration = Activator.CreateInstance(type) ?? throw new ArgumentException($"Provided path ({type}) is not valid.", nameof(type));
+ // Note: CreateInstance returns null for Nullable, e.g. CreateInstance(typeof(int?)) returns null.
+ configuration = Activator.CreateInstance(type)!;
}
using var stream = new MemoryStream(buffer?.Length ?? 0);
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index ce24e933d7..c244e7cfa4 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -36,7 +38,6 @@ using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Plugins;
using Emby.Server.Implementations.QuickConnect;
using Emby.Server.Implementations.ScheduledTasks;
-using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.SyncPlay;
@@ -57,7 +58,6 @@ using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
@@ -73,7 +73,6 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.QuickConnect;
using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Subtitles;
@@ -101,7 +100,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Prometheus.DotNetRuntime;
-using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager;
namespace Emby.Server.Implementations
@@ -116,6 +114,11 @@ namespace Emby.Server.Implementations
///
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
+ ///
+ /// The disposable parts.
+ ///
+ private readonly List _disposableParts = new List();
+
private readonly IFileSystem _fileSystemManager;
private readonly IConfiguration _startupConfig;
private readonly IXmlSerializer _xmlSerializer;
@@ -127,110 +130,15 @@ namespace Emby.Server.Implementations
private ISessionManager _sessionManager;
private string[] _urlPrefixes;
- ///
- /// Gets a value indicating whether this instance can self restart.
- ///
- public bool CanSelfRestart => _startupOptions.RestartPath != null;
-
- public bool CoreStartupHasCompleted { get; private set; }
-
- public virtual bool CanLaunchWebBrowser
- {
- get
- {
- if (!Environment.UserInteractive)
- {
- return false;
- }
-
- if (_startupOptions.IsService)
- {
- return false;
- }
-
- if (OperatingSystem.Id == OperatingSystemId.Windows
- || OperatingSystem.Id == OperatingSystemId.Darwin)
- {
- return true;
- }
-
- return false;
- }
- }
-
- ///
- /// Gets the singleton instance.
- ///
- public INetworkManager NetManager { get; internal set; }
-
- ///
- /// Occurs when [has pending restart changed].
- ///
- public event EventHandler HasPendingRestartChanged;
-
- ///
- /// Gets a value indicating whether this instance has changes that require the entire application to restart.
- ///
- /// true if this instance has pending application restart; otherwise, false.
- public bool HasPendingRestart { get; private set; }
-
- ///
- public bool IsShuttingDown { get; private set; }
-
- ///
- /// Gets the logger.
- ///
- protected ILogger Logger { get; }
-
- protected IServiceCollection ServiceCollection { get; }
-
- ///
- /// Gets the logger factory.
- ///
- protected ILoggerFactory LoggerFactory { get; }
-
- ///
- /// Gets or sets the application paths.
- ///
- /// The application paths.
- protected IServerApplicationPaths ApplicationPaths { get; set; }
-
///
/// Gets or sets all concrete types.
///
/// All concrete types.
private Type[] _allConcreteTypes;
- ///
- /// The disposable parts.
- ///
- private readonly List _disposableParts = new List();
+ private DeviceId _deviceId;
- ///
- /// Gets or sets the configuration manager.
- ///
- /// The configuration manager.
- public ServerConfigurationManager ConfigurationManager { get; set; }
-
- ///
- /// Gets or sets the service provider.
- ///
- public IServiceProvider ServiceProvider { get; set; }
-
- ///
- /// Gets the http port for the webhost.
- ///
- public int HttpPort { get; private set; }
-
- ///
- /// Gets the https port for the webhost.
- ///
- public int HttpsPort { get; private set; }
-
- ///
- /// Gets the value of the PublishedServerUrl setting.
- ///
- public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
+ private bool _disposed = false;
///
/// Initializes a new instance of the class.
@@ -273,6 +181,143 @@ namespace Emby.Server.Implementations
ApplicationVersion);
}
+ ///
+ /// Occurs when [has pending restart changed].
+ ///
+ public event EventHandler HasPendingRestartChanged;
+
+ ///
+ /// Gets a value indicating whether this instance can self restart.
+ ///
+ public bool CanSelfRestart => _startupOptions.RestartPath != null;
+
+ public bool CoreStartupHasCompleted { get; private set; }
+
+ public virtual bool CanLaunchWebBrowser
+ {
+ get
+ {
+ if (!Environment.UserInteractive)
+ {
+ return false;
+ }
+
+ if (_startupOptions.IsService)
+ {
+ return false;
+ }
+
+ return OperatingSystem.IsWindows() || OperatingSystem.IsMacOS();
+ }
+ }
+
+ ///
+ /// Gets the singleton instance.
+ ///
+ public INetworkManager NetManager { get; internal set; }
+
+ ///
+ /// Gets a value indicating whether this instance has changes that require the entire application to restart.
+ ///
+ /// true if this instance has pending application restart; otherwise, false.
+ public bool HasPendingRestart { get; private set; }
+
+ ///
+ public bool IsShuttingDown { get; private set; }
+
+ ///
+ /// Gets the logger.
+ ///
+ protected ILogger Logger { get; }
+
+ protected IServiceCollection ServiceCollection { get; }
+
+ ///
+ /// Gets the logger factory.
+ ///
+ protected ILoggerFactory LoggerFactory { get; }
+
+ ///
+ /// Gets or sets the application paths.
+ ///
+ /// The application paths.
+ protected IServerApplicationPaths ApplicationPaths { get; set; }
+
+ ///
+ /// Gets or sets the configuration manager.
+ ///
+ /// The configuration manager.
+ public ServerConfigurationManager ConfigurationManager { get; set; }
+
+ ///
+ /// Gets or sets the service provider.
+ ///
+ public IServiceProvider ServiceProvider { get; set; }
+
+ ///
+ /// Gets the http port for the webhost.
+ ///
+ public int HttpPort { get; private set; }
+
+ ///
+ /// Gets the https port for the webhost.
+ ///
+ public int HttpsPort { get; private set; }
+
+ ///
+ /// Gets the value of the PublishedServerUrl setting.
+ ///
+ public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
+
+ ///
+ public Version ApplicationVersion { get; }
+
+ ///
+ public string ApplicationVersionString { get; }
+
+ ///
+ /// Gets the current application user agent.
+ ///
+ /// The application user agent.
+ public string ApplicationUserAgent { get; }
+
+ ///
+ /// Gets the email address for use within a comment section of a user agent field.
+ /// Presently used to provide contact information to MusicBrainz service.
+ ///
+ public string ApplicationUserAgentAddress => "team@jellyfin.org";
+
+ ///
+ /// Gets the current application name.
+ ///
+ /// The application name.
+ public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
+
+ public string SystemId
+ {
+ get
+ {
+ _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
+
+ return _deviceId.Value;
+ }
+ }
+
+ ///
+ public string Name => ApplicationProductName;
+
+ private CertificateInfo CertificateInfo { get; set; }
+
+ public X509Certificate2 Certificate { get; private set; }
+
+ ///
+ public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps;
+
+ public string FriendlyName =>
+ string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName)
+ ? Environment.MachineName
+ : ConfigurationManager.Configuration.ServerName;
+
///
/// Temporary function to migration network settings out of system.xml and into network.xml.
/// TODO: remove at the point when a fixed migration path has been decided upon.
@@ -305,48 +350,6 @@ namespace Emby.Server.Implementations
.Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase);
}
- ///
- public Version ApplicationVersion { get; }
-
- ///
- public string ApplicationVersionString { get; }
-
- ///
- /// Gets the current application user agent.
- ///
- /// The application user agent.
- public string ApplicationUserAgent { get; }
-
- ///
- /// Gets the email address for use within a comment section of a user agent field.
- /// Presently used to provide contact information to MusicBrainz service.
- ///
- public string ApplicationUserAgentAddress => "team@jellyfin.org";
-
- ///
- /// Gets the current application name.
- ///
- /// The application name.
- public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
-
- private DeviceId _deviceId;
-
- public string SystemId
- {
- get
- {
- if (_deviceId == null)
- {
- _deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
- }
-
- return _deviceId.Value;
- }
- }
-
- ///
- public string Name => ApplicationProductName;
-
///
/// Creates an instance of type and resolves all constructor dependencies.
///
@@ -370,10 +373,7 @@ namespace Emby.Server.Implementations
/// System.Object.
protected object CreateInstanceSafe(Type type)
{
- if (_creatingInstances == null)
- {
- _creatingInstances = new List();
- }
+ _creatingInstances ??= new List();
if (_creatingInstances.IndexOf(type) != -1)
{
@@ -467,6 +467,7 @@ namespace Emby.Server.Implementations
///
/// Runs the startup tasks.
///
+ /// The cancellation token.
/// .
public async Task RunStartupTasksAsync(CancellationToken cancellationToken)
{
@@ -480,7 +481,7 @@ namespace Emby.Server.Implementations
_mediaEncoder.SetFFmpegPath();
- Logger.LogInformation("ServerId: {0}", SystemId);
+ Logger.LogInformation("ServerId: {ServerId}", SystemId);
var entryPoints = GetExports();
@@ -605,8 +606,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton();
- ServiceCollection.AddSingleton();
-
ServiceCollection.AddSingleton();
ServiceCollection.AddSingleton();
@@ -628,8 +627,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton();
- ServiceCollection.AddSingleton();
-
ServiceCollection.AddSingleton();
ServiceCollection.AddSingleton();
@@ -665,8 +662,7 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton();
- ServiceCollection.AddSingleton();
- ServiceCollection.AddSingleton();
+ ServiceCollection.AddScoped();
ServiceCollection.AddSingleton();
ServiceCollection.AddSingleton();
@@ -695,8 +691,6 @@ namespace Emby.Server.Implementations
_mediaEncoder = Resolve();
_sessionManager = Resolve();
- ((AuthenticationRepository)Resolve()).Initialize();
-
SetStaticProperties();
var userDataRepo = (SqliteUserDataRepository)Resolve();
@@ -725,7 +719,7 @@ namespace Emby.Server.Implementations
logger.LogInformation("Environment Variables: {EnvVars}", relevantEnvVars);
logger.LogInformation("Arguments: {Args}", commandLineArgs);
- logger.LogInformation("Operating system: {OS}", OperatingSystem.Name);
+ logger.LogInformation("Operating system: {OS}", MediaBrowser.Common.System.OperatingSystem.Name);
logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture);
logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess);
logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive);
@@ -877,10 +871,6 @@ namespace Emby.Server.Implementations
}
}
- private CertificateInfo CertificateInfo { get; set; }
-
- public X509Certificate2 Certificate { get; private set; }
-
private IEnumerable GetUrlPrefixes()
{
var hosts = new[] { "+" };
@@ -1102,16 +1092,14 @@ namespace Emby.Server.Implementations
ItemsByNamePath = ApplicationPaths.InternalMetadataPath,
InternalMetadataPath = ApplicationPaths.InternalMetadataPath,
CachePath = ApplicationPaths.CachePath,
- OperatingSystem = OperatingSystem.Id.ToString(),
- OperatingSystemDisplayName = OperatingSystem.Name,
+ OperatingSystem = MediaBrowser.Common.System.OperatingSystem.Id.ToString(),
+ OperatingSystemDisplayName = MediaBrowser.Common.System.OperatingSystem.Name,
CanSelfRestart = CanSelfRestart,
CanLaunchWebBrowser = CanLaunchWebBrowser,
- HasUpdateAvailable = HasUpdateAvailable,
TranscodingTempPath = ConfigurationManager.GetTranscodePath(),
ServerName = FriendlyName,
LocalAddress = GetSmartApiUrl(source),
SupportsLibraryMonitor = true,
- EncoderLocation = _mediaEncoder.EncoderLocation,
SystemArchitecture = RuntimeInformation.OSArchitecture,
PackageName = _startupOptions.PackageName
};
@@ -1122,25 +1110,22 @@ namespace Emby.Server.Implementations
.Select(i => new WakeOnLanInfo(i))
.ToList();
- public PublicSystemInfo GetPublicSystemInfo(IPAddress source)
+ public PublicSystemInfo GetPublicSystemInfo(IPAddress address)
{
return new PublicSystemInfo
{
Version = ApplicationVersionString,
ProductName = ApplicationProductName,
Id = SystemId,
- OperatingSystem = OperatingSystem.Id.ToString(),
+ OperatingSystem = MediaBrowser.Common.System.OperatingSystem.Id.ToString(),
ServerName = FriendlyName,
- LocalAddress = GetSmartApiUrl(source),
+ LocalAddress = GetSmartApiUrl(address),
StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted
};
}
///
- public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps;
-
- ///
- public string GetSmartApiUrl(IPAddress ipAddress, int? port = null)
+ public string GetSmartApiUrl(IPAddress remoteAddr, int? port = null)
{
// Published server ends with a /
if (!string.IsNullOrEmpty(PublishedServerUrl))
@@ -1149,7 +1134,7 @@ namespace Emby.Server.Implementations
return PublishedServerUrl.Trim('/');
}
- string smart = NetManager.GetBindInterface(ipAddress, out port);
+ string smart = NetManager.GetBindInterface(remoteAddr, out port);
// If the smartAPI doesn't start with http then treat it as a host or ip.
if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
@@ -1211,27 +1196,20 @@ namespace Emby.Server.Implementations
}
///
- public string GetLocalApiUrl(string host, string scheme = null, int? port = null)
+ public string GetLocalApiUrl(string hostname, string scheme = null, int? port = null)
{
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
// not. For consistency, always trim the trailing slash.
return new UriBuilder
{
Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
- Host = host,
+ Host = hostname,
Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl
}.ToString().TrimEnd('/');
}
- public string FriendlyName =>
- string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName)
- ? Environment.MachineName
- : ConfigurationManager.Configuration.ServerName;
-
- ///
- /// Shuts down.
- ///
+ ///
public async Task Shutdown()
{
if (IsShuttingDown)
@@ -1255,26 +1233,6 @@ namespace Emby.Server.Implementations
protected abstract void ShutdownInternal();
- public event EventHandler HasUpdateAvailableChanged;
-
- private bool _hasUpdateAvailable;
-
- public bool HasUpdateAvailable
- {
- get => _hasUpdateAvailable;
- set
- {
- var fireEvent = value && !_hasUpdateAvailable;
-
- _hasUpdateAvailable = value;
-
- if (fireEvent)
- {
- HasUpdateAvailableChanged?.Invoke(this, EventArgs.Empty);
- }
- }
- }
-
public IEnumerable GetApiPluginAssemblies()
{
var assemblies = _allConcreteTypes
@@ -1289,41 +1247,7 @@ namespace Emby.Server.Implementations
}
}
- public virtual void LaunchUrl(string url)
- {
- if (!CanLaunchWebBrowser)
- {
- throw new NotSupportedException();
- }
-
- var process = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = url,
- UseShellExecute = true,
- ErrorDialog = false
- },
- EnableRaisingEvents = true
- };
- process.Exited += (sender, args) => ((Process)sender).Dispose();
-
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error launching url: {url}", url);
- throw;
- }
- }
-
- private bool _disposed = false;
-
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
+ ///
public void Dispose()
{
Dispose(true);
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 7324b0ee9f..41d1f9b392 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -9,7 +11,7 @@ using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -100,7 +102,7 @@ namespace Emby.Server.Implementations.Channels
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
- return !(channel is IDisableMediaSourceDisplay);
+ return channel is not IDisableMediaSourceDisplay;
}
///
@@ -878,7 +880,7 @@ namespace Emby.Server.Implementations.Channels
}
}
- private async Task CacheResponse(object result, string path)
+ private async Task CacheResponse(ChannelItemResult result, string path)
{
try
{
@@ -1077,11 +1079,11 @@ namespace Emby.Server.Implementations.Channels
// was used for status
// if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
- //{
+ // {
// item.ExternalEtag = info.Etag;
// forceUpdate = true;
// _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name);
- //}
+ // }
if (!internalChannelId.Equals(item.ChannelId))
{
diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
index c69a07e83e..ca84094024 100644
--- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
+++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
@@ -82,9 +82,9 @@ namespace Emby.Server.Implementations.Collections
return null;
})
.Where(i => i != null)
- .GroupBy(x => x.Id)
+ .GroupBy(x => x!.Id) // We removed the null values
.Select(x => x.First())
- .ToList();
+ .ToList()!; // Again... the list doesn't contain any null values
}
///
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 1b85a9d4ba..79ef70fffd 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -61,13 +61,13 @@ namespace Emby.Server.Implementations.Collections
}
///
- public event EventHandler CollectionCreated;
+ public event EventHandler? CollectionCreated;
///
- public event EventHandler ItemsAddedToCollection;
+ public event EventHandler? ItemsAddedToCollection;
///
- public event EventHandler ItemsRemovedFromCollection;
+ public event EventHandler? ItemsRemovedFromCollection;
private IEnumerable FindFolders(string path)
{
@@ -78,14 +78,12 @@ namespace Emby.Server.Implementations.Collections
.Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path));
}
- internal async Task EnsureLibraryFolder(string path, bool createIfNeeded)
+ internal async Task EnsureLibraryFolder(string path, bool createIfNeeded)
{
- var existingFolders = FindFolders(path)
- .ToList();
-
- if (existingFolders.Count > 0)
+ var existingFolder = FindFolders(path).FirstOrDefault();
+ if (existingFolder != null)
{
- return existingFolders[0];
+ return existingFolder;
}
if (!createIfNeeded)
@@ -97,7 +95,7 @@ namespace Emby.Server.Implementations.Collections
var libraryOptions = new LibraryOptions
{
- PathInfos = new[] { new MediaPathInfo { Path = path } },
+ PathInfos = new[] { new MediaPathInfo(path) },
EnableRealtimeMonitor = false,
SaveLocalMetadata = true
};
@@ -114,7 +112,7 @@ namespace Emby.Server.Implementations.Collections
return Path.Combine(_appPaths.DataPath, "collections");
}
- private Task GetCollectionsFolder(bool createIfNeeded)
+ private Task GetCollectionsFolder(bool createIfNeeded)
{
return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
}
@@ -162,9 +160,9 @@ namespace Emby.Server.Implementations.Collections
DateCreated = DateTime.UtcNow
};
- parentFolder.AddChild(collection, CancellationToken.None);
+ parentFolder.AddChild(collection);
- if (options.ItemIdList.Length > 0)
+ if (options.ItemIdList.Count > 0)
{
await AddToCollectionAsync(
collection.Id,
@@ -198,13 +196,12 @@ namespace Emby.Server.Implementations.Collections
}
///
- public Task AddToCollectionAsync(Guid collectionId, IEnumerable ids)
- => AddToCollectionAsync(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
+ public Task AddToCollectionAsync(Guid collectionId, IEnumerable itemIds)
+ => AddToCollectionAsync(collectionId, itemIds, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
private async Task AddToCollectionAsync(Guid collectionId, IEnumerable ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
{
- var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
- if (collection == null)
+ if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
{
throw new ArgumentException("No collection exists with the supplied Id");
}
@@ -248,11 +245,7 @@ namespace Emby.Server.Implementations.Collections
if (fireEvent)
{
- ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs
- {
- Collection = collection,
- ItemsChanged = itemList
- });
+ ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
}
}
}
@@ -260,9 +253,7 @@ namespace Emby.Server.Implementations.Collections
///
public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable itemIds)
{
- var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
-
- if (collection == null)
+ if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
{
throw new ArgumentException("No collection exists with the supplied Id");
}
@@ -304,11 +295,7 @@ namespace Emby.Server.Implementations.Collections
},
RefreshPriority.High);
- ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
- {
- Collection = collection,
- ItemsChanged = itemList
- });
+ ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
}
///
@@ -320,11 +307,7 @@ namespace Emby.Server.Implementations.Collections
foreach (var item in items)
{
- if (item is not ISupportsBoxSetGrouping)
- {
- results[item.Id] = item;
- }
- else
+ if (item is ISupportsBoxSetGrouping)
{
var itemId = item.Id;
@@ -348,6 +331,7 @@ namespace Emby.Server.Implementations.Collections
}
var alreadyInResults = false;
+
// this is kind of a performance hack because only Video has alternate versions that should be in a box set?
if (item is Video video)
{
@@ -363,11 +347,13 @@ namespace Emby.Server.Implementations.Collections
}
}
- if (!alreadyInResults)
+ if (alreadyInResults)
{
- results[itemId] = item;
+ continue;
}
}
+
+ results[item.Id] = item;
}
return results.Values;
diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
index 7a8ed8c29f..ff5602f243 100644
--- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.Globalization;
using System.IO;
diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
index 12a9e44e70..4a9b280852 100644
--- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
+++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index 8c756a7f4c..01c9fbca81 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -59,7 +61,7 @@ namespace Emby.Server.Implementations.Data
protected virtual int? CacheSize => null;
///
- /// Gets the journal mode.
+ /// Gets the journal mode. .
///
/// The journal mode.
protected virtual string JournalMode => "TRUNCATE";
@@ -181,11 +183,9 @@ namespace Emby.Server.Implementations.Data
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(name);
+ columnNames.Add(columnName);
}
}
diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs
index 5c094ddd2d..afc8966f9c 100644
--- a/Emby.Server.Implementations/Data/ManagedConnection.cs
+++ b/Emby.Server.Implementations/Data/ManagedConnection.cs
@@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Data
{
public class ManagedConnection : IDisposable
{
- private SQLiteDatabaseConnection _db;
+ private SQLiteDatabaseConnection? _db;
private readonly SemaphoreSlim _writeLock;
private bool _disposed = false;
@@ -54,12 +54,12 @@ namespace Emby.Server.Implementations.Data
return _db.RunInTransaction(action, mode);
}
- public IEnumerable> Query(string sql)
+ public IEnumerable> Query(string sql)
{
return _db.Query(sql);
}
- public IEnumerable> Query(string sql, params object[] values)
+ public IEnumerable> Query(string sql, params object[] values)
{
return _db.Query(sql, values);
}
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index a04d630887..3289e76098 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -1,3 +1,4 @@
+#nullable disable
#pragma warning disable CS1591
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());
}
@@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Data
private static string GetDateTimeKindFormat(DateTimeKind kind)
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
- public static DateTime ReadDateTime(this IResultSetValue result)
+ public static DateTime ReadDateTime(this ResultSetValue result)
{
var dateText = result.ToString();
@@ -96,49 +97,139 @@ namespace Emby.Server.Implementations.Data
DateTimeStyles.None).ToUniversalTime();
}
- public static DateTime? TryReadDateTime(this IResultSetValue result)
+ public static bool TryReadDateTime(this IReadOnlyList 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))
{
- return dateTimeResult.ToUniversalTime();
+ result = dateTimeResult.ToUniversalTime();
+ return true;
}
- return null;
+ result = default;
+ return false;
}
- public static bool IsDBNull(this IReadOnlyList result, int index)
+ public static bool TryGetGuid(this IReadOnlyList reader, int index, out Guid result)
{
- return result[index].SQLiteType == SQLiteType.Null;
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ result = item.ReadGuidFromBlob();
+ return true;
}
- public static string GetString(this IReadOnlyList result, int index)
+ public static bool IsDbNull(this ResultSetValue result)
+ {
+ return result.SQLiteType == SQLiteType.Null;
+ }
+
+ public static string GetString(this IReadOnlyList result, int index)
{
return result[index].ToString();
}
- public static bool GetBoolean(this IReadOnlyList result, int index)
+ public static bool TryGetString(this IReadOnlyList 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 result, int index)
{
return result[index].ToBool();
}
- public static int GetInt32(this IReadOnlyList result, int index)
+ public static bool TryGetBoolean(this IReadOnlyList reader, int index, out bool result)
{
- return result[index].ToInt();
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ result = item.ToBool();
+ return true;
}
- public static long GetInt64(this IReadOnlyList result, int index)
+ public static bool TryGetInt32(this IReadOnlyList 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 result, int index)
{
return result[index].ToInt64();
}
- public static float GetFloat(this IReadOnlyList result, int index)
+ public static bool TryGetInt64(this IReadOnlyList reader, int index, out long result)
{
- return result[index].ToFloat();
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ result = item.ToInt64();
+ return true;
}
- public static Guid GetGuid(this IReadOnlyList result, int index)
+ public static bool TryGetSingle(this IReadOnlyList 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 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 result, int index)
{
return result[index].ReadGuidFromBlob();
}
@@ -350,7 +441,7 @@ namespace Emby.Server.Implementations.Data
}
}
- public static IEnumerable> ExecuteQuery(this IStatement statement)
+ public static IEnumerable> ExecuteQuery(this IStatement statement)
{
while (statement.MoveNext())
{
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 694805ebe9..30f88c7964 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -1,6 +1,9 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
+using System.Buffers.Text;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -8,10 +11,12 @@ using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
+using Diacritics.Extensions;
using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -40,6 +45,7 @@ namespace Emby.Server.Implementations.Data
///
public class SqliteItemRepository : BaseSqliteRepository, IItemRepository
{
+ private const string FromText = " from TypedBaseItems A";
private const string ChaptersTableName = "Chapters2";
private readonly IServerConfigurationManager _config;
@@ -69,6 +75,12 @@ namespace Emby.Server.Implementations.Data
///
/// Initializes a new instance of the class.
///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// config is null.
public SqliteItemRepository(
IServerConfigurationManager config,
IServerApplicationHost appHost,
@@ -502,7 +514,7 @@ namespace Emby.Server.Implementations.Data
using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
{
saveImagesStatement.TryBind("@Id", item.Id.ToByteArray());
- saveImagesStatement.TryBind("@Images", SerializeImages(item));
+ saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
saveImagesStatement.MoveNext();
}
@@ -897,8 +909,8 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId);
saveItemStatement.TryBind("@Tagline", item.Tagline);
- saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item));
- saveItemStatement.TryBind("@Images", SerializeImages(item));
+ saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item.ProviderIds));
+ saveItemStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
if (item.ProductionLocations.Length > 0)
{
@@ -968,10 +980,10 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.MoveNext();
}
- private static string SerializeProviderIds(BaseItem item)
+ internal static string SerializeProviderIds(Dictionary providerIds)
{
StringBuilder str = new StringBuilder();
- foreach (var i in item.ProviderIds)
+ foreach (var i in providerIds)
{
// Ideally we shouldn't need this IsNullOrWhiteSpace check,
// but we're seeing some cases of bad data slip through
@@ -995,35 +1007,25 @@ namespace Emby.Server.Implementations.Data
return str.ToString();
}
- private static void DeserializeProviderIds(string value, BaseItem item)
+ internal static void DeserializeProviderIds(string value, IHasProviderIds item)
{
if (string.IsNullOrWhiteSpace(value))
{
return;
}
- if (item.ProviderIds.Count > 0)
+ foreach (var part in value.SpanSplit('|'))
{
- return;
- }
-
- var parts = value.Split('|', StringSplitOptions.RemoveEmptyEntries);
-
- foreach (var part in parts)
- {
- var idParts = part.Split('=');
-
- if (idParts.Length == 2)
+ var providerDelimiterIndex = part.IndexOf('=');
+ if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('='))
{
- item.SetProviderId(idParts[0], idParts[1]);
+ item.SetProviderId(part.Slice(0, providerDelimiterIndex).ToString(), part.Slice(providerDelimiterIndex + 1).ToString());
}
}
}
- private string SerializeImages(BaseItem item)
+ internal string SerializeImages(ItemImageInfo[] images)
{
- var images = item.ImageInfos;
-
if (images.Length == 0)
{
return null;
@@ -1045,39 +1047,48 @@ namespace Emby.Server.Implementations.Data
return str.ToString();
}
- private void DeserializeImages(string value, BaseItem item)
+ internal ItemImageInfo[] DeserializeImages(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
- return;
+ return Array.Empty();
}
- if (item.ImageInfos.Length > 0)
- {
- return;
- }
+ // TODO The following is an ugly performance optimization, but it's extremely unlikely that the data in the database would be malformed
+ var valueSpan = value.AsSpan();
+ var count = valueSpan.Count('|') + 1;
- var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries);
- var list = new List();
- foreach (var part in parts)
+ var position = 0;
+ var result = new ItemImageInfo[count];
+ foreach (var part in valueSpan.Split('|'))
{
var image = ItemImageInfoFromValueString(part);
if (image != null)
{
- list.Add(image);
+ result[position++] = image;
}
}
- item.ImageInfos = list.ToArray();
+ if (position == count)
+ {
+ return result;
+ }
+
+ if (position == 0)
+ {
+ return Array.Empty();
+ }
+
+ // Extremely unlikely, but somehow one or more of the image strings were malformed. Cut the array.
+ return result[..position];
}
- public void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
+ private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
{
const char Delimiter = '*';
var path = image.Path ?? string.Empty;
- var hash = image.BlurHash ?? string.Empty;
bldr.Append(GetPathToSave(path))
.Append(Delimiter)
@@ -1087,48 +1098,115 @@ namespace Emby.Server.Implementations.Data
.Append(Delimiter)
.Append(image.Width)
.Append(Delimiter)
- .Append(image.Height)
- .Append(Delimiter)
- // Replace delimiters with other characters.
- // This can be removed when we migrate to a proper DB.
- .Append(hash.Replace('*', '/').Replace('|', '\\'));
+ .Append(image.Height);
+
+ var hash = image.BlurHash;
+ if (!string.IsNullOrEmpty(hash))
+ {
+ bldr.Append(Delimiter)
+ // Replace delimiters with other characters.
+ // This can be removed when we migrate to a proper DB.
+ .Append(hash.Replace('*', '/').Replace('|', '\\'));
+ }
}
- public ItemImageInfo ItemImageInfoFromValueString(string value)
+ internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan value)
{
- var parts = value.Split('*', StringSplitOptions.None);
-
- if (parts.Length < 3)
+ var nextSegment = value.IndexOf('*');
+ if (nextSegment == -1)
{
return null;
}
- var image = new ItemImageInfo();
+ ReadOnlySpan path = value[..nextSegment];
+ value = value[(nextSegment + 1)..];
+ nextSegment = value.IndexOf('*');
+ if (nextSegment == -1)
+ {
+ return null;
+ }
- image.Path = RestorePath(parts[0]);
+ ReadOnlySpan dateModified = value[..nextSegment];
+ value = value[(nextSegment + 1)..];
+ nextSegment = value.IndexOf('*');
+ if (nextSegment == -1)
+ {
+ nextSegment = value.Length;
+ }
- if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
+ ReadOnlySpan imageType = value[..nextSegment];
+
+ var image = new ItemImageInfo
+ {
+ Path = RestorePath(path.ToString())
+ };
+
+ if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)
+ && ticks >= DateTime.MinValue.Ticks
+ && ticks <= DateTime.MaxValue.Ticks)
{
image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
}
+ else
+ {
+ return null;
+ }
- if (Enum.TryParse(parts[2], true, out ImageType type))
+ if (Enum.TryParse(imageType.ToString(), true, out ImageType type))
{
image.Type = type;
}
-
- if (parts.Length >= 5)
+ else
{
- if (int.TryParse(parts[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var width)
- && int.TryParse(parts[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
+ return null;
+ }
+
+ // Optional parameters: width*height*blurhash
+ if (nextSegment + 1 < value.Length - 1)
+ {
+ value = value[(nextSegment + 1)..];
+ nextSegment = value.IndexOf('*');
+ if (nextSegment == -1 || nextSegment == value.Length)
+ {
+ return image;
+ }
+
+ ReadOnlySpan widthSpan = value[..nextSegment];
+
+ value = value[(nextSegment + 1)..];
+ nextSegment = value.IndexOf('*');
+ if (nextSegment == -1)
+ {
+ nextSegment = value.Length;
+ }
+
+ ReadOnlySpan heightSpan = value[..nextSegment];
+
+ if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width)
+ && int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
{
image.Width = width;
image.Height = height;
}
- if (parts.Length >= 6)
+ if (nextSegment < value.Length - 1)
{
- image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|');
+ value = value[(nextSegment + 1)..];
+ var length = value.Length;
+
+ Span blurHashSpan = stackalloc char[length];
+ for (int i = 0; i < length; i++)
+ {
+ var c = value[i];
+ blurHashSpan[i] = c switch
+ {
+ '/' => '*',
+ '\\' => '|',
+ _ => c
+ };
+ }
+
+ image.BlurHash = new string(blurHashSpan);
}
}
@@ -1241,12 +1319,12 @@ namespace Emby.Server.Implementations.Data
return true;
}
- private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query)
+ private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query)
{
return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query));
}
- private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields)
+ private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields)
{
var typeString = reader.GetString(0);
@@ -1291,27 +1369,30 @@ namespace Emby.Server.Implementations.Data
if (queryHasStartDate)
{
- if (!reader.IsDBNull(index))
+ if (item is IHasStartDate hasStartDate && reader.TryReadDateTime(index, out var startDate))
{
- if (item is IHasStartDate hasStartDate)
- {
- hasStartDate.StartDate = reader[index].ReadDateTime();
- }
+ hasStartDate.StartDate = startDate;
}
index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryReadDateTime(index++, out var endDate))
{
- item.EndDate = reader[index].TryReadDateTime();
+ item.EndDate = endDate;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ var channelId = reader[index];
+ if (!channelId.IsDbNull())
{
- item.ChannelId = new Guid(reader.GetString(index));
+ if (!Utf8Parser.TryParse(channelId.ToBlob(), out Guid value, out _, standardFormat: 'N'))
+ {
+ var str = reader.GetString(index);
+ Logger.LogWarning("{ChannelId} isn't in the expected format", str);
+ value = new Guid(str);
+ }
+
+ item.ChannelId = value;
}
index++;
@@ -1320,33 +1401,25 @@ namespace Emby.Server.Implementations.Data
{
if (item is IHasProgramAttributes hasProgramAttributes)
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetBoolean(index++, out var isMovie))
{
- hasProgramAttributes.IsMovie = reader.GetBoolean(index);
+ hasProgramAttributes.IsMovie = isMovie;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetBoolean(index++, out var isSeries))
{
- hasProgramAttributes.IsSeries = reader.GetBoolean(index);
+ hasProgramAttributes.IsSeries = isSeries;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var episodeTitle))
{
- hasProgramAttributes.EpisodeTitle = reader.GetString(index);
+ hasProgramAttributes.EpisodeTitle = episodeTitle;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetBoolean(index++, out var isRepeat))
{
- hasProgramAttributes.IsRepeat = reader.GetBoolean(index);
+ hasProgramAttributes.IsRepeat = isRepeat;
}
-
- index++;
}
else
{
@@ -1354,242 +1427,190 @@ namespace Emby.Server.Implementations.Data
}
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetSingle(index++, out var communityRating))
{
- item.CommunityRating = reader.GetFloat(index);
+ item.CommunityRating = communityRating;
}
- index++;
-
if (HasField(query, ItemFields.CustomRating))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var customRating))
{
- item.CustomRating = reader.GetString(index);
+ item.CustomRating = customRating;
}
-
- index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetInt32(index++, out var indexNumber))
{
- item.IndexNumber = reader.GetInt32(index);
+ item.IndexNumber = indexNumber;
}
- index++;
-
if (HasField(query, ItemFields.Settings))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetBoolean(index++, out var isLocked))
{
- item.IsLocked = reader.GetBoolean(index);
+ item.IsLocked = isLocked;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var preferredMetadataLanguage))
{
- item.PreferredMetadataLanguage = reader.GetString(index);
+ item.PreferredMetadataLanguage = preferredMetadataLanguage;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var preferredMetadataCountryCode))
{
- item.PreferredMetadataCountryCode = reader.GetString(index);
+ item.PreferredMetadataCountryCode = preferredMetadataCountryCode;
}
-
- index++;
}
if (HasField(query, ItemFields.Width))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetInt32(index++, out var width))
{
- item.Width = reader.GetInt32(index);
+ item.Width = width;
}
-
- index++;
}
if (HasField(query, ItemFields.Height))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetInt32(index++, out var height))
{
- item.Height = reader.GetInt32(index);
+ item.Height = height;
}
-
- index++;
}
if (HasField(query, ItemFields.DateLastRefreshed))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryReadDateTime(index++, out var dateLastRefreshed))
{
- item.DateLastRefreshed = reader[index].ReadDateTime();
+ item.DateLastRefreshed = dateLastRefreshed;
}
-
- index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var name))
{
- item.Name = reader.GetString(index);
+ item.Name = name;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var restorePath))
{
- item.Path = RestorePath(reader.GetString(index));
+ item.Path = RestorePath(restorePath);
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryReadDateTime(index++, out var premiereDate))
{
- item.PremiereDate = reader[index].TryReadDateTime();
+ item.PremiereDate = premiereDate;
}
- index++;
-
if (HasField(query, ItemFields.Overview))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var overview))
{
- item.Overview = reader.GetString(index);
+ item.Overview = overview;
}
-
- index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetInt32(index++, out var parentIndexNumber))
{
- item.ParentIndexNumber = reader.GetInt32(index);
+ item.ParentIndexNumber = parentIndexNumber;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetInt32(index++, out var productionYear))
{
- item.ProductionYear = reader.GetInt32(index);
+ item.ProductionYear = productionYear;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var officialRating))
{
- item.OfficialRating = reader.GetString(index);
+ item.OfficialRating = officialRating;
}
- index++;
-
if (HasField(query, ItemFields.SortName))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var forcedSortName))
{
- item.ForcedSortName = reader.GetString(index);
+ item.ForcedSortName = forcedSortName;
}
-
- index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetInt64(index++, out var runTimeTicks))
{
- item.RunTimeTicks = reader.GetInt64(index);
+ item.RunTimeTicks = runTimeTicks;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetInt64(index++, out var size))
{
- item.Size = reader.GetInt64(index);
+ item.Size = size;
}
- index++;
-
if (HasField(query, ItemFields.DateCreated))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryReadDateTime(index++, out var dateCreated))
{
- item.DateCreated = reader[index].ReadDateTime();
+ item.DateCreated = dateCreated;
}
-
- index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryReadDateTime(index++, out var dateModified))
{
- item.DateModified = reader[index].ReadDateTime();
+ item.DateModified = dateModified;
}
- index++;
-
- item.Id = reader.GetGuid(index);
- index++;
+ item.Id = reader.GetGuid(index++);
if (HasField(query, ItemFields.Genres))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var genres))
{
- item.Genres = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
+ item.Genres = genres.Split('|', StringSplitOptions.RemoveEmptyEntries);
}
-
- index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetGuid(index++, out var parentId))
{
- item.ParentId = reader.GetGuid(index);
+ item.ParentId = parentId;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var audioString))
{
- if (Enum.TryParse(reader.GetString(index), true, out ProgramAudio audio))
+ // TODO Span overload coming in the future https://github.com/dotnet/runtime/issues/1916
+ if (Enum.TryParse(audioString, true, out ProgramAudio audio))
{
item.Audio = audio;
}
}
- index++;
-
// TODO: Even if not needed by apps, the server needs it internally
// But get this excluded from contexts where it is not needed
if (hasServiceName)
{
if (item is LiveTvChannel liveTvChannel)
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index, out var serviceName))
{
- liveTvChannel.ServiceName = reader.GetString(index);
+ liveTvChannel.ServiceName = serviceName;
}
}
index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetBoolean(index++, out var isInMixedFolder))
{
- item.IsInMixedFolder = reader.GetBoolean(index);
+ item.IsInMixedFolder = isInMixedFolder;
}
- index++;
-
if (HasField(query, ItemFields.DateLastSaved))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryReadDateTime(index++, out var dateLastSaved))
{
- item.DateLastSaved = reader[index].ReadDateTime();
+ item.DateLastSaved = dateLastSaved;
}
-
- index++;
}
if (HasField(query, ItemFields.Settings))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var lockedFields))
{
IEnumerable GetLockedFields(string s)
{
@@ -1602,37 +1623,31 @@ namespace Emby.Server.Implementations.Data
}
}
- item.LockedFields = GetLockedFields(reader.GetString(index)).ToArray();
+ item.LockedFields = GetLockedFields(lockedFields).ToArray();
}
-
- index++;
}
if (HasField(query, ItemFields.Studios))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var studios))
{
- item.Studios = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
+ item.Studios = studios.Split('|', StringSplitOptions.RemoveEmptyEntries);
}
-
- index++;
}
if (HasField(query, ItemFields.Tags))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var tags))
{
- item.Tags = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
+ item.Tags = tags.Split('|', StringSplitOptions.RemoveEmptyEntries);
}
-
- index++;
}
if (hasTrailerTypes)
{
if (item is Trailer trailer)
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index, out var trailerTypes))
{
IEnumerable GetTrailerTypes(string s)
{
@@ -1645,7 +1660,7 @@ namespace Emby.Server.Implementations.Data
}
}
- trailer.TrailerTypes = GetTrailerTypes(reader.GetString(index)).ToArray();
+ trailer.TrailerTypes = GetTrailerTypes(trailerTypes).ToArray();
}
}
@@ -1654,19 +1669,17 @@ namespace Emby.Server.Implementations.Data
if (HasField(query, ItemFields.OriginalTitle))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var originalTitle))
{
- item.OriginalTitle = reader.GetString(index);
+ item.OriginalTitle = originalTitle;
}
-
- index++;
}
if (item is Video video)
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index, out var primaryVersionId))
{
- video.PrimaryVersionId = reader.GetString(index);
+ video.PrimaryVersionId = primaryVersionId;
}
}
@@ -1674,40 +1687,34 @@ namespace Emby.Server.Implementations.Data
if (HasField(query, ItemFields.DateLastMediaAdded))
{
- if (item is Folder folder && !reader.IsDBNull(index))
+ if (item is Folder folder && reader.TryReadDateTime(index, out var dateLastMediaAdded))
{
- folder.DateLastMediaAdded = reader[index].TryReadDateTime();
+ folder.DateLastMediaAdded = dateLastMediaAdded;
}
index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var album))
{
- item.Album = reader.GetString(index);
+ item.Album = album;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetSingle(index++, out var criticRating))
{
- item.CriticRating = reader.GetFloat(index);
+ item.CriticRating = criticRating;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetBoolean(index++, out var isVirtualItem))
{
- item.IsVirtualItem = reader.GetBoolean(index);
+ item.IsVirtualItem = isVirtualItem;
}
- index++;
-
if (item is IHasSeries hasSeriesName)
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index, out var seriesName))
{
- hasSeriesName.SeriesName = reader.GetString(index);
+ hasSeriesName.SeriesName = seriesName;
}
}
@@ -1717,15 +1724,15 @@ namespace Emby.Server.Implementations.Data
{
if (item is Episode episode)
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index, out var seasonName))
{
- episode.SeasonName = reader.GetString(index);
+ episode.SeasonName = seasonName;
}
index++;
- if (!reader.IsDBNull(index))
+ if (reader.TryGetGuid(index, out var seasonId))
{
- episode.SeasonId = reader.GetGuid(index);
+ episode.SeasonId = seasonId;
}
}
else
@@ -1741,9 +1748,9 @@ namespace Emby.Server.Implementations.Data
{
if (hasSeries != null)
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetGuid(index, out var seriesId))
{
- hasSeries.SeriesId = reader.GetGuid(index);
+ hasSeries.SeriesId = seriesId;
}
}
@@ -1752,56 +1759,48 @@ namespace Emby.Server.Implementations.Data
if (HasField(query, ItemFields.PresentationUniqueKey))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var presentationUniqueKey))
{
- item.PresentationUniqueKey = reader.GetString(index);
+ item.PresentationUniqueKey = presentationUniqueKey;
}
-
- index++;
}
if (HasField(query, ItemFields.InheritedParentalRatingValue))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetInt32(index++, out var parentalRating))
{
- item.InheritedParentalRatingValue = reader.GetInt32(index);
+ item.InheritedParentalRatingValue = parentalRating;
}
-
- index++;
}
if (HasField(query, ItemFields.ExternalSeriesId))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var externalSeriesId))
{
- item.ExternalSeriesId = reader.GetString(index);
+ item.ExternalSeriesId = externalSeriesId;
}
-
- index++;
}
if (HasField(query, ItemFields.Taglines))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var tagLine))
{
- item.Tagline = reader.GetString(index);
+ item.Tagline = tagLine;
}
-
- index++;
}
- if (!reader.IsDBNull(index))
+ if (item.ProviderIds.Count == 0 && reader.TryGetString(index, out var providerIds))
{
- DeserializeProviderIds(reader.GetString(index), item);
+ DeserializeProviderIds(providerIds, item);
}
index++;
if (query.DtoOptions.EnableImages)
{
- if (!reader.IsDBNull(index))
+ if (item.ImageInfos.Length == 0 && reader.TryGetString(index, out var imageInfos))
{
- DeserializeImages(reader.GetString(index), item);
+ item.ImageInfos = DeserializeImages(imageInfos);
}
index++;
@@ -1809,72 +1808,62 @@ namespace Emby.Server.Implementations.Data
if (HasField(query, ItemFields.ProductionLocations))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var productionLocations))
{
- item.ProductionLocations = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries).ToArray();
+ item.ProductionLocations = productionLocations.Split('|', StringSplitOptions.RemoveEmptyEntries);
}
-
- index++;
}
if (HasField(query, ItemFields.ExtraIds))
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var extraIds))
{
- item.ExtraIds = SplitToGuids(reader.GetString(index));
+ item.ExtraIds = SplitToGuids(extraIds);
}
-
- index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetInt32(index++, out var totalBitrate))
{
- item.TotalBitrate = reader.GetInt32(index);
+ item.TotalBitrate = totalBitrate;
}
- index++;
-
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var extraTypeString))
{
- if (Enum.TryParse(reader.GetString(index), true, out ExtraType extraType))
+ if (Enum.TryParse(extraTypeString, true, out ExtraType extraType))
{
item.ExtraType = extraType;
}
}
- index++;
-
if (hasArtistFields)
{
- if (item is IHasArtist hasArtists && !reader.IsDBNull(index))
+ if (item is IHasArtist hasArtists && reader.TryGetString(index, out var artists))
{
- hasArtists.Artists = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
+ hasArtists.Artists = artists.Split('|', StringSplitOptions.RemoveEmptyEntries);
}
index++;
- if (item is IHasAlbumArtist hasAlbumArtists && !reader.IsDBNull(index))
+ if (item is IHasAlbumArtist hasAlbumArtists && reader.TryGetString(index, out var albumArtists))
{
- hasAlbumArtists.AlbumArtists = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
+ hasAlbumArtists.AlbumArtists = albumArtists.Split('|', StringSplitOptions.RemoveEmptyEntries);
}
index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index++, out var externalId))
{
- item.ExternalId = reader.GetString(index);
+ item.ExternalId = externalId;
}
- index++;
-
if (HasField(query, ItemFields.SeriesPresentationUniqueKey))
{
if (hasSeries != null)
{
- if (!reader.IsDBNull(index))
+ if (reader.TryGetString(index, out var seriesPresentationUniqueKey))
{
- hasSeries.SeriesPresentationUniqueKey = reader.GetString(index);
+ hasSeries.SeriesPresentationUniqueKey = seriesPresentationUniqueKey;
}
}
@@ -1883,21 +1872,19 @@ namespace Emby.Server.Implementations.Data
if (enableProgramAttributes)
{
- if (item is LiveTvProgram program && !reader.IsDBNull(index))
+ if (item is LiveTvProgram program && reader.TryGetString(index, out var showId))
{
- program.ShowId = reader.GetString(index);
+ program.ShowId = showId;
}
index++;
}
- if (!reader.IsDBNull(index))
+ if (reader.TryGetGuid(index, out var ownerId))
{
- item.OwnerId = reader.GetGuid(index);
+ item.OwnerId = ownerId;
}
- index++;
-
return item;
}
@@ -1915,12 +1902,7 @@ namespace Emby.Server.Implementations.Data
return result;
}
- ///
- /// Gets chapters for an item.
- ///
- /// The item.
- /// IEnumerable{ChapterInfo}.
- /// id
+ ///
public List GetChapters(BaseItem item)
{
CheckDisposed();
@@ -1943,13 +1925,7 @@ namespace Emby.Server.Implementations.Data
}
}
- ///
- /// Gets a single chapter for an item.
- ///
- /// The item.
- /// The index.
- /// ChapterInfo.
- /// id
+ ///
public ChapterInfo GetChapter(BaseItem item, int index)
{
CheckDisposed();
@@ -1977,21 +1953,21 @@ namespace Emby.Server.Implementations.Data
/// The reader.
/// The item.
/// ChapterInfo.
- private ChapterInfo GetChapter(IReadOnlyList reader, BaseItem item)
+ private ChapterInfo GetChapter(IReadOnlyList reader, BaseItem item)
{
var chapter = new ChapterInfo
{
StartPositionTicks = reader.GetInt64(0)
};
- if (!reader.IsDBNull(1))
+ if (reader.TryGetString(1, out var chapterName))
{
- chapter.Name = reader.GetString(1);
+ chapter.Name = chapterName;
}
- if (!reader.IsDBNull(2))
+ if (reader.TryGetString(2, out var imagePath))
{
- chapter.ImagePath = reader.GetString(2);
+ chapter.ImagePath = imagePath;
if (!string.IsNullOrEmpty(chapter.ImagePath))
{
@@ -2006,9 +1982,9 @@ namespace Emby.Server.Implementations.Data
}
}
- if (!reader.IsDBNull(3))
+ if (reader.TryReadDateTime(3, out var imageDateModified))
{
- chapter.ImageDateModified = reader[3].ReadDateTime();
+ chapter.ImageDateModified = imageDateModified;
}
return chapter;
@@ -2061,7 +2037,7 @@ namespace Emby.Server.Implementations.Data
for (var i = startIndex; i < endIndex; i++)
{
- insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
+ insertText.AppendFormat(CultureInfo.InvariantCulture, "(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
}
insertText.Length -= 1; // Remove last ,
@@ -2116,30 +2092,7 @@ namespace Emby.Server.Implementations.Data
|| query.IsLiked.HasValue;
}
- private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToArray();
-
- private string[] GetColumnNamesFromField(ItemFields field)
- {
- switch (field)
- {
- case ItemFields.Settings:
- return new[] { "IsLocked", "PreferredMetadataCountryCode", "PreferredMetadataLanguage", "LockedFields" };
- case ItemFields.ServiceName:
- return new[] { "ExternalServiceId" };
- case ItemFields.SortName:
- return new[] { "ForcedSortName" };
- case ItemFields.Taglines:
- return new[] { "Tagline" };
- case ItemFields.Tags:
- return new[] { "Tags" };
- case ItemFields.IsHD:
- return Array.Empty();
- default:
- return new[] { field.ToString() };
- }
- }
+ private readonly ItemFields[] _allFields = Enum.GetValues();
private bool HasField(InternalItemsQuery query, ItemFields name)
{
@@ -2321,77 +2274,98 @@ namespace Emby.Server.Implementations.Data
return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x));
}
- private List GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable startColumns)
+ private void SetFinalColumnsToSelect(InternalItemsQuery query, List columns)
{
- var list = startColumns.ToList();
-
foreach (var field in _allFields)
{
if (!HasField(query, field))
{
- foreach (var fieldToRemove in GetColumnNamesFromField(field))
+ switch (field)
{
- list.Remove(fieldToRemove);
+ case ItemFields.Settings:
+ columns.Remove("IsLocked");
+ columns.Remove("PreferredMetadataCountryCode");
+ columns.Remove("PreferredMetadataLanguage");
+ columns.Remove("LockedFields");
+ break;
+ case ItemFields.ServiceName:
+ columns.Remove("ExternalServiceId");
+ break;
+ case ItemFields.SortName:
+ columns.Remove("ForcedSortName");
+ break;
+ case ItemFields.Taglines:
+ columns.Remove("Tagline");
+ break;
+ case ItemFields.Tags:
+ columns.Remove("Tags");
+ break;
+ case ItemFields.IsHD:
+ // do nothing
+ break;
+ default:
+ columns.Remove(field.ToString());
+ break;
}
}
}
if (!HasProgramAttributes(query))
{
- list.Remove("IsMovie");
- list.Remove("IsSeries");
- list.Remove("EpisodeTitle");
- list.Remove("IsRepeat");
- list.Remove("ShowId");
+ columns.Remove("IsMovie");
+ columns.Remove("IsSeries");
+ columns.Remove("EpisodeTitle");
+ columns.Remove("IsRepeat");
+ columns.Remove("ShowId");
}
if (!HasEpisodeAttributes(query))
{
- list.Remove("SeasonName");
- list.Remove("SeasonId");
+ columns.Remove("SeasonName");
+ columns.Remove("SeasonId");
}
if (!HasStartDate(query))
{
- list.Remove("StartDate");
+ columns.Remove("StartDate");
}
if (!HasTrailerTypes(query))
{
- list.Remove("TrailerTypes");
+ columns.Remove("TrailerTypes");
}
if (!HasArtistFields(query))
{
- list.Remove("AlbumArtists");
- list.Remove("Artists");
+ columns.Remove("AlbumArtists");
+ columns.Remove("Artists");
}
if (!HasSeriesFields(query))
{
- list.Remove("SeriesId");
+ columns.Remove("SeriesId");
}
if (!HasEpisodeAttributes(query))
{
- list.Remove("SeasonName");
- list.Remove("SeasonId");
+ columns.Remove("SeasonName");
+ columns.Remove("SeasonId");
}
if (!query.DtoOptions.EnableImages)
{
- list.Remove("Images");
+ columns.Remove("Images");
}
if (EnableJoinUserData(query))
{
- list.Add("UserDatas.UserId");
- list.Add("UserDatas.lastPlayedDate");
- list.Add("UserDatas.playbackPositionTicks");
- list.Add("UserDatas.playcount");
- list.Add("UserDatas.isFavorite");
- list.Add("UserDatas.played");
- list.Add("UserDatas.rating");
+ columns.Add("UserDatas.UserId");
+ columns.Add("UserDatas.lastPlayedDate");
+ columns.Add("UserDatas.playbackPositionTicks");
+ columns.Add("UserDatas.playcount");
+ columns.Add("UserDatas.isFavorite");
+ columns.Add("UserDatas.played");
+ columns.Add("UserDatas.rating");
}
if (query.SimilarTo != null)
@@ -2439,7 +2413,7 @@ namespace Emby.Server.Implementations.Data
builder.Append(") as SimilarityScore");
- list.Add(builder.ToString());
+ columns.Add(builder.ToString());
var oldLen = query.ExcludeItemIds.Length;
var newLen = oldLen + item.ExtraIds.Length + 1;
@@ -2466,10 +2440,8 @@ namespace Emby.Server.Implementations.Data
builder.Append(") as SearchScore");
- list.Add(builder.ToString());
+ columns.Add(builder.ToString());
}
-
- return list;
}
private void BindSearchParams(InternalItemsQuery query, IStatement statement)
@@ -2535,31 +2507,25 @@ namespace Emby.Server.Implementations.Data
private string GetGroupBy(InternalItemsQuery query)
{
- var groups = new List();
-
- if (EnableGroupByPresentationUniqueKey(query))
+ var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(query);
+ if (enableGroupByPresentationUniqueKey && query.GroupBySeriesPresentationUniqueKey)
{
- groups.Add("PresentationUniqueKey");
+ return " Group by PresentationUniqueKey, SeriesPresentationUniqueKey";
+ }
+
+ if (enableGroupByPresentationUniqueKey)
+ {
+ return " Group by PresentationUniqueKey";
}
if (query.GroupBySeriesPresentationUniqueKey)
{
- groups.Add("SeriesPresentationUniqueKey");
- }
-
- if (groups.Count > 0)
- {
- return " Group by " + string.Join(',', groups);
+ return " Group by SeriesPresentationUniqueKey";
}
return string.Empty;
}
- private string GetFromText(string alias = "A")
- {
- return " from TypedBaseItems " + alias;
- }
-
public int GetCount(InternalItemsQuery query)
{
if (query == null)
@@ -2577,17 +2543,21 @@ namespace Emby.Server.Implementations.Data
query.Limit = query.Limit.Value + 4;
}
- var commandText = "select "
- + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
- + GetFromText()
- + GetJoinUserDataText(query);
+ var columns = new List { "count(distinct PresentationUniqueKey)" };
+ SetFinalColumnsToSelect(query, columns);
+ var commandTextBuilder = new StringBuilder("select ", 256)
+ .AppendJoin(',', columns)
+ .Append(FromText)
+ .Append(GetJoinUserDataText(query));
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
{
- commandText += " where " + string.Join(" AND ", whereClauses);
+ commandTextBuilder.Append(" where ")
+ .AppendJoin(" AND ", whereClauses);
}
+ var commandText = commandTextBuilder.ToString();
int count;
using (var connection = GetConnection(true))
{
@@ -2629,20 +2599,23 @@ namespace Emby.Server.Implementations.Data
query.Limit = query.Limit.Value + 4;
}
- var commandText = "select "
- + string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
- + GetFromText()
- + GetJoinUserDataText(query);
+ var columns = _retriveItemColumns.ToList();
+ SetFinalColumnsToSelect(query, columns);
+ var commandTextBuilder = new StringBuilder("select ", 1024)
+ .AppendJoin(',', columns)
+ .Append(FromText)
+ .Append(GetJoinUserDataText(query));
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
{
- commandText += " where " + string.Join(" AND ", whereClauses);
+ commandTextBuilder.Append(" where ")
+ .AppendJoin(" AND ", whereClauses);
}
- commandText += GetGroupBy(query)
- + GetOrderByText(query);
+ commandTextBuilder.Append(GetGroupBy(query))
+ .Append(GetOrderByText(query));
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
@@ -2650,15 +2623,18 @@ namespace Emby.Server.Implementations.Data
if (query.Limit.HasValue || offset > 0)
{
- commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ commandTextBuilder.Append(" LIMIT ")
+ .Append(query.Limit ?? int.MaxValue);
}
if (offset > 0)
{
- commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
+ commandTextBuilder.Append(" OFFSET ")
+ .Append(offset);
}
}
+ var commandText = commandTextBuilder.ToString();
var items = new List();
using (var connection = GetConnection(true))
{
@@ -2721,87 +2697,22 @@ namespace Emby.Server.Implementations.Data
private string FixUnicodeChars(string buffer)
{
- if (buffer.IndexOf('\u2013', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u2013', '-'); // en dash
- }
-
- if (buffer.IndexOf('\u2014', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u2014', '-'); // em dash
- }
-
- if (buffer.IndexOf('\u2015', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u2015', '-'); // horizontal bar
- }
-
- if (buffer.IndexOf('\u2017', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u2017', '_'); // double low line
- }
-
- if (buffer.IndexOf('\u2018', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u2018', '\''); // left single quotation mark
- }
-
- if (buffer.IndexOf('\u2019', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u2019', '\''); // right single quotation mark
- }
-
- if (buffer.IndexOf('\u201a', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark
- }
-
- if (buffer.IndexOf('\u201b', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark
- }
-
- if (buffer.IndexOf('\u201c', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark
- }
-
- if (buffer.IndexOf('\u201d', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark
- }
-
- if (buffer.IndexOf('\u201e', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark
- }
-
- if (buffer.IndexOf('\u2026', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis
- }
-
- if (buffer.IndexOf('\u2032', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u2032', '\''); // prime
- }
-
- if (buffer.IndexOf('\u2033', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u2033', '\"'); // double prime
- }
-
- if (buffer.IndexOf('\u0060', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u0060', '\''); // grave accent
- }
-
- if (buffer.IndexOf('\u00B4', StringComparison.Ordinal) > -1)
- {
- buffer = buffer.Replace('\u00B4', '\''); // acute accent
- }
-
- return buffer;
+ buffer = buffer.Replace('\u2013', '-'); // en dash
+ buffer = buffer.Replace('\u2014', '-'); // em dash
+ buffer = buffer.Replace('\u2015', '-'); // horizontal bar
+ buffer = buffer.Replace('\u2017', '_'); // double low line
+ buffer = buffer.Replace('\u2018', '\''); // left single quotation mark
+ buffer = buffer.Replace('\u2019', '\''); // right single quotation mark
+ buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark
+ buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark
+ buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark
+ buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark
+ buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark
+ buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis
+ buffer = buffer.Replace('\u2032', '\''); // prime
+ buffer = buffer.Replace('\u2033', '\"'); // double prime
+ buffer = buffer.Replace('\u0060', '\''); // grave accent
+ return buffer.Replace('\u00B4', '\''); // acute accent
}
private void AddItem(List items, BaseItem newItem)
@@ -2879,20 +2790,27 @@ namespace Emby.Server.Implementations.Data
query.Limit = query.Limit.Value + 4;
}
- var commandText = "select "
- + string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
- + GetFromText()
- + GetJoinUserDataText(query);
+ var columns = _retriveItemColumns.ToList();
+ SetFinalColumnsToSelect(query, columns);
+ var commandTextBuilder = new StringBuilder("select ", 512)
+ .AppendJoin(',', columns)
+ .Append(FromText)
+ .Append(GetJoinUserDataText(query));
var whereClauses = GetWhereClauses(query, null);
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses);
+ string.Join(" AND ", whereClauses);
- commandText += whereText
- + GetGroupBy(query)
- + GetOrderByText(query);
+ if (!string.IsNullOrEmpty(whereText))
+ {
+ commandTextBuilder.Append(" where ")
+ .Append(whereText);
+ }
+
+ commandTextBuilder.Append(GetGroupBy(query))
+ .Append(GetOrderByText(query));
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
@@ -2900,43 +2818,58 @@ namespace Emby.Server.Implementations.Data
if (query.Limit.HasValue || offset > 0)
{
- commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ commandTextBuilder.Append(" LIMIT ")
+ .Append(query.Limit ?? int.MaxValue);
}
if (offset > 0)
{
- commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
+ commandTextBuilder.Append(" OFFSET ")
+ .Append(offset);
}
}
var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
- var statementTexts = new List();
+ var itemQuery = string.Empty;
+ var totalRecordCountQuery = string.Empty;
if (!isReturningZeroItems)
{
- statementTexts.Add(commandText);
+ itemQuery = commandTextBuilder.ToString();
}
if (query.EnableTotalRecordCount)
{
- commandText = string.Empty;
+ commandTextBuilder.Clear();
+ commandTextBuilder.Append(" select ");
+
+ List columnsToSelect;
if (EnableGroupByPresentationUniqueKey(query))
{
- commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
+ columnsToSelect = new List { "count (distinct PresentationUniqueKey)" };
}
else if (query.GroupBySeriesPresentationUniqueKey)
{
- commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
+ columnsToSelect = new List { "count (distinct SeriesPresentationUniqueKey)" };
}
else
{
- commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
+ columnsToSelect = new List { "count (guid)" };
}
- commandText += GetJoinUserDataText(query)
- + whereText;
- statementTexts.Add(commandText);
+ SetFinalColumnsToSelect(query, columnsToSelect);
+
+ commandTextBuilder.AppendJoin(',', columnsToSelect)
+ .Append(FromText)
+ .Append(GetJoinUserDataText(query));
+ if (!string.IsNullOrEmpty(whereText))
+ {
+ commandTextBuilder.Append(" where ")
+ .Append(whereText);
+ }
+
+ totalRecordCountQuery = commandTextBuilder.ToString();
}
var list = new List();
@@ -2946,11 +2879,12 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction(
db =>
{
- var statements = PrepareAll(db, statementTexts);
+ var itemQueryStatement = PrepareStatement(db, itemQuery);
+ var totalRecordCountQueryStatement = PrepareStatement(db, totalRecordCountQuery);
if (!isReturningZeroItems)
{
- using (var statement = statements[0])
+ using (var statement = itemQueryStatement)
{
if (EnableJoinUserData(query))
{
@@ -2980,11 +2914,14 @@ namespace Emby.Server.Implementations.Data
}
}
}
+
+ LogQueryTime("GetItems.ItemQuery", itemQuery, now);
}
+ now = DateTime.UtcNow;
if (query.EnableTotalRecordCount)
{
- using (var statement = statements[statements.Length - 1])
+ using (var statement = totalRecordCountQueryStatement)
{
if (EnableJoinUserData(query))
{
@@ -2999,11 +2936,12 @@ namespace Emby.Server.Implementations.Data
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
+
+ LogQueryTime("GetItems.TotalRecordCount", totalRecordCountQuery, now);
}
}, ReadTransactionMode);
}
- LogQueryTime("GetItems", commandText, now);
result.Items = list;
return result;
}
@@ -3136,19 +3074,22 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
- var commandText = "select "
- + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
- + GetFromText()
- + GetJoinUserDataText(query);
+ var columns = new List { "guid" };
+ SetFinalColumnsToSelect(query, columns);
+ var commandTextBuilder = new StringBuilder("select ", 256)
+ .AppendJoin(',', columns)
+ .Append(FromText)
+ .Append(GetJoinUserDataText(query));
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
{
- commandText += " where " + string.Join(" AND ", whereClauses);
+ commandTextBuilder.Append(" where ")
+ .AppendJoin(" AND ", whereClauses);
}
- commandText += GetGroupBy(query)
- + GetOrderByText(query);
+ commandTextBuilder.Append(GetGroupBy(query))
+ .Append(GetOrderByText(query));
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
@@ -3156,15 +3097,18 @@ namespace Emby.Server.Implementations.Data
if (query.Limit.HasValue || offset > 0)
{
- commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ commandTextBuilder.Append(" LIMIT ")
+ .Append(query.Limit ?? int.MaxValue);
}
if (offset > 0)
{
- commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
+ commandTextBuilder.Append(" OFFSET ")
+ .Append(offset);
}
}
+ var commandText = commandTextBuilder.ToString();
var list = new List();
using (var connection = GetConnection(true))
{
@@ -3203,7 +3147,9 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
- var commandText = "select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid", "path" })) + GetFromText();
+ var columns = new List { "guid", "path" };
+ SetFinalColumnsToSelect(query, columns);
+ var commandText = "select " + string.Join(',', columns) + FromText;
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
@@ -3245,12 +3191,8 @@ namespace Emby.Server.Implementations.Data
foreach (var row in statement.ExecuteQuery())
{
var id = row.GetGuid(0);
- string path = null;
- if (!row.IsDBNull(1))
- {
- path = row.GetString(1);
- }
+ row.TryGetString(1, out var path);
list.Add(new Tuple(id, path));
}
@@ -3283,9 +3225,11 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
+ var columns = new List { "guid" };
+ SetFinalColumnsToSelect(query, columns);
var commandText = "select "
- + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
- + GetFromText()
+ + string.Join(',', columns)
+ + FromText
+ GetJoinUserDataText(query);
var whereClauses = GetWhereClauses(query, null);
@@ -3325,19 +3269,23 @@ namespace Emby.Server.Implementations.Data
{
commandText = string.Empty;
+ List columnsToSelect;
if (EnableGroupByPresentationUniqueKey(query))
{
- commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
+ columnsToSelect = new List { "count (distinct PresentationUniqueKey)" };
}
else if (query.GroupBySeriesPresentationUniqueKey)
{
- commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
+ columnsToSelect = new List { "count (distinct SeriesPresentationUniqueKey)" };
}
else
{
- commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
+ columnsToSelect = new List { "count (guid)" };
}
+ SetFinalColumnsToSelect(query, columnsToSelect);
+ commandText += " select " + string.Join(',', columnsToSelect) + FromText;
+
commandText += GetJoinUserDataText(query)
+ whereText;
statementTexts.Add(commandText);
@@ -3584,11 +3532,11 @@ namespace Emby.Server.Implementations.Data
statement?.TryBind("@IsFolder", query.IsFolder);
}
- var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
+ var includeTypes = query.IncludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray();
// Only specify excluded types if no included types are specified
if (includeTypes.Length == 0)
{
- var excludeTypes = query.ExcludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
+ var excludeTypes = query.ExcludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray();
if (excludeTypes.Length == 1)
{
whereClauses.Add("type<>@type");
@@ -4444,7 +4392,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(string.Join(" AND ", excludeIds));
}
- if (query.ExcludeProviderIds.Count > 0)
+ if (query.ExcludeProviderIds != null && query.ExcludeProviderIds.Count > 0)
{
var excludeIds = new List();
@@ -4474,7 +4422,7 @@ namespace Emby.Server.Implementations.Data
}
}
- if (query.HasAnyProviderId.Count > 0)
+ if (query.HasAnyProviderId != null && query.HasAnyProviderId.Count > 0)
{
var hasProviderIds = new List();
@@ -4532,56 +4480,50 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb"));
}
- var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList();
- var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
-
var queryTopParentIds = query.TopParentIds;
- if (queryTopParentIds.Length == 1)
+ if (queryTopParentIds.Length > 0)
{
- if (enableItemsByName && includedItemByNameTypes.Count == 1)
- {
- whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)");
- if (statement != null)
- {
- statement.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
- }
- }
- else if (enableItemsByName && includedItemByNameTypes.Count > 1)
- {
- var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
- whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))");
- }
- else
- {
- whereClauses.Add("(TopParentId=@TopParentId)");
- }
+ var includedItemByNameTypes = GetItemByNameTypesInQuery(query);
+ var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
- if (statement != null)
+ if (queryTopParentIds.Length == 1)
{
- statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
- }
- }
- else if (queryTopParentIds.Length > 1)
- {
- var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
-
- if (enableItemsByName && includedItemByNameTypes.Count == 1)
- {
- whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))");
- if (statement != null)
+ if (enableItemsByName && includedItemByNameTypes.Count == 1)
{
- statement.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
+ whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)");
+ statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
}
+ else if (enableItemsByName && includedItemByNameTypes.Count > 1)
+ {
+ var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
+ whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))");
+ }
+ else
+ {
+ whereClauses.Add("(TopParentId=@TopParentId)");
+ }
+
+ statement?.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
}
- else if (enableItemsByName && includedItemByNameTypes.Count > 1)
+ else if (queryTopParentIds.Length > 1)
{
- var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
- whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))");
- }
- else
- {
- whereClauses.Add("TopParentId in (" + val + ")");
+ var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
+
+ if (enableItemsByName && includedItemByNameTypes.Count == 1)
+ {
+ whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))");
+ statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
+ }
+ else if (enableItemsByName && includedItemByNameTypes.Count > 1)
+ {
+ var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
+ whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))");
+ }
+ else
+ {
+ whereClauses.Add("TopParentId in (" + val + ")");
+ }
}
}
@@ -4790,27 +4732,27 @@ namespace Emby.Server.Implementations.Data
if (IsTypeInQuery(nameof(Person), query))
{
- list.Add(nameof(Person));
+ list.Add(typeof(Person).FullName);
}
if (IsTypeInQuery(nameof(Genre), query))
{
- list.Add(nameof(Genre));
+ list.Add(typeof(Genre).FullName);
}
if (IsTypeInQuery(nameof(MusicGenre), query))
{
- list.Add(nameof(MusicGenre));
+ list.Add(typeof(MusicGenre).FullName);
}
if (IsTypeInQuery(nameof(MusicArtist), query))
{
- list.Add(nameof(MusicArtist));
+ list.Add(typeof(MusicArtist).FullName);
}
if (IsTypeInQuery(nameof(Studio), query))
{
- list.Add(nameof(Studio));
+ list.Add(typeof(Studio).FullName);
}
return list;
@@ -4863,17 +4805,12 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new[]
- {
- nameof(Episode),
- nameof(Video),
- nameof(Movie),
- nameof(MusicVideo),
- nameof(Series),
- nameof(Season)
- };
-
- if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)))
+ if (query.IncludeItemTypes.Contains(nameof(Episode), StringComparer.OrdinalIgnoreCase)
+ || query.IncludeItemTypes.Contains(nameof(Video), StringComparer.OrdinalIgnoreCase)
+ || query.IncludeItemTypes.Contains(nameof(Movie), StringComparer.OrdinalIgnoreCase)
+ || query.IncludeItemTypes.Contains(nameof(MusicVideo), StringComparer.OrdinalIgnoreCase)
+ || query.IncludeItemTypes.Contains(nameof(Series), StringComparer.OrdinalIgnoreCase)
+ || query.IncludeItemTypes.Contains(nameof(Season), StringComparer.OrdinalIgnoreCase))
{
return true;
}
@@ -4915,15 +4852,10 @@ namespace Emby.Server.Implementations.Data
typeof(AggregateFolder)
};
- public void UpdateInheritedValues(CancellationToken cancellationToken)
- {
- UpdateInheritedTags(cancellationToken);
- }
-
- private void UpdateInheritedTags(CancellationToken cancellationToken)
+ public void UpdateInheritedValues()
{
string sql = string.Join(
- ";",
+ ';',
new string[]
{
"delete from itemvalues where type = 6",
@@ -4946,37 +4878,38 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
}
- private static Dictionary GetTypeMapDictionary()
+ private static Dictionary GetTypeMapDictionary()
{
- var dict = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var dict = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var t in _knownTypes)
{
- dict[t.Name] = new[] { t.FullName };
+ dict[t.Name] = t.FullName;
}
- dict["Program"] = new[] { typeof(LiveTvProgram).FullName };
- dict["TvChannel"] = new[] { typeof(LiveTvChannel).FullName };
+ dict["Program"] = typeof(LiveTvProgram).FullName;
+ dict["TvChannel"] = typeof(LiveTvChannel).FullName;
return dict;
}
// Not crazy about having this all the way down here, but at least it's in one place
- private readonly Dictionary _types = GetTypeMapDictionary();
+ private readonly Dictionary _types = GetTypeMapDictionary();
- private string[] MapIncludeItemTypes(string value)
+ private string MapIncludeItemTypes(string value)
{
- if (_types.TryGetValue(value, out string[] result))
+ if (_types.TryGetValue(value, out string result))
{
return result;
}
if (IsValidType(value))
{
- return new[] { value };
+ return value;
}
- return Array.Empty();
+ Logger.LogWarning("Unknown item type: {ItemType}", value);
+ return null;
}
public void DeleteItem(Guid id)
@@ -5279,64 +5212,87 @@ AND Type = @InternalPersonType)");
public List GetStudioNames()
{
- return GetItemValueNames(new[] { 3 }, new List(), new List());
+ return GetItemValueNames(new[] { 3 }, Array.Empty(), Array.Empty());
}
public List GetAllArtistNames()
{
- return GetItemValueNames(new[] { 0, 1 }, new List(), new List());
+ return GetItemValueNames(new[] { 0, 1 }, Array.Empty(), Array.Empty());
}
public List GetMusicGenreNames()
{
- return GetItemValueNames(new[] { 2 }, new List { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }, new List());
+ return GetItemValueNames(
+ new[] { 2 },
+ new string[]
+ {
+ typeof(Audio).FullName,
+ typeof(MusicVideo).FullName,
+ typeof(MusicAlbum).FullName,
+ typeof(MusicArtist).FullName
+ },
+ Array.Empty());
}
public List GetGenreNames()
{
- return GetItemValueNames(new[] { 2 }, new List(), new List { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" });
+ return GetItemValueNames(
+ new[] { 2 },
+ Array.Empty(),
+ new string[]
+ {
+ typeof(Audio).FullName,
+ typeof(MusicVideo).FullName,
+ typeof(MusicAlbum).FullName,
+ typeof(MusicArtist).FullName
+ });
}
- private List GetItemValueNames(int[] itemValueTypes, List withItemTypes, List excludeItemTypes)
+ private List GetItemValueNames(int[] itemValueTypes, IReadOnlyList withItemTypes, IReadOnlyList excludeItemTypes)
{
CheckDisposed();
- withItemTypes = withItemTypes.SelectMany(MapIncludeItemTypes).ToList();
- excludeItemTypes = excludeItemTypes.SelectMany(MapIncludeItemTypes).ToList();
-
var now = DateTime.UtcNow;
- var typeClause = itemValueTypes.Length == 1 ?
- ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
- ("Type in (" + string.Join(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
-
- var commandText = "Select Value From ItemValues where " + typeClause;
+ var stringBuilder = new StringBuilder("Select Value From ItemValues where Type", 128);
+ if (itemValueTypes.Length == 1)
+ {
+ stringBuilder.Append('=')
+ .Append(itemValueTypes[0]);
+ }
+ else
+ {
+ stringBuilder.Append(" in (")
+ .AppendJoin(',', itemValueTypes)
+ .Append(')');
+ }
if (withItemTypes.Count > 0)
{
- var typeString = string.Join(',', withItemTypes.Select(i => "'" + i + "'"));
- commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))";
+ stringBuilder.Append(" AND ItemId In (select guid from typedbaseitems where type in (")
+ .AppendJoinInSingleQuotes(',', withItemTypes)
+ .Append("))");
}
if (excludeItemTypes.Count > 0)
{
- var typeString = string.Join(',', excludeItemTypes.Select(i => "'" + i + "'"));
- commandText += " AND ItemId not In (select guid from typedbaseitems where type in (" + typeString + "))";
+ stringBuilder.Append(" AND ItemId not In (select guid from typedbaseitems where type in (")
+ .AppendJoinInSingleQuotes(',', excludeItemTypes)
+ .Append("))");
}
- commandText += " Group By CleanValue";
+ stringBuilder.Append(" Group By CleanValue");
+ var commandText = stringBuilder.ToString();
var list = new List();
using (var connection = GetConnection(true))
+ using (var statement = PrepareStatement(connection, commandText))
{
- using (var statement = PrepareStatement(connection, commandText))
+ foreach (var row in statement.ExecuteQuery())
{
- foreach (var row in statement.ExecuteQuery())
+ if (row.TryGetString(0, out var result))
{
- if (!row.IsDBNull(0))
- {
- list.Add(row.GetString(0));
- }
+ list.Add(result);
}
}
}
@@ -5362,18 +5318,19 @@ AND Type = @InternalPersonType)");
var now = DateTime.UtcNow;
var typeClause = itemValueTypes.Length == 1 ?
- ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
- ("Type in (" + string.Join(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
+ ("Type=" + itemValueTypes[0]) :
+ ("Type in (" + string.Join(',', itemValueTypes) + ")");
InternalItemsQuery typeSubQuery = null;
- Dictionary itemCountColumns = null;
+ string itemCountColumns = null;
+ var stringBuilder = new StringBuilder(1024);
var typesToCount = query.IncludeItemTypes;
if (typesToCount.Length > 0)
{
- var itemCountColumnQuery = "select group_concat(type, '|')" + GetFromText("B");
+ stringBuilder.Append("(select group_concat(type, '|') from TypedBaseItems B");
typeSubQuery = new InternalItemsQuery(query.User)
{
@@ -5389,20 +5346,22 @@ AND Type = @InternalPersonType)");
};
var whereClauses = GetWhereClauses(typeSubQuery, null);
- whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
+ stringBuilder.Append(" where ")
+ .AppendJoin(" AND ", whereClauses)
+ .Append(" AND ")
+ .Append("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND ")
+ .Append(typeClause)
+ .Append(")) as itemTypes");
- itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses);
-
- itemCountColumns = new Dictionary()
- {
- { "itemTypes", "(" + itemCountColumnQuery + ") as itemTypes" }
- };
+ itemCountColumns = stringBuilder.ToString();
+ stringBuilder.Clear();
}
List columns = _retriveItemColumns.ToList();
- if (itemCountColumns != null)
+ // Unfortunately we need to add it to columns to ensure the order of the columns in the select
+ if (!string.IsNullOrEmpty(itemCountColumns))
{
- columns.AddRange(itemCountColumns.Values);
+ columns.Add(itemCountColumns);
}
// do this first before calling GetFinalColumnsToSelect, otherwise ExcludeItemIds will be set by SimilarTo
@@ -5423,20 +5382,20 @@ AND Type = @InternalPersonType)");
IsSeries = query.IsSeries
};
- columns = GetFinalColumnsToSelect(query, columns);
-
- var commandText = "select "
- + string.Join(',', columns)
- + GetFromText()
- + GetJoinUserDataText(query);
+ SetFinalColumnsToSelect(query, columns);
var innerWhereClauses = GetWhereClauses(innerQuery, null);
- var innerWhereText = innerWhereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", innerWhereClauses);
+ stringBuilder.Append(" where Type=@SelectType And CleanName In (Select CleanValue from ItemValues where ")
+ .Append(typeClause)
+ .Append(" AND ItemId in (select guid from TypedBaseItems");
+ if (innerWhereClauses.Count > 0)
+ {
+ stringBuilder.Append(" where ")
+ .AppendJoin(" AND ", innerWhereClauses);
+ }
- var whereText = " where Type=@SelectType And CleanName In (Select CleanValue from ItemValues where " + typeClause + " AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))";
+ stringBuilder.Append("))");
var outerQuery = new InternalItemsQuery(query.User)
{
@@ -5461,21 +5420,31 @@ AND Type = @InternalPersonType)");
};
var outerWhereClauses = GetWhereClauses(outerQuery, null);
-
if (outerWhereClauses.Count != 0)
{
- whereText += " AND " + string.Join(" AND ", outerWhereClauses);
+ stringBuilder.Append(" AND ")
+ .AppendJoin(" AND ", outerWhereClauses);
}
- commandText += whereText + " group by PresentationUniqueKey";
+ var whereText = stringBuilder.ToString();
+ stringBuilder.Clear();
- if (query.SimilarTo != null || !string.IsNullOrEmpty(query.SearchTerm))
+ stringBuilder.Append("select ")
+ .AppendJoin(',', columns)
+ .Append(FromText)
+ .Append(GetJoinUserDataText(query))
+ .Append(whereText)
+ .Append(" group by PresentationUniqueKey");
+
+ if (query.OrderBy.Count != 0
+ || query.SimilarTo != null
+ || !string.IsNullOrEmpty(query.SearchTerm))
{
- commandText += GetOrderByText(query);
+ stringBuilder.Append(GetOrderByText(query));
}
else
{
- commandText += " order by SortName";
+ stringBuilder.Append(" order by SortName");
}
if (query.Limit.HasValue || query.StartIndex.HasValue)
@@ -5484,32 +5453,39 @@ AND Type = @InternalPersonType)");
if (query.Limit.HasValue || offset > 0)
{
- commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ stringBuilder.Append(" LIMIT ")
+ .Append(query.Limit ?? int.MaxValue);
}
if (offset > 0)
{
- commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
+ stringBuilder.Append(" OFFSET ")
+ .Append(offset);
}
}
var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
- var statementTexts = new List();
+ string commandText = string.Empty;
+
if (!isReturningZeroItems)
{
- statementTexts.Add(commandText);
+ commandText = stringBuilder.ToString();
}
+ string countText = string.Empty;
if (query.EnableTotalRecordCount)
{
- var countText = "select "
- + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
- + GetFromText()
- + GetJoinUserDataText(query)
- + whereText;
+ stringBuilder.Clear();
+ var columnsToSelect = new List { "count (distinct PresentationUniqueKey)" };
+ SetFinalColumnsToSelect(query, columnsToSelect);
+ stringBuilder.Append("select ")
+ .AppendJoin(',', columnsToSelect)
+ .Append(FromText)
+ .Append(GetJoinUserDataText(query))
+ .Append(whereText);
- statementTexts.Add(countText);
+ countText = stringBuilder.ToString();
}
var list = new List<(BaseItem, ItemCounts)>();
@@ -5519,11 +5495,9 @@ AND Type = @InternalPersonType)");
connection.RunInTransaction(
db =>
{
- var statements = PrepareAll(db, statementTexts);
-
if (!isReturningZeroItems)
{
- using (var statement = statements[0])
+ using (var statement = PrepareStatement(db, commandText))
{
statement.TryBind("@SelectType", returnType);
if (EnableJoinUserData(query))
@@ -5564,13 +5538,7 @@ AND Type = @InternalPersonType)");
if (query.EnableTotalRecordCount)
{
- commandText = "select "
- + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
- + GetFromText()
- + GetJoinUserDataText(query)
- + whereText;
-
- using (var statement = statements[statements.Length - 1])
+ using (var statement = PrepareStatement(db, countText))
{
statement.TryBind("@SelectType", returnType);
if (EnableJoinUserData(query))
@@ -5607,7 +5575,7 @@ AND Type = @InternalPersonType)");
return result;
}
- private ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, string[] typesToCount)
+ private static ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, string[] typesToCount)
{
var counts = new ItemCounts();
@@ -5616,51 +5584,43 @@ AND Type = @InternalPersonType)");
return counts;
}
- var typeString = reader.IsDBNull(countStartColumn) ? null : reader.GetString(countStartColumn);
-
- if (string.IsNullOrWhiteSpace(typeString))
+ if (!reader.TryGetString(countStartColumn, out var typeString))
{
return counts;
}
- var allTypes = typeString.Split('|', StringSplitOptions.RemoveEmptyEntries)
- .ToLookup(x => x);
-
- foreach (var type in allTypes)
+ foreach (var typeName in typeString.AsSpan().Split('|'))
{
- var value = type.Count();
- var typeName = type.Key;
-
- if (string.Equals(typeName, typeof(Series).FullName, StringComparison.OrdinalIgnoreCase))
+ if (typeName.Equals(typeof(Series).FullName, StringComparison.OrdinalIgnoreCase))
{
- counts.SeriesCount = value;
+ counts.SeriesCount++;
}
- else if (string.Equals(typeName, typeof(Episode).FullName, StringComparison.OrdinalIgnoreCase))
+ else if (typeName.Equals(typeof(Episode).FullName, StringComparison.OrdinalIgnoreCase))
{
- counts.EpisodeCount = value;
+ counts.EpisodeCount++;
}
- else if (string.Equals(typeName, typeof(Movie).FullName, StringComparison.OrdinalIgnoreCase))
+ else if (typeName.Equals(typeof(Movie).FullName, StringComparison.OrdinalIgnoreCase))
{
- counts.MovieCount = value;
+ counts.MovieCount++;
}
- else if (string.Equals(typeName, typeof(MusicAlbum).FullName, StringComparison.OrdinalIgnoreCase))
+ else if (typeName.Equals(typeof(MusicAlbum).FullName, StringComparison.OrdinalIgnoreCase))
{
- counts.AlbumCount = value;
+ counts.AlbumCount++;
}
- else if (string.Equals(typeName, typeof(MusicArtist).FullName, StringComparison.OrdinalIgnoreCase))
+ else if (typeName.Equals(typeof(MusicArtist).FullName, StringComparison.OrdinalIgnoreCase))
{
- counts.ArtistCount = value;
+ counts.ArtistCount++;
}
- else if (string.Equals(typeName, typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase))
+ else if (typeName.Equals(typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase))
{
- counts.SongCount = value;
+ counts.SongCount++;
}
- else if (string.Equals(typeName, typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase))
+ else if (typeName.Equals(typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase))
{
- counts.TrailerCount = value;
+ counts.TrailerCount++;
}
- counts.ItemCount += value;
+ counts.ItemCount++;
}
return counts;
@@ -5809,7 +5769,10 @@ AND Type = @InternalPersonType)");
var endIndex = Math.Min(people.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
- insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture));
+ insertText.AppendFormat(
+ CultureInfo.InvariantCulture,
+ "(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),",
+ i.ToString(CultureInfo.InvariantCulture));
}
// Remove last comma
@@ -5843,7 +5806,7 @@ AND Type = @InternalPersonType)");
}
}
- private PersonInfo GetPerson(IReadOnlyList reader)
+ private PersonInfo GetPerson(IReadOnlyList reader)
{
var item = new PersonInfo
{
@@ -5851,19 +5814,19 @@ AND Type = @InternalPersonType)");
Name = reader.GetString(1)
};
- if (!reader.IsDBNull(2))
+ if (reader.TryGetString(2, out var role))
{
- item.Role = reader.GetString(2);
+ item.Role = role;
}
- if (!reader.IsDBNull(3))
+ if (reader.TryGetString(3, out var type))
{
- item.Type = reader.GetString(3);
+ item.Type = type;
}
- if (!reader.IsDBNull(4))
+ if (reader.TryGetInt32(4, out var sortOrder))
{
- item.SortOrder = reader.GetInt32(4);
+ item.SortOrder = sortOrder;
}
return item;
@@ -6050,7 +6013,7 @@ AND Type = @InternalPersonType)");
///
/// The reader.
/// ChapterInfo.
- private MediaStream GetMediaStream(IReadOnlyList reader)
+ private MediaStream GetMediaStream(IReadOnlyList reader)
{
var item = new MediaStream
{
@@ -6059,150 +6022,150 @@ AND Type = @InternalPersonType)");
item.Type = Enum.Parse(reader[2].ToString(), true);
- if (reader[3].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(3, out var codec))
{
- item.Codec = reader[3].ToString();
+ item.Codec = codec;
}
- if (reader[4].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(4, out var language))
{
- item.Language = reader[4].ToString();
+ item.Language = language;
}
- if (reader[5].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(5, out var channelLayout))
{
- item.ChannelLayout = reader[5].ToString();
+ item.ChannelLayout = channelLayout;
}
- if (reader[6].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(6, out var profile))
{
- item.Profile = reader[6].ToString();
+ item.Profile = profile;
}
- if (reader[7].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(7, out var aspectRatio))
{
- item.AspectRatio = reader[7].ToString();
+ item.AspectRatio = aspectRatio;
}
- if (reader[8].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(8, out var path))
{
- item.Path = RestorePath(reader[8].ToString());
+ item.Path = RestorePath(path);
}
item.IsInterlaced = reader.GetBoolean(9);
- if (reader[10].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetInt32(10, out var bitrate))
{
- item.BitRate = reader.GetInt32(10);
+ item.BitRate = bitrate;
}
- if (reader[11].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetInt32(11, out var channels))
{
- item.Channels = reader.GetInt32(11);
+ item.Channels = channels;
}
- if (reader[12].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetInt32(12, out var sampleRate))
{
- item.SampleRate = reader.GetInt32(12);
+ item.SampleRate = sampleRate;
}
item.IsDefault = reader.GetBoolean(13);
item.IsForced = reader.GetBoolean(14);
item.IsExternal = reader.GetBoolean(15);
- if (reader[16].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetInt32(16, out var width))
{
- item.Width = reader.GetInt32(16);
+ item.Width = width;
}
- if (reader[17].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetInt32(17, out var height))
{
- item.Height = reader.GetInt32(17);
+ item.Height = height;
}
- if (reader[18].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetSingle(18, out var averageFrameRate))
{
- item.AverageFrameRate = reader.GetFloat(18);
+ item.AverageFrameRate = averageFrameRate;
}
- if (reader[19].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetSingle(19, out var realFrameRate))
{
- item.RealFrameRate = reader.GetFloat(19);
+ item.RealFrameRate = realFrameRate;
}
- if (reader[20].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetSingle(20, out var level))
{
- item.Level = reader.GetFloat(20);
+ item.Level = level;
}
- if (reader[21].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(21, out var pixelFormat))
{
- item.PixelFormat = reader[21].ToString();
+ item.PixelFormat = pixelFormat;
}
- if (reader[22].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetInt32(22, out var bitDepth))
{
- item.BitDepth = reader.GetInt32(22);
+ item.BitDepth = bitDepth;
}
- if (reader[23].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetBoolean(23, out var isAnamorphic))
{
- item.IsAnamorphic = reader.GetBoolean(23);
+ item.IsAnamorphic = isAnamorphic;
}
- if (reader[24].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetInt32(24, out var refFrames))
{
- item.RefFrames = reader.GetInt32(24);
+ item.RefFrames = refFrames;
}
- if (reader[25].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(25, out var codecTag))
{
- item.CodecTag = reader.GetString(25);
+ item.CodecTag = codecTag;
}
- if (reader[26].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(26, out var comment))
{
- item.Comment = reader.GetString(26);
+ item.Comment = comment;
}
- if (reader[27].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(27, out var nalLengthSize))
{
- item.NalLengthSize = reader.GetString(27);
+ item.NalLengthSize = nalLengthSize;
}
- if (reader[28].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetBoolean(28, out var isAVC))
{
- item.IsAVC = reader[28].ToBool();
+ item.IsAVC = isAVC;
}
- if (reader[29].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(29, out var title))
{
- item.Title = reader[29].ToString();
+ item.Title = title;
}
- if (reader[30].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(30, out var timeBase))
{
- item.TimeBase = reader[30].ToString();
+ item.TimeBase = timeBase;
}
- if (reader[31].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(31, out var codecTimeBase))
{
- item.CodecTimeBase = reader[31].ToString();
+ item.CodecTimeBase = codecTimeBase;
}
- if (reader[32].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(32, out var colorPrimaries))
{
- item.ColorPrimaries = reader[32].ToString();
+ item.ColorPrimaries = colorPrimaries;
}
- if (reader[33].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(33, out var colorSpace))
{
- item.ColorSpace = reader[33].ToString();
+ item.ColorSpace = colorSpace;
}
- if (reader[34].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(34, out var colorTransfer))
{
- item.ColorTransfer = reader[34].ToString();
+ item.ColorTransfer = colorTransfer;
}
if (item.Type == MediaStreamType.Subtitle)
@@ -6261,7 +6224,7 @@ AND Type = @InternalPersonType)");
CheckDisposed();
if (id == Guid.Empty)
{
- throw new ArgumentException(nameof(id));
+ throw new ArgumentException("Guid can't be empty.", nameof(id));
}
if (attachments == null)
@@ -6351,36 +6314,36 @@ AND Type = @InternalPersonType)");
///
/// The reader.
/// MediaAttachment.
- private MediaAttachment GetMediaAttachment(IReadOnlyList reader)
+ private MediaAttachment GetMediaAttachment(IReadOnlyList reader)
{
var item = new MediaAttachment
{
Index = reader[1].ToInt()
};
- if (reader[2].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(2, out var codec))
{
- item.Codec = reader[2].ToString();
+ item.Codec = codec;
}
- if (reader[2].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(3, out var codecTag))
{
- item.CodecTag = reader[3].ToString();
+ item.CodecTag = codecTag;
}
- if (reader[4].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(4, out var comment))
{
- item.Comment = reader[4].ToString();
+ item.Comment = comment;
}
- if (reader[6].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(5, out var fileName))
{
- item.FileName = reader[5].ToString();
+ item.FileName = fileName;
}
- if (reader[6].SQLiteType != SQLiteType.Null)
+ if (reader.TryGetString(6, out var mimeType))
{
- item.MimeType = reader[6].ToString();
+ item.MimeType = mimeType;
}
return item;
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index 6574db6078..829f1de2f6 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -127,19 +129,17 @@ namespace Emby.Server.Implementations.Data
return list;
}
- ///
- /// Saves the user data.
- ///
- public void SaveUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken)
+ ///
+ public void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken)
{
if (userData == null)
{
throw new ArgumentNullException(nameof(userData));
}
- if (internalUserId <= 0)
+ if (userId <= 0)
{
- throw new ArgumentNullException(nameof(internalUserId));
+ throw new ArgumentNullException(nameof(userId));
}
if (string.IsNullOrEmpty(key))
@@ -147,22 +147,23 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(key));
}
- PersistUserData(internalUserId, key, userData, cancellationToken);
+ PersistUserData(userId, key, userData, cancellationToken);
}
- public void SaveAllUserData(long internalUserId, UserItemData[] userData, CancellationToken cancellationToken)
+ ///
+ public void SaveAllUserData(long userId, UserItemData[] userData, CancellationToken cancellationToken)
{
if (userData == null)
{
throw new ArgumentNullException(nameof(userData));
}
- if (internalUserId <= 0)
+ if (userId <= 0)
{
- throw new ArgumentNullException(nameof(internalUserId));
+ throw new ArgumentNullException(nameof(userId));
}
- PersistAllUserData(internalUserId, userData, cancellationToken);
+ PersistAllUserData(userId, userData, cancellationToken);
}
///
@@ -172,7 +173,6 @@ namespace Emby.Server.Implementations.Data
/// The key.
/// The user data.
/// The cancellation token.
- /// Task.
public void PersistUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -262,19 +262,19 @@ namespace Emby.Server.Implementations.Data
///
/// Gets the user data.
///
- /// The user id.
+ /// The user id.
/// The key.
/// Task{UserItemData}.
///
/// userId
/// or
- /// key
+ /// key.
///
- public UserItemData GetUserData(long internalUserId, string key)
+ public UserItemData GetUserData(long userId, string key)
{
- if (internalUserId <= 0)
+ if (userId <= 0)
{
- throw new ArgumentNullException(nameof(internalUserId));
+ throw new ArgumentNullException(nameof(userId));
}
if (string.IsNullOrEmpty(key))
@@ -286,7 +286,7 @@ namespace Emby.Server.Implementations.Data
{
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
{
- statement.TryBind("@UserId", internalUserId);
+ statement.TryBind("@UserId", userId);
statement.TryBind("@Key", key);
foreach (var row in statement.ExecuteQuery())
@@ -299,7 +299,7 @@ namespace Emby.Server.Implementations.Data
}
}
- public UserItemData GetUserData(long internalUserId, List keys)
+ public UserItemData GetUserData(long userId, List keys)
{
if (keys == null)
{
@@ -311,19 +311,19 @@ namespace Emby.Server.Implementations.Data
return null;
}
- return GetUserData(internalUserId, keys[0]);
+ return GetUserData(userId, keys[0]);
}
///
/// Return all user-data associated with the given user.
///
- ///
- ///
- public List GetAllUserData(long internalUserId)
+ /// The internal user id.
+ /// The list of user item data.
+ public List GetAllUserData(long userId)
{
- if (internalUserId <= 0)
+ if (userId <= 0)
{
- throw new ArgumentNullException(nameof(internalUserId));
+ throw new ArgumentNullException(nameof(userId));
}
var list = new List();
@@ -332,7 +332,7 @@ namespace Emby.Server.Implementations.Data
{
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId"))
{
- statement.TryBind("@UserId", internalUserId);
+ statement.TryBind("@UserId", userId);
foreach (var row in statement.ExecuteQuery())
{
@@ -347,17 +347,18 @@ namespace Emby.Server.Implementations.Data
///
/// Read a row from the specified reader into the provided userData object.
///
- ///
- private UserItemData ReadRow(IReadOnlyList reader)
+ /// The list of result set values.
+ /// The user item data.
+ private UserItemData ReadRow(IReadOnlyList reader)
{
var userData = new UserItemData();
userData.Key = reader[0].ToString();
// 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();
@@ -365,19 +366,19 @@ namespace Emby.Server.Implementations.Data
userData.IsFavorite = reader[5].ToBool();
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;
diff --git a/Emby.Server.Implementations/Data/TypeMapper.cs b/Emby.Server.Implementations/Data/TypeMapper.cs
index 7044b1d194..064664e1fe 100644
--- a/Emby.Server.Implementations/Data/TypeMapper.cs
+++ b/Emby.Server.Implementations/Data/TypeMapper.cs
@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Data
/// This holds all the types in the running assemblies
/// so that we can de-serialize properly when we don't have strong types.
///
- private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary();
+ private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary();
///
/// Gets the type.
@@ -21,26 +21,16 @@ namespace Emby.Server.Implementations.Data
/// Name of the type.
/// Type.
/// typeName is null.
- public Type GetType(string typeName)
+ public Type? GetType(string typeName)
{
if (string.IsNullOrEmpty(typeName))
{
throw new ArgumentNullException(nameof(typeName));
}
- return _typeMap.GetOrAdd(typeName, LookupType);
- }
-
- ///
- /// Lookups the type.
- ///
- /// Name of the type.
- /// Type.
- private Type LookupType(string typeName)
- {
- return AppDomain.CurrentDomain.GetAssemblies()
- .Select(a => a.GetType(typeName))
- .FirstOrDefault(t => t != null);
+ return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
+ .Select(a => a.GetType(k))
+ .FirstOrDefault(t => t != null));
}
}
}
diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs
index fa6ac95fd3..3d15b3e768 100644
--- a/Emby.Server.Implementations/Devices/DeviceId.cs
+++ b/Emby.Server.Implementations/Devices/DeviceId.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
deleted file mode 100644
index da5047d244..0000000000
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ /dev/null
@@ -1,144 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
-using Jellyfin.Data.Events;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.Devices;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Session;
-
-namespace Emby.Server.Implementations.Devices
-{
- public class DeviceManager : IDeviceManager
- {
- private readonly IUserManager _userManager;
- private readonly IAuthenticationRepository _authRepo;
- private readonly ConcurrentDictionary _capabilitiesMap = new ();
-
- public DeviceManager(IAuthenticationRepository authRepo, IUserManager userManager)
- {
- _userManager = userManager;
- _authRepo = authRepo;
- }
-
- public event EventHandler>> DeviceOptionsUpdated;
-
- public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
- {
- _capabilitiesMap[deviceId] = capabilities;
- }
-
- public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
- {
- _authRepo.UpdateDeviceOptions(deviceId, options);
-
- DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs>(new Tuple(deviceId, options)));
- }
-
- public DeviceOptions GetDeviceOptions(string deviceId)
- {
- return _authRepo.GetDeviceOptions(deviceId);
- }
-
- public ClientCapabilities GetCapabilities(string id)
- {
- return _capabilitiesMap.TryGetValue(id, out ClientCapabilities result)
- ? result
- : new ClientCapabilities();
- }
-
- public DeviceInfo GetDevice(string id)
- {
- var session = _authRepo.Get(new AuthenticationInfoQuery
- {
- DeviceId = id
- }).Items.FirstOrDefault();
-
- var device = session == null ? null : ToDeviceInfo(session);
-
- return device;
- }
-
- public QueryResult GetDevices(DeviceQuery query)
- {
- IEnumerable sessions = _authRepo.Get(new AuthenticationInfoQuery
- {
- // UserId = query.UserId
- HasUser = true
- }).Items;
-
- // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
- if (query.SupportsSync.HasValue)
- {
- var val = query.SupportsSync.Value;
-
- sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val);
- }
-
- if (!query.UserId.Equals(Guid.Empty))
- {
- var user = _userManager.GetUserById(query.UserId);
-
- sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
- }
-
- var array = sessions.Select(ToDeviceInfo).ToArray();
-
- return new QueryResult(array);
- }
-
- private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo)
- {
- var caps = GetCapabilities(authInfo.DeviceId);
-
- return new DeviceInfo
- {
- AppName = authInfo.AppName,
- AppVersion = authInfo.AppVersion,
- Id = authInfo.DeviceId,
- LastUserId = authInfo.UserId,
- LastUserName = authInfo.UserName,
- Name = authInfo.DeviceName,
- DateLastActivity = authInfo.DateLastActivity,
- IconUrl = caps?.IconUrl
- };
- }
-
- public bool CanAccessDevice(User user, string deviceId)
- {
- if (user == null)
- {
- throw new ArgumentException("user not found");
- }
-
- if (string.IsNullOrEmpty(deviceId))
- {
- throw new ArgumentNullException(nameof(deviceId));
- }
-
- if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator))
- {
- return true;
- }
-
- if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase))
- {
- var capabilities = GetCapabilities(deviceId);
-
- if (capabilities != null && capabilities.SupportsPersistentIdentifier)
- {
- return false;
- }
- }
-
- return true;
- }
- }
-}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 54b18a8c80..74400b5128 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -49,8 +51,6 @@ namespace Emby.Server.Implementations.Dto
private readonly IMediaSourceManager _mediaSourceManager;
private readonly Lazy _livetvManagerFactory;
- private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
-
public DtoService(
ILogger logger,
ILibraryManager libraryManager,
@@ -73,6 +73,8 @@ namespace Emby.Server.Implementations.Dto
_livetvManagerFactory = livetvManagerFactory;
}
+ private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
+
///
public IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null)
{
@@ -505,7 +507,6 @@ namespace Emby.Server.Implementations.Dto
///
/// The dto.
/// The item.
- /// Task.
private void AttachPeople(BaseItemDto dto, BaseItem item)
{
// Ordering by person type to ensure actors and artists are at the front.
@@ -614,7 +615,6 @@ namespace Emby.Server.Implementations.Dto
///
/// The dto.
/// The item.
- /// Task.
private void AttachStudios(BaseItemDto dto, BaseItem item)
{
dto.Studios = item.Studios
@@ -665,10 +665,7 @@ namespace Emby.Server.Implementations.Dto
var tag = GetImageCacheTag(item, image);
if (!string.IsNullOrEmpty(image.BlurHash))
{
- if (dto.ImageBlurHashes == null)
- {
- dto.ImageBlurHashes = new Dictionary>();
- }
+ dto.ImageBlurHashes ??= new Dictionary>();
if (!dto.ImageBlurHashes.ContainsKey(image.Type))
{
@@ -702,10 +699,7 @@ namespace Emby.Server.Implementations.Dto
if (hashes.Count > 0)
{
- if (dto.ImageBlurHashes == null)
- {
- dto.ImageBlurHashes = new Dictionary>();
- }
+ dto.ImageBlurHashes ??= new Dictionary>();
dto.ImageBlurHashes[imageType] = hashes;
}
@@ -811,7 +805,7 @@ namespace Emby.Server.Implementations.Dto
dto.MediaType = item.MediaType;
- if (!(item is LiveTvProgram))
+ if (item is not LiveTvProgram)
{
dto.LocationType = item.LocationType;
}
@@ -898,10 +892,7 @@ namespace Emby.Server.Implementations.Dto
dto.Taglines = new string[] { item.Tagline };
}
- if (dto.Taglines == null)
- {
- dto.Taglines = Array.Empty();
- }
+ dto.Taglines ??= Array.Empty();
}
dto.Type = item.GetBaseItemKind();
@@ -935,9 +926,9 @@ namespace Emby.Server.Implementations.Dto
}
// if (options.ContainsField(ItemFields.MediaSourceCount))
- //{
+ // {
// Songs always have one
- //}
+ // }
}
if (item is IHasArtist hasArtist)
@@ -945,10 +936,10 @@ namespace Emby.Server.Implementations.Dto
dto.Artists = hasArtist.Artists;
// var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
- //{
+ // {
// EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
- //});
+ // });
// dto.ArtistItems = artistItems.Items
// .Select(i =>
@@ -965,7 +956,7 @@ namespace Emby.Server.Implementations.Dto
// Include artists that are not in the database yet, e.g., just added via metadata editor
// var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
dto.ArtistItems = hasArtist.Artists
- //.Except(foundArtists, new DistinctNameComparer())
+ // .Except(foundArtists, new DistinctNameComparer())
.Select(i =>
{
// This should not be necessary but we're seeing some cases of it
@@ -997,10 +988,10 @@ namespace Emby.Server.Implementations.Dto
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
// var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
- //{
+ // {
// EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
- //});
+ // });
// dto.AlbumArtists = artistItems.Items
// .Select(i =>
@@ -1015,7 +1006,7 @@ namespace Emby.Server.Implementations.Dto
// .ToList();
dto.AlbumArtists = hasAlbumArtist.AlbumArtists
- //.Except(foundArtists, new DistinctNameComparer())
+ // .Except(foundArtists, new DistinctNameComparer())
.Select(i =>
{
// This should not be necessary but we're seeing some cases of it
@@ -1042,8 +1033,7 @@ namespace Emby.Server.Implementations.Dto
}
// Add video info
- var video = item as Video;
- if (video != null)
+ if (item is Video video)
{
dto.VideoType = video.VideoType;
dto.Video3DFormat = video.Video3DFormat;
@@ -1082,9 +1072,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.MediaStreams))
{
// Add VideoInfo
- var iHasMediaSources = item as IHasMediaSources;
-
- if (iHasMediaSources != null)
+ if (item is IHasMediaSources)
{
MediaStream[] mediaStreams;
@@ -1153,7 +1141,7 @@ namespace Emby.Server.Implementations.Dto
// TODO maybe remove the if statement entirely
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{
- episodeSeries = episodeSeries ?? episode.Series;
+ episodeSeries ??= episode.Series;
if (episodeSeries != null)
{
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
@@ -1166,7 +1154,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.SeriesStudio))
{
- episodeSeries = episodeSeries ?? episode.Series;
+ episodeSeries ??= episode.Series;
if (episodeSeries != null)
{
dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
@@ -1179,7 +1167,7 @@ namespace Emby.Server.Implementations.Dto
{
dto.AirDays = series.AirDays;
dto.AirTime = series.AirTime;
- dto.Status = series.Status.HasValue ? series.Status.Value.ToString() : null;
+ dto.Status = series.Status?.ToString();
}
// Add SeasonInfo
@@ -1192,7 +1180,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.SeriesStudio))
{
- series = series ?? season.Series;
+ series ??= season.Series;
if (series != null)
{
dto.SeriesStudio = series.Studios.FirstOrDefault();
@@ -1203,7 +1191,7 @@ namespace Emby.Server.Implementations.Dto
// TODO maybe remove the if statement entirely
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{
- series = series ?? season.Series;
+ series ??= season.Series;
if (series != null)
{
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
@@ -1290,7 +1278,7 @@ namespace Emby.Server.Implementations.Dto
var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
- if (parent == null && !(originalItem is UserRootFolder) && !(originalItem is UserView) && !(originalItem is AggregateFolder) && !(originalItem is ICollectionFolder) && !(originalItem is Channel))
+ if (parent == null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is not AggregateFolder && originalItem is not ICollectionFolder && originalItem is not Channel)
{
parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
}
@@ -1323,9 +1311,12 @@ namespace Emby.Server.Implementations.Dto
var imageTags = dto.ImageTags;
- while (((!(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0) || parent is Series) &&
- (parent = parent ?? (isFirst ? GetImageDisplayParent(item, item) ?? owner : parent)) != null)
+ while ((!(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0)
+ || (!(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0)
+ || (!(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0)
+ || parent is Series)
{
+ parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent;
if (parent == null)
{
break;
@@ -1355,7 +1346,7 @@ namespace Emby.Server.Implementations.Dto
}
}
- if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
+ if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && parent is not ICollectionFolder && parent is not UserView)
{
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
@@ -1405,7 +1396,6 @@ namespace Emby.Server.Implementations.Dto
///
/// The dto.
/// The item.
- /// Task.
public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
{
dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 9248053f54..fa24e9dd1d 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -9,6 +9,7 @@
+
@@ -22,16 +23,17 @@
+
-
+
-
+
-
-
-
+
+
+
@@ -43,11 +45,13 @@
net5.0
false
true
- true
AD0001
- AllEnabledByDefault
- ../jellyfin.ruleset
+ false
+
+
+
+ true
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index 14201ead29..0a4efd73c7 100644
--- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -106,8 +108,6 @@ namespace Emby.Server.Implementations.EntryPoints
NatUtility.StartDiscovery();
_timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
-
- _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
}
private void Stop()
@@ -118,13 +118,6 @@ namespace Emby.Server.Implementations.EntryPoints
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
_timer?.Dispose();
-
- _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
- }
-
- private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs e)
- {
- NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
}
private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index ae1b51b4c3..df48346e3c 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -147,7 +149,7 @@ namespace Emby.Server.Implementations.EntryPoints
private static bool EnableRefreshMessage(BaseItem item)
{
- if (!(item is Folder folder))
+ if (item is not Folder folder)
{
return false;
}
@@ -401,7 +403,7 @@ namespace Emby.Server.Implementations.EntryPoints
return false;
}
- if (item is IItemByName && !(item is MusicArtist))
+ if (item is IItemByName && item is not MusicArtist)
{
return false;
}
diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
index 824bb85f44..e0ca02d986 100644
--- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index 3624e079f5..feaccf9fa1 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System;
using System.Net.Sockets;
using System.Threading;
@@ -39,6 +37,9 @@ namespace Emby.Server.Implementations.EntryPoints
///
/// Initializes a new instance of the class.
///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
public UdpServerEntryPoint(
ILogger logger,
IServerApplicationHost appHost,
@@ -56,8 +57,8 @@ namespace Emby.Server.Implementations.EntryPoints
try
{
- _udpServer = new UdpServer(_logger, _appHost, _config);
- _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
+ _udpServer = new UdpServer(_logger, _appHost, _config, PortNumber);
+ _udpServer.Start(_cancellationTokenSource.Token);
}
catch (SocketException ex)
{
diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
index 1989e9ed25..d3bcd5e13e 100644
--- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly Dictionary> _changedItems = new Dictionary>();
private readonly object _syncLock = new object();
- private Timer _updateTimer;
+ private Timer? _updateTimer;
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager)
{
@@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask;
}
- void OnUserDataManagerUserDataSaved(object sender, UserDataSaveEventArgs e)
+ private void OnUserDataManagerUserDataSaved(object? sender, UserDataSaveEventArgs e)
{
if (e.SaveReason == UserDataSaveReason.PlaybackProgress)
{
@@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.EntryPoints
_updateTimer.Change(UpdateDuration, Timeout.Infinite);
}
- if (!_changedItems.TryGetValue(e.UserId, out List keys))
+ if (!_changedItems.TryGetValue(e.UserId, out List? keys))
{
keys = new List();
_changedItems[e.UserId] = keys;
@@ -87,7 +87,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
- private void UpdateTimerCallback(object state)
+ private void UpdateTimerCallback(object? state)
{
lock (_syncLock)
{
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
index 9afabf5272..e2ad07177e 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
@@ -17,9 +18,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
_authorizationContext = authorizationContext;
}
- public AuthorizationInfo Authenticate(HttpRequest request)
+ public async Task Authenticate(HttpRequest request)
{
- var auth = _authorizationContext.GetAuthorizationInfo(request);
+ var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false);
if (!auth.HasToken)
{
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
deleted file mode 100644
index 024404ceb0..0000000000
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ /dev/null
@@ -1,291 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Security;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Net.Http.Headers;
-
-namespace Emby.Server.Implementations.HttpServer.Security
-{
- public class AuthorizationContext : IAuthorizationContext
- {
- private readonly IAuthenticationRepository _authRepo;
- private readonly IUserManager _userManager;
-
- public AuthorizationContext(IAuthenticationRepository authRepo, IUserManager userManager)
- {
- _authRepo = authRepo;
- _userManager = userManager;
- }
-
- public AuthorizationInfo GetAuthorizationInfo(HttpContext requestContext)
- {
- if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached))
- {
- return (AuthorizationInfo)cached;
- }
-
- return GetAuthorization(requestContext);
- }
-
- public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext)
- {
- var auth = GetAuthorizationDictionary(requestContext);
- var authInfo = GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query);
- return authInfo;
- }
-
- ///
- /// Gets the authorization.
- ///
- /// The HTTP req.
- /// Dictionary{System.StringSystem.String}.
- private AuthorizationInfo GetAuthorization(HttpContext httpReq)
- {
- var auth = GetAuthorizationDictionary(httpReq);
- var authInfo = GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query);
-
- httpReq.Request.HttpContext.Items["AuthorizationInfo"] = authInfo;
- return authInfo;
- }
-
- private AuthorizationInfo GetAuthorizationInfoFromDictionary(
- in Dictionary auth,
- in IHeaderDictionary headers,
- in IQueryCollection queryString)
- {
- string deviceId = null;
- string device = null;
- string client = null;
- string version = null;
- string token = null;
-
- if (auth != null)
- {
- auth.TryGetValue("DeviceId", out deviceId);
- auth.TryGetValue("Device", out device);
- auth.TryGetValue("Client", out client);
- auth.TryGetValue("Version", out version);
- auth.TryGetValue("Token", out token);
- }
-
- if (string.IsNullOrEmpty(token))
- {
- token = headers["X-Emby-Token"];
- }
-
- if (string.IsNullOrEmpty(token))
- {
- token = headers["X-MediaBrowser-Token"];
- }
-
- if (string.IsNullOrEmpty(token))
- {
- token = queryString["ApiKey"];
- }
-
- // TODO deprecate this query parameter.
- if (string.IsNullOrEmpty(token))
- {
- token = queryString["api_key"];
- }
-
- var authInfo = new AuthorizationInfo
- {
- Client = client,
- Device = device,
- DeviceId = deviceId,
- Version = version,
- Token = token,
- IsAuthenticated = false,
- HasToken = false
- };
-
- if (string.IsNullOrWhiteSpace(token))
- {
- // Request doesn't contain a token.
- return authInfo;
- }
-
- authInfo.HasToken = true;
- var result = _authRepo.Get(new AuthenticationInfoQuery
- {
- AccessToken = token
- });
-
- if (result.Items.Count > 0)
- {
- authInfo.IsAuthenticated = true;
- }
-
- var originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null;
-
- if (originalAuthenticationInfo != null)
- {
- var updateToken = false;
-
- // TODO: Remove these checks for IsNullOrWhiteSpace
- if (string.IsNullOrWhiteSpace(authInfo.Client))
- {
- authInfo.Client = originalAuthenticationInfo.AppName;
- }
-
- if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
- {
- authInfo.DeviceId = originalAuthenticationInfo.DeviceId;
- }
-
- // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
- var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
-
- if (string.IsNullOrWhiteSpace(authInfo.Device))
- {
- authInfo.Device = originalAuthenticationInfo.DeviceName;
- }
- else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
- {
- if (allowTokenInfoUpdate)
- {
- updateToken = true;
- originalAuthenticationInfo.DeviceName = authInfo.Device;
- }
- }
-
- if (string.IsNullOrWhiteSpace(authInfo.Version))
- {
- authInfo.Version = originalAuthenticationInfo.AppVersion;
- }
- else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
- {
- if (allowTokenInfoUpdate)
- {
- updateToken = true;
- originalAuthenticationInfo.AppVersion = authInfo.Version;
- }
- }
-
- if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3)
- {
- originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow;
- updateToken = true;
- }
-
- if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty))
- {
- authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId);
-
- if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase))
- {
- originalAuthenticationInfo.UserName = authInfo.User.Username;
- updateToken = true;
- }
-
- authInfo.IsApiKey = false;
- }
- else
- {
- authInfo.IsApiKey = true;
- }
-
- if (updateToken)
- {
- _authRepo.Update(originalAuthenticationInfo);
- }
- }
-
- return authInfo;
- }
-
- ///
- /// Gets the auth.
- ///
- /// The HTTP req.
- /// Dictionary{System.StringSystem.String}.
- private Dictionary GetAuthorizationDictionary(HttpContext httpReq)
- {
- var auth = httpReq.Request.Headers["X-Emby-Authorization"];
-
- if (string.IsNullOrEmpty(auth))
- {
- auth = httpReq.Request.Headers[HeaderNames.Authorization];
- }
-
- return GetAuthorization(auth);
- }
-
- ///
- /// Gets the auth.
- ///
- /// The HTTP req.
- /// Dictionary{System.StringSystem.String}.
- private Dictionary GetAuthorizationDictionary(HttpRequest httpReq)
- {
- var auth = httpReq.Headers["X-Emby-Authorization"];
-
- if (string.IsNullOrEmpty(auth))
- {
- auth = httpReq.Headers[HeaderNames.Authorization];
- }
-
- return GetAuthorization(auth);
- }
-
- ///
- /// Gets the authorization.
- ///
- /// The authorization header.
- /// Dictionary{System.StringSystem.String}.
- private Dictionary GetAuthorization(string authorizationHeader)
- {
- if (authorizationHeader == null)
- {
- return null;
- }
-
- var parts = authorizationHeader.Split(' ', 2);
-
- // There should be at least to parts
- if (parts.Length != 2)
- {
- return null;
- }
-
- var acceptedNames = new[] { "MediaBrowser", "Emby" };
-
- // It has to be a digest request
- if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase))
- {
- return null;
- }
-
- // Remove uptil the first space
- authorizationHeader = parts[1];
- parts = authorizationHeader.Split(',');
-
- var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
-
- foreach (var item in parts)
- {
- var param = item.Trim().Split('=', 2);
-
- if (param.Length == 2)
- {
- var value = NormalizeValue(param[1].Trim('"'));
- result[param[0]] = value;
- }
- }
-
- return result;
- }
-
- private static string NormalizeValue(string value)
- {
- return string.IsNullOrEmpty(value) ? value : WebUtility.HtmlEncode(value);
- }
- }
-}
diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
index dd77b45d89..a7647caf95 100644
--- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System;
+using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
@@ -23,27 +24,33 @@ namespace Emby.Server.Implementations.HttpServer.Security
_sessionManager = sessionManager;
}
- public SessionInfo GetSession(HttpContext requestContext)
+ public async Task GetSession(HttpContext requestContext)
{
- var authorization = _authContext.GetAuthorizationInfo(requestContext);
+ var authorization = await _authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false);
var user = authorization.User;
- return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user);
+ return await _sessionManager.LogSessionActivity(
+ authorization.Client,
+ authorization.Version,
+ authorization.DeviceId,
+ authorization.Device,
+ requestContext.GetNormalizedRemoteIp().ToString(),
+ user).ConfigureAwait(false);
}
- public SessionInfo GetSession(object requestContext)
+ public Task GetSession(object requestContext)
{
return GetSession((HttpContext)requestContext);
}
- public User GetUser(HttpContext requestContext)
+ public async Task GetUser(HttpContext requestContext)
{
- var session = GetSession(requestContext);
+ var session = await GetSession(requestContext).ConfigureAwait(false);
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
}
- public User GetUser(object requestContext)
+ public Task GetUser(object requestContext)
{
return GetUser(((HttpRequest)requestContext).HttpContext);
}
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index 06acb56061..7010a6fb08 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System;
using System.Buffers;
using System.IO.Pipelines;
@@ -9,7 +7,7 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
@@ -64,7 +62,7 @@ namespace Emby.Server.Implementations.HttpServer
public event EventHandler? Closed;
///
- /// Gets or sets the remote end point.
+ /// Gets the remote end point.
///
public IPAddress? RemoteEndPoint { get; }
@@ -84,7 +82,7 @@ namespace Emby.Server.Implementations.HttpServer
public DateTime LastKeepAliveDate { get; set; }
///
- /// Gets or sets the query string.
+ /// Gets the query string.
///
/// The query string.
public IQueryCollection QueryString { get; }
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
index 1bee1ac310..f86bfd7550 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -33,7 +35,7 @@ namespace Emby.Server.Implementations.HttpServer
///
public async Task WebSocketRequestHandler(HttpContext context)
{
- _ = _authService.Authenticate(context.Request);
+ _ = await _authService.Authenticate(context.Request).ConfigureAwait(false);
try
{
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs
index 7435e9d0bf..47a83d77ce 100644
--- a/Emby.Server.Implementations/IO/FileRefresher.cs
+++ b/Emby.Server.Implementations/IO/FileRefresher.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index 3353fae9d8..aa80bccd72 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 3893a15771..2c722ff25a 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -2,16 +2,15 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Text;
+using System.Runtime.InteropServices;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
-using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Server.Implementations.IO
{
@@ -24,7 +23,7 @@ namespace Emby.Server.Implementations.IO
private readonly List _shortcutHandlers = new List();
private readonly string _tempPath;
- private readonly bool _isEnvironmentCaseInsensitive;
+ private static readonly bool _isEnvironmentCaseInsensitive = OperatingSystem.IsWindows();
public ManagedFileSystem(
ILogger logger,
@@ -32,8 +31,6 @@ namespace Emby.Server.Implementations.IO
{
Logger = logger;
_tempPath = applicationPaths.TempDirectory;
-
- _isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows;
}
public virtual void AddShortcutHandler(IShortcutHandler handler)
@@ -55,7 +52,7 @@ namespace Emby.Server.Implementations.IO
}
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));
}
///
@@ -64,7 +61,7 @@ namespace Emby.Server.Implementations.IO
/// The filename.
/// System.String.
/// filename
- public virtual string ResolveShortcut(string filename)
+ public virtual string? ResolveShortcut(string filename)
{
if (string.IsNullOrEmpty(filename))
{
@@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.IO
}
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);
}
@@ -246,8 +243,8 @@ namespace Emby.Server.Implementations.IO
{
result.Length = fileInfo.Length;
- // Issue #2354 get the size of files behind symbolic links
- if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
+ // Issue #2354 get the size of files behind symbolic links. Also Enum.HasFlag is bad as it boxes!
+ if ((fileInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
try
{
@@ -263,8 +260,6 @@ namespace Emby.Server.Implementations.IO
result.Exists = false;
}
}
-
- result.DirectoryName = fileInfo.DirectoryName;
}
result.CreationTimeUtc = GetCreationTimeUtc(info);
@@ -303,16 +298,37 @@ namespace Emby.Server.Implementations.IO
/// The filename.
/// System.String.
/// The filename is null.
- public virtual string GetValidFilename(string filename)
+ public string GetValidFilename(string filename)
{
- var builder = new StringBuilder(filename);
-
- foreach (var c in Path.GetInvalidFileNameChars())
+ var invalid = Path.GetInvalidFileNameChars();
+ var first = filename.IndexOfAny(invalid);
+ 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] = ' ';
+ }
+ }
+ }
+ });
}
///
@@ -385,7 +401,7 @@ namespace Emby.Server.Implementations.IO
public virtual void SetHidden(string path, bool isHidden)
{
- if (OperatingSystem.Id != OperatingSystemId.Windows)
+ if (!OperatingSystem.IsWindows())
{
return;
}
@@ -407,9 +423,9 @@ namespace Emby.Server.Implementations.IO
}
}
- public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
+ public virtual void SetAttributes(string path, bool isHidden, bool readOnly)
{
- if (OperatingSystem.Id != OperatingSystemId.Windows)
+ if (!OperatingSystem.IsWindows())
{
return;
}
@@ -421,14 +437,14 @@ namespace Emby.Server.Implementations.IO
return;
}
- if (info.IsReadOnly == isReadOnly && info.IsHidden == isHidden)
+ if (info.IsReadOnly == readOnly && info.IsHidden == isHidden)
{
return;
}
var attributes = File.GetAttributes(path);
- if (isReadOnly)
+ if (readOnly)
{
attributes = attributes | FileAttributes.ReadOnly;
}
@@ -585,7 +601,7 @@ namespace Emby.Server.Implementations.IO
return GetFiles(path, null, false, recursive);
}
- public virtual IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
+ public virtual IEnumerable GetFiles(string path, IReadOnlyList? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{
var enumerationOptions = GetEnumerationOptions(recursive);
@@ -602,13 +618,13 @@ namespace Emby.Server.Implementations.IO
{
files = files.Where(i =>
{
- var ext = i.Extension;
- if (ext == null)
+ var ext = i.Extension.AsSpan();
+ if (ext.IsEmpty)
{
return false;
}
- return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
+ return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
});
}
@@ -620,8 +636,7 @@ namespace Emby.Server.Implementations.IO
var directoryInfo = new DirectoryInfo(path);
var enumerationOptions = GetEnumerationOptions(recursive);
- return ToMetadata(directoryInfo.EnumerateDirectories("*", enumerationOptions))
- .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", enumerationOptions)));
+ return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", enumerationOptions));
}
private IEnumerable ToMetadata(IEnumerable infos)
@@ -639,7 +654,7 @@ namespace Emby.Server.Implementations.IO
return GetFilePaths(path, null, false, recursive);
}
- public virtual IEnumerable GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
+ public virtual IEnumerable GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{
var enumerationOptions = GetEnumerationOptions(recursive);
@@ -656,13 +671,13 @@ namespace Emby.Server.Implementations.IO
{
files = files.Where(i =>
{
- var ext = Path.GetExtension(i);
- if (ext == null)
+ var ext = Path.GetExtension(i.AsSpan());
+ if (ext.IsEmpty)
{
return false;
}
- return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
+ return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
});
}
@@ -684,20 +699,5 @@ namespace Emby.Server.Implementations.IO
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();
- }
- }
}
}
diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
index e6696b8c4c..76c58d5dcd 100644
--- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
+++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO
public string Extension => ".mblink";
- public string Resolve(string shortcutPath)
+ public string? Resolve(string shortcutPath)
{
if (string.IsNullOrEmpty(shortcutPath))
{
diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs
index c16ebd61b7..e4f5f4cf0b 100644
--- a/Emby.Server.Implementations/IO/StreamHelper.cs
+++ b/Emby.Server.Implementations/IO/StreamHelper.cs
@@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.IO
{
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.Shared.Rent(bufferSize);
try
diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs
index f719dc5f89..1d97882db5 100644
--- a/Emby.Server.Implementations/IStartupOptions.cs
+++ b/Emby.Server.Implementations/IStartupOptions.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#nullable enable
namespace Emby.Server.Implementations
{
@@ -11,7 +10,7 @@ namespace Emby.Server.Implementations
string? FFmpegPath { get; }
///
- /// Gets the value of the --service command line option.
+ /// Gets a value value indicating whether to run as service by the --service command line option.
///
bool IsService { get; }
diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index 5f7e51858a..4a026fd218 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -49,7 +51,7 @@ namespace Emby.Server.Implementations.Images
public int Order => 0;
- protected virtual bool Supports(BaseItem _) => true;
+ protected virtual bool Supports(BaseItem item) => true;
public async Task FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
@@ -191,7 +193,7 @@ namespace Emby.Server.Implementations.Images
InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray()
};
- if (options.InputPaths.Length == 0)
+ if (options.InputPaths.Count == 0)
{
return null;
}
diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
index 161b4c4528..0229fbae79 100644
--- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -28,27 +30,27 @@ namespace Emby.Server.Implementations.Images
string[] includeItemTypes;
- if (string.Equals(viewType, CollectionType.Movies))
+ if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Movie" };
}
- else if (string.Equals(viewType, CollectionType.TvShows))
+ else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Series" };
}
- else if (string.Equals(viewType, CollectionType.Music))
+ else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "MusicAlbum" };
}
- else if (string.Equals(viewType, CollectionType.Books))
+ else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Book", "AudioBook" };
}
- else if (string.Equals(viewType, CollectionType.BoxSets))
+ else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "BoxSet" };
}
- else if (string.Equals(viewType, CollectionType.HomeVideos) || string.Equals(viewType, CollectionType.Photos))
+ else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Video", "Photo" };
}
diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
index 50c5314820..900b3fd9c6 100644
--- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/Images/FolderImageProvider.cs b/Emby.Server.Implementations/Images/FolderImageProvider.cs
index 0224ab32a0..859017f869 100644
--- a/Emby.Server.Implementations/Images/FolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/FolderImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs
index 3817882312..6da431c68e 100644
--- a/Emby.Server.Implementations/Images/GenreImageProvider.cs
+++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs
index 0ce1b91e88..b8f0f0d65c 100644
--- a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs
+++ b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.Collections.Generic;
@@ -29,9 +31,7 @@ namespace Emby.Server.Implementations.Images
{
var subItem = i.Item2;
- var episode = subItem as Episode;
-
- if (episode != null)
+ if (subItem is Episode episode)
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index 3380e29d48..c7d1139639 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Library
if (parent != null)
{
// Don't resolve these into audio files
- if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename, StringComparison.Ordinal)
+ if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFilename, StringComparison.Ordinal)
&& _libraryManager.IsAudioFile(filename))
{
return true;
diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
index 236453e805..6c65b58999 100644
--- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
+++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs
index e30a675931..5384c04b3b 100644
--- a/Emby.Server.Implementations/Library/IgnorePatterns.cs
+++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System;
using System.Linq;
using DotNet.Globbing;
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 6a9f4174dc..8054beae3f 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -19,6 +21,7 @@ using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.ScheduledTasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller;
@@ -48,6 +51,7 @@ using MediaBrowser.Providers.MediaInfo;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
using VideoResolver = Emby.Naming.Video.VideoResolver;
@@ -175,10 +179,7 @@ namespace Emby.Server.Implementations.Library
{
lock (_rootFolderSyncLock)
{
- if (_rootFolder == null)
- {
- _rootFolder = CreateRootFolder();
- }
+ _rootFolder ??= CreateRootFolder();
}
}
@@ -286,14 +287,14 @@ namespace Emby.Server.Implementations.Library
if (item is IItemByName)
{
- if (!(item is MusicArtist))
+ if (item is not MusicArtist)
{
return;
}
}
else if (!item.IsFolder)
{
- if (!(item is Video) && !(item is LiveTvChannel))
+ if (item is not Video && item is not LiveTvChannel)
{
return;
}
@@ -558,7 +559,6 @@ namespace Emby.Server.Implementations.Library
var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
{
Parent = parent,
- Path = fullPath,
FileInfo = fileInfo,
CollectionType = collectionType,
LibraryOptions = libraryOptions
@@ -684,7 +684,7 @@ namespace Emby.Server.Implementations.Library
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));
@@ -697,25 +697,32 @@ namespace Emby.Server.Implementations.Library
}
private IEnumerable ResolveFileList(
- IEnumerable fileList,
+ IReadOnlyList fileList,
IDirectoryService directoryService,
Folder parent,
string collectionType,
IItemResolver[] resolvers,
LibraryOptions libraryOptions)
{
- return fileList.Select(f =>
+ // Given that fileList is a list we can save enumerator allocations by indexing
+ for (var i = 0; i < fileList.Count; i++)
{
+ var file = fileList[i];
+ BaseItem result = null;
try
{
- return ResolvePath(f, directoryService, resolvers, parent, collectionType, libraryOptions);
+ result = ResolvePath(file, directoryService, resolvers, parent, collectionType, libraryOptions);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error resolving path {path}", f.FullName);
- return null;
+ _logger.LogError(ex, "Error resolving path {Path}", file.FullName);
}
- }).Where(i => i != null);
+
+ if (result != null)
+ {
+ yield return result;
+ }
+ }
}
///
@@ -859,7 +866,7 @@ namespace Emby.Server.Implementations.Library
{
var path = Person.GetPath(name);
var id = GetItemByNameId(path);
- if (!(GetItemById(id) is Person item))
+ if (GetItemById(id) is not Person item)
{
item = new Person
{
@@ -1066,17 +1073,17 @@ namespace Emby.Server.Implementations.Library
// Start by just validating the children of the root, but go no further
await RootFolder.ValidateChildren(
new SimpleProgress(),
- cancellationToken,
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
- recursive: false).ConfigureAwait(false);
+ recursive: false,
+ cancellationToken).ConfigureAwait(false);
await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
await GetUserRootFolder().ValidateChildren(
new SimpleProgress(),
- cancellationToken,
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
- recursive: false).ConfigureAwait(false);
+ recursive: false,
+ cancellationToken).ConfigureAwait(false);
// Quickly scan CollectionFolders for changes
foreach (var folder in GetUserRootFolder().Children.OfType())
@@ -1096,7 +1103,7 @@ namespace Emby.Server.Implementations.Library
innerProgress.RegisterAction(pct => progress.Report(pct * 0.96));
// Validate the entire media library
- await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
+ await RootFolder.ValidateChildren(innerProgress, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true, cancellationToken).ConfigureAwait(false);
progress.Report(96);
@@ -1163,7 +1170,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(percent * 100);
}
- _itemRepository.UpdateInheritedValues(cancellationToken);
+ _itemRepository.UpdateInheritedValues();
progress.Report(100);
}
@@ -1754,22 +1761,20 @@ namespace Emby.Server.Implementations.Library
return orderedItems ?? items;
}
- public IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderByList)
+ public IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderBy)
{
var isFirst = true;
IOrderedEnumerable orderedItems = null;
- foreach (var orderBy in orderByList)
+ foreach (var (name, sortOrder) in orderBy)
{
- var comparer = GetComparer(orderBy.Item1, user);
+ var comparer = GetComparer(name, user);
if (comparer == null)
{
continue;
}
- var sortOrder = orderBy.Item2;
-
if (isFirst)
{
orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer);
@@ -2077,7 +2082,7 @@ namespace Emby.Server.Implementations.Library
return new List();
}
- return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType().ToList());
+ return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType());
}
public List GetCollectionFolders(BaseItem item, List allUserRootChildren)
@@ -2102,20 +2107,20 @@ namespace Emby.Server.Implementations.Library
return GetCollectionFoldersInternal(item, allUserRootChildren);
}
- private static List GetCollectionFoldersInternal(BaseItem item, List allUserRootChildren)
+ private static List GetCollectionFoldersInternal(BaseItem item, IEnumerable allUserRootChildren)
{
return allUserRootChildren
- .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase))
+ .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path.AsSpan(), StringComparison.OrdinalIgnoreCase))
.ToList();
}
public LibraryOptions GetLibraryOptions(BaseItem item)
{
- if (!(item is CollectionFolder collectionFolder))
+ if (item is not CollectionFolder collectionFolder)
{
+ // List.Find is more performant than FirstOrDefault due to enumerator allocation
collectionFolder = GetCollectionFolders(item)
- .OfType()
- .FirstOrDefault();
+ .Find(folder => folder is CollectionFolder) as CollectionFolder;
}
return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
@@ -2501,8 +2506,7 @@ namespace Emby.Server.Implementations.Library
///
public bool IsVideoFile(string path)
{
- var resolver = new VideoResolver(GetNamingOptions());
- return resolver.IsVideoFile(path);
+ return VideoResolver.IsVideoFile(path, GetNamingOptions());
}
///
@@ -2517,7 +2521,7 @@ namespace Emby.Server.Implementations.Library
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
{
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)
{
// In other words, no filter applied
@@ -2529,9 +2533,24 @@ namespace Emby.Server.Implementations.Library
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
// TODO nullable - what are we trying to do there with empty episodeInfo?
- var episodeInfo = episode.IsFileProtocol
- ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo(episode.Path)
- : new Naming.TV.EpisodeInfo(episode.Path);
+ EpisodeInfo episodeInfo = null;
+ if (episode.IsFileProtocol)
+ {
+ episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming);
+ // Resolve from parent folder if it's not the Season folder
+ var parent = episode.GetParent();
+ if (episodeInfo == null && parent.GetType() == typeof(Folder))
+ {
+ episodeInfo = resolver.Resolve(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
{
@@ -2666,6 +2685,7 @@ namespace Emby.Server.Implementations.Library
return changed;
}
+ ///
public NamingOptions GetNamingOptions()
{
if (_namingOptions == null)
@@ -2679,13 +2699,12 @@ namespace Emby.Server.Implementations.Library
public ItemLookupInfo ParseName(string name)
{
- var resolver = new VideoResolver(GetNamingOptions());
-
- var result = resolver.CleanDateTime(name);
+ var namingOptions = GetNamingOptions();
+ var result = VideoResolver.CleanDateTime(name, namingOptions);
return new ItemLookupInfo
{
- Name = resolver.TryCleanString(result.Name, out var newName) ? newName.ToString() : result.Name,
+ Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name,
Year = result.Year
};
}
@@ -2699,9 +2718,7 @@ namespace Emby.Server.Implementations.Library
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
- var videoListResolver = new VideoListResolver(namingOptions);
-
- var videos = videoListResolver.Resolve(fileSystemChildren);
+ var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
@@ -2745,9 +2762,7 @@ namespace Emby.Server.Implementations.Library
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
- var videoListResolver = new VideoListResolver(namingOptions);
-
- var videos = videoListResolver.Resolve(fileSystemChildren);
+ var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
@@ -2880,6 +2895,12 @@ namespace Emby.Server.Implementations.Library
}
public void UpdatePeople(BaseItem item, List people)
+ {
+ UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult();
+ }
+
+ ///
+ public async Task UpdatePeopleAsync(BaseItem item, List people, CancellationToken cancellationToken)
{
if (!item.SupportsPeople)
{
@@ -2887,6 +2908,8 @@ namespace Emby.Server.Implementations.Library
}
_itemRepository.UpdatePeople(item.Id, people);
+
+ await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false);
}
public async Task ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
@@ -2990,6 +3013,58 @@ namespace Emby.Server.Implementations.Library
}
}
+ private async Task SavePeopleMetadataAsync(IEnumerable people, CancellationToken cancellationToken)
+ {
+ var personsToSave = new List();
+
+ 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()
{
Task.Run(() =>
@@ -2999,9 +3074,9 @@ namespace Emby.Server.Implementations.Library
});
}
- public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
+ public void AddMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
{
- AddMediaPathInternal(virtualFolderName, pathInfo, true);
+ AddMediaPathInternal(virtualFolderName, mediaPath, true);
}
private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions)
@@ -3054,11 +3129,11 @@ namespace Emby.Server.Implementations.Library
}
}
- public void UpdateMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
+ public void UpdateMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
{
- if (pathInfo == null)
+ if (mediaPath == null)
{
- throw new ArgumentNullException(nameof(pathInfo));
+ throw new ArgumentNullException(nameof(mediaPath));
}
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
@@ -3071,9 +3146,9 @@ namespace Emby.Server.Implementations.Library
var list = libraryOptions.PathInfos.ToList();
foreach (var originalPathInfo in list)
{
- if (string.Equals(pathInfo.Path, originalPathInfo.Path, StringComparison.Ordinal))
+ if (string.Equals(mediaPath.Path, originalPathInfo.Path, StringComparison.Ordinal))
{
- originalPathInfo.NetworkPath = pathInfo.NetworkPath;
+ originalPathInfo.NetworkPath = mediaPath.NetworkPath;
break;
}
}
@@ -3096,10 +3171,7 @@ namespace Emby.Server.Implementations.Library
{
if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal)))
{
- list.Add(new MediaPathInfo
- {
- Path = location
- });
+ list.Add(new MediaPathInfo(location));
}
}
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index c2951dd155..8062691827 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -10,7 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index d0b85f07d4..91c9e61cf3 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -13,7 +15,7 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@@ -350,7 +352,7 @@ namespace Emby.Server.Implementations.Library
private string[] NormalizeLanguage(string language)
{
- if (language == null)
+ if (string.IsNullOrEmpty(language))
{
return Array.Empty();
}
@@ -379,8 +381,7 @@ namespace Emby.Server.Implementations.Library
}
}
- var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
- ? Array.Empty() : NormalizeLanguage(user.SubtitleLanguagePreference);
+ var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference);
var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null
@@ -409,9 +410,7 @@ namespace Emby.Server.Implementations.Library
}
}
- var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference)
- ? Array.Empty()
- : NormalizeLanguage(user.AudioLanguagePreference);
+ var preferredAudio = NormalizeLanguage(user.AudioLanguagePreference);
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
}
@@ -590,18 +589,9 @@ namespace Emby.Server.Implementations.Library
public Task GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
{
- var info = _openStreams.Values.FirstOrDefault(i =>
- {
- var liveStream = i as ILiveStream;
- if (liveStream != null)
- {
- return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
- }
+ var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
- return false;
- });
-
- return Task.FromResult(info as IDirectStreamProvider);
+ return Task.FromResult(info.Value as IDirectStreamProvider);
}
public async Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
index 28fa062396..b833122ea0 100644
--- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs
+++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index 658c53f288..e2f1fb0ade 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -34,9 +36,10 @@ namespace Emby.Server.Implementations.Library
return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList();
}
- public List GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions)
+ ///
+ public List GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions)
{
- return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
+ return GetInstantMixFromGenres(artist.Genres, user, dtoOptions);
}
public List GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions)
@@ -100,8 +103,7 @@ namespace Emby.Server.Implementations.Library
public List GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
{
- var genre = item as MusicGenre;
- if (genre != null)
+ if (item is MusicGenre genre)
{
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
}
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 770cf6bb02..86b8039fab 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System;
using System.Diagnostics.CodeAnalysis;
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
// 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))
+ if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ if (path.Length > subPath.Length
+ && !oldSubPathEndsWithSeparator
+ && path[subPath.Length] != newDirectorySeparatorChar)
{
return false;
}
diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs
index 4e4cac75bf..ac75e5d3a1 100644
--- a/Emby.Server.Implementations/Library/ResolverHelper.cs
+++ b/Emby.Server.Implementations/Library/ResolverHelper.cs
@@ -18,11 +18,10 @@ namespace Emby.Server.Implementations.Library
///
/// The item.
/// The parent.
- /// The file system.
/// The library manager.
/// The directory service.
- /// Item must have a path
- public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService)
+ /// Item must have a path.
+ 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
if (string.IsNullOrEmpty(item.Path))
@@ -43,9 +42,14 @@ namespace Emby.Server.Implementations.Library
// Make sure DateCreated and DateModified have values
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);
}
///
@@ -72,9 +76,9 @@ namespace Emby.Server.Implementations.Library
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
// 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);
// Make sure DateCreated and DateModified have values
@@ -84,28 +88,15 @@ namespace Emby.Server.Implementations.Library
///
/// Ensures the name.
///
- 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 (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 = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory);
+ item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name);
}
}
- ///
- /// Gets the display name.
- ///
- /// The path.
- /// if set to true [is directory].
- /// System.String.
- private static string GetDisplayName(string path, bool isDirectory)
- {
- return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path);
- }
-
///
/// Ensures DateCreated and DateModified have values.
///
@@ -114,21 +105,6 @@ namespace Emby.Server.Implementations.Library
/// The 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
if (!fileSystem.AreEqual(args.Path, item.Path))
{
@@ -136,7 +112,7 @@ namespace Emby.Server.Implementations.Library
if (childData != null)
{
- SetDateCreated(item, fileSystem, childData);
+ SetDateCreated(item, childData);
}
else
{
@@ -144,17 +120,17 @@ namespace Emby.Server.Implementations.Library
if (fileData.Exists)
{
- SetDateCreated(item, fileSystem, fileData);
+ SetDateCreated(item, fileData);
}
}
}
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();
@@ -163,7 +139,7 @@ namespace Emby.Server.Implementations.Library
// directoryService.getFile may return null
if (info != null)
{
- var dateCreated = fileSystem.GetCreationTimeUtc(info);
+ var dateCreated = info.CreationTimeUtc;
if (dateCreated.Equals(DateTime.MinValue))
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index 4ad84579d8..fd9747b4b7 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -19,11 +21,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
///
public class AudioResolver : ItemResolver, IMultiItemResolver
{
- private readonly ILibraryManager LibraryManager;
+ private readonly ILibraryManager _libraryManager;
public AudioResolver(ILibraryManager libraryManager)
{
- LibraryManager = libraryManager;
+ _libraryManager = libraryManager;
}
///
@@ -86,13 +88,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
var files = args.FileSystemChildren
- .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+ .Where(i => !_libraryManager.IgnoreFile(i, args.Parent))
.ToList();
return FindAudio(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
- if (LibraryManager.IsAudioFile(args.Path))
+ if (_libraryManager.IsAudioFile(args.Path))
{
var extension = Path.GetExtension(args.Path);
@@ -105,7 +107,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
// For conflicting extensions, give priority to videos
- if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path))
+ if (isMixedCollectionType && _libraryManager.IsVideoFile(args.Path))
{
return null;
}
@@ -180,7 +182,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
}
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
+ var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var resolver = new AudioBookListResolver(namingOptions);
var resolverResult = resolver.Resolve(files).ToList();
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index bf32381ebf..8e1eccb10a 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index 60f82806fb..3d2ae95d24 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.Linq;
using System.Threading.Tasks;
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index 6e688693be..b102b86cfb 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -1,8 +1,11 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
using System.IO;
using System.Linq;
+using DiscUtils.Udf;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -14,17 +17,17 @@ namespace Emby.Server.Implementations.Library.Resolvers
///
/// Resolves a Path into a Video or Video subclass.
///
- ///
+ /// The type of item to resolve.
public abstract class BaseVideoResolver : MediaBrowser.Controller.Resolvers.ItemResolver
where T : Video, new()
{
- protected readonly ILibraryManager LibraryManager;
-
protected BaseVideoResolver(ILibraryManager libraryManager)
{
LibraryManager = libraryManager;
}
+ protected ILibraryManager LibraryManager { get; }
+
///
/// Resolves the specified args.
///
@@ -45,11 +48,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected virtual TVideoType ResolveVideo(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new()
{
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
+ var namingOptions = LibraryManager.GetNamingOptions();
// If the path is a file check for a matching extensions
- var parser = new VideoResolver(namingOptions);
-
if (args.IsDirectory)
{
TVideoType video = null;
@@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
{
- videoInfo = parser.ResolveDirectory(args.Path);
+ videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
@@ -80,9 +81,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
break;
}
- if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService))
+ if (IsBluRayDirectory(filename))
{
- videoInfo = parser.ResolveDirectory(args.Path);
+ videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
@@ -100,7 +101,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
else if (IsDvdFile(filename))
{
- videoInfo = parser.ResolveDirectory(args.Path);
+ videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
@@ -130,7 +131,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
else
{
- var videoInfo = parser.Resolve(args.Path, false, false);
+ var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false);
if (videoInfo == null)
{
@@ -165,13 +166,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
{
- var extension = Path.GetExtension(video.Path);
- video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ?
- VideoType.Iso :
- VideoType.VideoFile;
+ var extension = Path.GetExtension(video.Path.AsSpan());
+ video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase)
+ || extension.Equals(".img", StringComparison.OrdinalIgnoreCase)
+ ? VideoType.Iso
+ : VideoType.VideoFile;
- video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase);
+ video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase);
video.IsPlaceHolder = videoInfo.IsStub;
if (videoInfo.IsStub)
@@ -193,14 +194,30 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
if (video.VideoType == VideoType.Iso)
{
- if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1)
+ if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase))
{
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;
}
+ else
+ {
+ // use disc-utils, both DVDs and BDs use UDF filesystem
+ using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
+ {
+ UdfReader udfReader = new UdfReader(videoFileStream);
+ if (udfReader.DirectoryExists("VIDEO_TS"))
+ {
+ video.IsoType = IsoType.Dvd;
+ }
+ else if (udfReader.DirectoryExists("BDMV"))
+ {
+ video.IsoType = IsoType.BluRay;
+ }
+ }
+ }
}
}
@@ -250,10 +267,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void Set3DFormat(Video video)
{
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
-
- var resolver = new Format3DParser(namingOptions);
- var result = resolver.Parse(video.Path);
+ var result = Format3DParser.Parse(video.Path, LibraryManager.GetNamingOptions());
Set3DFormat(video, result.Is3D, result.Format3D);
}
@@ -282,25 +296,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
///
- /// Determines whether [is blu ray directory] [the specified directory name].
+ /// Determines whether [is bluray directory] [the specified directory name].
///
- protected bool IsBluRayDirectory(string fullPath, string directoryName, IDirectoryService directoryService)
+ /// The directory name.
+ /// Whether the directory is a bluray directory.
+ protected bool IsBluRayDirectory(string directoryName)
{
- if (!string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
-
- return true;
- // var blurayExtensions = new[]
- //{
- // ".mts",
- // ".m2ts",
- // ".bdmv",
- // ".mpls"
- //};
-
- // return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
+ return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index 0525c7e307..68076730b3 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs
index 7dbce7a6ef..7aaee017dd 100644
--- a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
similarity index 95%
rename from Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
rename to Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
index 62268fce90..9599faea4b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using MediaBrowser.Controller.Entities;
diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
index 9ca76095b2..3f29ab191f 100644
--- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
@@ -7,10 +9,16 @@ namespace Emby.Server.Implementations.Library.Resolvers
///
/// Class ItemResolver.
///
- ///
+ /// The type of BaseItem.
public abstract class ItemResolver : IItemResolver
where T : BaseItem, new()
{
+ ///
+ /// Gets the priority.
+ ///
+ /// The priority.
+ public virtual ResolverPriority Priority => ResolverPriority.First;
+
///
/// Resolves the specified args.
///
@@ -21,12 +29,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
- ///
- /// Gets the priority.
- ///
- /// The priority.
- public virtual ResolverPriority Priority => ResolverPriority.First;
-
///
/// Sets initial values on the newly resolved item.
///
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
index 295e9e120b..69d71d0d9a 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.IO;
using MediaBrowser.Controller.Entities;
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 714bc3a84d..8b55a77449 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -1,9 +1,12 @@
+#nullable disable
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Video;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -255,10 +258,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
}
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
+ var namingOptions = LibraryManager.GetNamingOptions();
- var resolver = new VideoListResolver(namingOptions);
- var resolverResult = resolver.Resolve(files, suppportMultiEditions).ToList();
+ var resolverResult = VideoListResolver.Resolve(files, namingOptions, suppportMultiEditions).ToList();
var result = new MultiItemResolverResult
{
@@ -376,7 +378,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
var multiDiscFolders = new List();
- var libraryOptions = args.GetLibraryOptions();
+ var libraryOptions = args.LibraryOptions;
var supportPhotos = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && libraryOptions.EnablePhotos;
var photos = new List();
@@ -398,7 +400,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return movie;
}
- if (IsBluRayDirectory(child.FullName, filename, directoryService))
+ if (IsBluRayDirectory(filename))
{
var movie = new T
{
@@ -479,7 +481,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return true;
}
- if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService)))
+ if (subfolders.Any(s => IsBluRayDirectory(s.Name)))
{
videoTypes.Add(VideoType.BluRay);
return true;
@@ -535,7 +537,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return returnVideo;
}
- private bool IsInvalid(Folder parent, string collectionType)
+ private bool IsInvalid(Folder parent, ReadOnlySpan collectionType)
{
if (parent != null)
{
@@ -545,12 +547,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
}
- if (string.IsNullOrEmpty(collectionType))
+ if (collectionType.IsEmpty)
{
return false;
}
- return !_validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
+ return !_validCollectionTypes.Contains(collectionType, StringComparison.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
index 3ac837057a..534bc80ddc 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
@@ -13,7 +15,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
public class PhotoAlbumResolver : FolderResolver
{
private readonly IImageProcessor _imageProcessor;
- private ILibraryManager _libraryManager;
+ private readonly ILibraryManager _libraryManager;
///
/// Initializes a new instance of the class.
@@ -26,6 +28,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
_libraryManager = libraryManager;
}
+ ///
+ public override ResolverPriority Priority => ResolverPriority.Second;
+
///
/// Resolves the specified args.
///
@@ -39,8 +44,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Must be an image file within a photo collection
var collectionType = args.GetCollectionType();
- if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
- (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos))
+ if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos))
{
if (HasPhotos(args))
{
@@ -84,8 +89,5 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false;
}
-
- ///
- public override ResolverPriority Priority => ResolverPriority.Second;
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index bcfcee9c6d..57bf40e9ec 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -47,7 +49,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
var collectionType = args.CollectionType;
if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
- || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos))
+ || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos))
{
if (IsImageFile(args.Path, _imageProcessor))
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
index 5f051321f6..2c4ead7198 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -16,7 +18,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
///
public class PlaylistResolver : FolderResolver
{
- private string[] _musicPlaylistCollectionTypes = new string[] {
+ private string[] _musicPlaylistCollectionTypes =
+ {
string.Empty,
CollectionType.Music
};
diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
index 99f3041909..7b4e143348 100644
--- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index 9b4cd7a3df..d6ae910565 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.Linq;
using MediaBrowser.Controller.Entities;
@@ -35,14 +37,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
- var season = parent as Season;
-
// Just in case the user decided to nest episodes.
// Not officially supported but in some cases we can handle it.
- if (season == null)
- {
- season = parent.GetParents().OfType().FirstOrDefault();
- }
+
+ var season = parent as Season ?? parent.GetParents().OfType().FirstOrDefault();
// 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
@@ -55,11 +53,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
if (episode != null)
{
- var series = parent as Series;
- if (series == null)
- {
- series = parent.GetParents().OfType().FirstOrDefault();
- }
+ var series = parent as Series ?? parent.GetParents().OfType().FirstOrDefault();
if (series != null)
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index 3332e1806c..7d707df182 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System.Globalization;
using Emby.Naming.TV;
using MediaBrowser.Controller.Entities.TV;
@@ -88,7 +90,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("NameSeasonNumber"),
seasonNumber,
- args.GetLibraryOptions().PreferredMetadataLanguage);
+ args.LibraryOptions.PreferredMetadataLanguage);
}
return season;
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 4a9d2cf8ca..a1562abd31 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -19,19 +21,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
///
public class SeriesResolver : FolderResolver
{
- private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
///
/// Initializes a new instance of the class.
///
- /// The file system.
/// The logger.
/// The library manager.
- public SeriesResolver(IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager)
+ public SeriesResolver(ILogger logger, ILibraryManager libraryManager)
{
- _fileSystem = fileSystem;
_logger = logger;
_libraryManager = libraryManager;
}
@@ -59,15 +58,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
- // if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
- //{
- // return new Series
- // {
- // Path = args.Path,
- // Name = Path.GetFileName(args.Path)
- // };
- //}
-
var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path);
if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
@@ -100,7 +90,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
- if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, false))
+ if (IsSeriesFolder(args.Path, args.FileSystemChildren, _logger, _libraryManager, false))
{
return new Series
{
@@ -117,8 +107,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
public static bool IsSeriesFolder(
string path,
IEnumerable fileSystemChildren,
- IDirectoryService directoryService,
- IFileSystem fileSystem,
ILogger logger,
ILibraryManager libraryManager,
bool isTvContentType)
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index bcdf854ca3..9d0a24a88a 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -1,14 +1,16 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Linq;
+using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index 827e3c64b3..c4e230f211 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -175,6 +177,7 @@ namespace Emby.Server.Implementations.Library
return dto;
}
+ ///
public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions options)
{
var userData = GetUserData(user, item);
@@ -189,7 +192,7 @@ namespace Emby.Server.Implementations.Library
///
/// The data.
/// DtoUserItemData.
- ///
+ /// is null.
private UserItemDataDto GetUserItemDataDto(UserItemData data)
{
if (data == null)
@@ -210,6 +213,7 @@ namespace Emby.Server.Implementations.Library
};
}
+ ///
public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks)
{
var playedToCompletion = false;
@@ -220,7 +224,7 @@ namespace Emby.Server.Implementations.Library
var hasRuntime = runtimeTicks > 0;
// If a position has been reported, and if we know the duration
- if (positionTicks > 0 && hasRuntime && !(item is AudioBook))
+ if (positionTicks > 0 && hasRuntime && item is not AudioBook && item is not Book)
{
var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100;
@@ -239,7 +243,7 @@ namespace Emby.Server.Implementations.Library
{
// Enforce MinResumeDuration
var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds;
- if (durationSeconds < _config.Configuration.MinResumeDurationSeconds && !(item is Book))
+ if (durationSeconds < _config.Configuration.MinResumeDurationSeconds)
{
positionTicks = 0;
data.Played = playedToCompletion = true;
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index ac041bcf6c..e2da672a3c 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
index 9a8c5f39d5..8577d722e0 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -87,12 +87,15 @@ namespace Emby.Server.Implementations.Library.Validators
foreach (var item in deadEntities)
{
- _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
+ _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
- _libraryManager.DeleteItem(item, new DeleteOptions
- {
- DeleteFileLocation = false
- }, false);
+ _libraryManager.DeleteItem(
+ item,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false
+ },
+ false);
}
progress.Report(100);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 7a6b1d8b61..bb3d635d12 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return targetFile;
}
- public Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ public Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
if (directStreamProvider != null)
{
@@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
- Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
+ Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Opened recording stream from tuner provider");
- Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
+ Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index c580a6ebfb..0205ed9e5d 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -157,8 +159,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
var recordingFolders = GetRecordingFolders().ToArray();
- var virtualFolders = _libraryManager.GetVirtualFolders()
- .ToList();
+ var virtualFolders = _libraryManager.GetVirtualFolders();
var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
@@ -175,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
continue;
}
- var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray();
+ var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo(i)).ToArray();
var libraryOptions = new LibraryOptions
{
@@ -208,7 +209,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var path in pathsToRemove)
{
- await RemovePathFromLibrary(path).ConfigureAwait(false);
+ await RemovePathFromLibraryAsync(path).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -217,13 +218,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private async Task RemovePathFromLibrary(string path)
+ private async Task RemovePathFromLibraryAsync(string path)
{
_logger.LogDebug("Removing path from library: {0}", path);
var requiresRefresh = false;
- var virtualFolders = _libraryManager.GetVirtualFolders()
- .ToList();
+ var virtualFolders = _libraryManager.GetVirtualFolders();
foreach (var virtualFolder in virtualFolders)
{
@@ -458,7 +458,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
{
var tunerChannelId = tunerChannel.TunerChannelId;
- if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1)
+ if (tunerChannelId.Contains(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase))
{
tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
}
@@ -610,16 +610,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new NotImplementedException();
}
- public Task CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
+ public Task CreateTimer(TimerInfo info, CancellationToken cancellationToken)
{
- var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ?
+ var existingTimer = string.IsNullOrWhiteSpace(info.ProgramId) ?
null :
- _timerProvider.GetTimerByProgramId(timer.ProgramId);
+ _timerProvider.GetTimerByProgramId(info.ProgramId);
if (existingTimer != null)
{
- if (existingTimer.Status == RecordingStatus.Cancelled ||
- existingTimer.Status == RecordingStatus.Completed)
+ if (existingTimer.Status == RecordingStatus.Cancelled
+ || existingTimer.Status == RecordingStatus.Completed)
{
existingTimer.Status = RecordingStatus.New;
existingTimer.IsManual = true;
@@ -632,32 +632,32 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- timer.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+ info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
LiveTvProgram programInfo = null;
- if (!string.IsNullOrWhiteSpace(timer.ProgramId))
+ if (!string.IsNullOrWhiteSpace(info.ProgramId))
{
- programInfo = GetProgramInfoFromCache(timer);
+ programInfo = GetProgramInfoFromCache(info);
}
if (programInfo == null)
{
- _logger.LogInformation("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
- programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
+ _logger.LogInformation("Unable to find program with Id {0}. Will search using start date", info.ProgramId);
+ programInfo = GetProgramInfoFromCache(info.ChannelId, info.StartDate);
}
if (programInfo != null)
{
- CopyProgramInfoToTimerInfo(programInfo, timer);
+ CopyProgramInfoToTimerInfo(programInfo, info);
}
- timer.IsManual = true;
- _timerProvider.Add(timer);
+ info.IsManual = true;
+ _timerProvider.Add(info);
- TimerCreated?.Invoke(this, new GenericEventArgs(timer));
+ TimerCreated?.Invoke(this, new GenericEventArgs(info));
- return Task.FromResult(timer.Id);
+ return Task.FromResult(info.Id);
}
public async Task CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
@@ -801,22 +801,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
{
- if (string.IsNullOrWhiteSpace(path))
+ if (string.IsNullOrWhiteSpace(path) || _activeRecordings.IsEmpty)
{
return null;
}
- foreach (var recording in _activeRecordings.Values)
+ foreach (var (_, recordingInfo) in _activeRecordings)
{
- if (string.Equals(recording.Path, path, StringComparison.Ordinal) && !recording.CancellationTokenSource.IsCancellationRequested)
+ if (string.Equals(recordingInfo.Path, path, StringComparison.Ordinal) && !recordingInfo.CancellationTokenSource.IsCancellationRequested)
{
- var timer = recording.Timer;
+ var timer = recordingInfo.Timer;
if (timer.Status != RecordingStatus.InProgress)
{
return null;
}
- return recording;
+ return recordingInfo;
}
}
@@ -911,18 +911,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
- List programs;
-
if (epgChannel == null)
{
_logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
- programs = new List();
+ continue;
}
- else
- {
- programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
+
+ List programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
.ConfigureAwait(false)).ToList();
- }
// Replace the value that came from the provider with a normalized value
foreach (var program in programs)
@@ -938,7 +934,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- return new List();
+ return Enumerable.Empty();
}
private List> GetListingProviders()
@@ -1290,7 +1286,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
- _logger.LogInformation("Writing file to path: " + recordPath);
+ _logger.LogInformation("Writing file to: {Path}", recordPath);
Action onStarted = async () =>
{
@@ -1415,13 +1411,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void TriggerRefresh(string path)
{
- _logger.LogInformation("Triggering refresh on {path}", path);
+ _logger.LogInformation("Triggering refresh on {Path}", path);
var item = GetAffectedBaseItem(Path.GetDirectoryName(path));
if (item != null)
{
- _logger.LogInformation("Refreshing recording parent {path}", item.Path);
+ _logger.LogInformation("Refreshing recording parent {Path}", item.Path);
_providerManager.QueueRefresh(
item.Id,
@@ -1456,7 +1452,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase))
{
var parentItem = item.GetParent();
- if (parentItem != null && !(parentItem is AggregateFolder))
+ if (parentItem != null && parentItem is not AggregateFolder)
{
item = parentItem;
}
@@ -1510,8 +1506,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
DeleteLibraryItemsForTimers(timersToDelete);
- var librarySeries = _libraryManager.FindByPath(seriesPath, true) as Folder;
- if (librarySeries == null)
+ if (_libraryManager.FindByPath(seriesPath, true) is not Folder librarySeries)
{
return;
}
@@ -1621,9 +1616,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
return _activeRecordings
- .Values
- .ToList()
- .Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
+ .Any(i => string.Equals(i.Value.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Value.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
}
private IRecorder GetRecorder(MediaSourceInfo mediaSource)
@@ -1667,7 +1660,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- process.Exited += Process_Exited;
+ process.Exited += OnProcessExited;
process.Start();
}
catch (Exception ex)
@@ -1681,7 +1674,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return arguments.Replace("{path}", path, StringComparison.OrdinalIgnoreCase);
}
- private void Process_Exited(object sender, EventArgs e)
+ private void OnProcessExited(object sender, EventArgs e)
{
using (var process = (Process)sender)
{
@@ -2239,14 +2232,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var enabledTimersForSeries = new List();
foreach (var timer in allTimers)
{
- var existingTimer = _timerProvider.GetTimer(timer.Id);
-
- if (existingTimer == null)
- {
- existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId)
+ var existingTimer = _timerProvider.GetTimer(timer.Id)
+ ?? (string.IsNullOrWhiteSpace(timer.ProgramId)
? null
- : _timerProvider.GetTimerByProgramId(timer.ProgramId);
- }
+ : _timerProvider.GetTimerByProgramId(timer.ProgramId));
if (existingTimer == null)
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 44a8cdee49..e10bc76470 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -9,8 +11,9 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
@@ -307,22 +310,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
using (var reader = new StreamReader(source))
{
- while (!reader.EndOfStream)
+ await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
- var line = await reader.ReadLineAsync().ConfigureAwait(false);
-
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
- await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+ await target.WriteAsync(bytes.AsMemory()).ConfigureAwait(false);
await target.FlushAsync().ConfigureAwait(false);
}
}
}
- catch (ObjectDisposedException)
- {
- // TODO Investigate and properly fix.
- // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
- }
catch (Exception ex)
{
_logger.LogError(ex, "Error reading ffmpeg recording log");
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
index 8c27ca76e3..20a8213a77 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
@@ -6,10 +6,8 @@ using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
-
internal class EpgChannelData
{
-
private readonly Dictionary _channelsById;
private readonly Dictionary _channelsByNumber;
@@ -39,13 +37,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- public ChannelInfo GetChannelById(string id)
+ public ChannelInfo? GetChannelById(string id)
=> _channelsById.GetValueOrDefault(id);
- public ChannelInfo GetChannelByNumber(string number)
+ public ChannelInfo? GetChannelByNumber(string number)
=> _channelsByNumber.GetValueOrDefault(number);
- public ChannelInfo GetChannelByName(string name)
+ public ChannelInfo? GetChannelByName(string name)
=> _channelsByName.GetValueOrDefault(name);
public static string NormalizeName(string value)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
index 4712724d67..dfe3517b22 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
///
/// Records the specified media source.
///
- Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
+ Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index 1cac9cb963..4a031e4752 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -5,7 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index 142c59542a..32245f899e 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
- internal class RecordingHelper
+ internal static class RecordingHelper
{
public static DateTime GetStartTime(TimerInfo timer)
{
@@ -70,17 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private static string GetDateString(DateTime date)
{
- date = date.ToLocalTime();
-
- return string.Format(
- CultureInfo.InvariantCulture,
- "{0}_{1}_{2}_{3}_{4}_{5}",
- date.Year.ToString("0000", CultureInfo.InvariantCulture),
- date.Month.ToString("00", CultureInfo.InvariantCulture),
- date.Day.ToString("00", CultureInfo.InvariantCulture),
- date.Hour.ToString("00", CultureInfo.InvariantCulture),
- date.Minute.ToString("00", CultureInfo.InvariantCulture),
- date.Second.ToString("00", CultureInfo.InvariantCulture));
+ return date.ToLocalTime().ToString("yyyy_MM_dd_HH_mm_ss", CultureInfo.InvariantCulture);
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 1efa90e256..a861e6ae44 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
}
- public event EventHandler> TimerFired;
+ public event EventHandler>? TimerFired;
public void RestartTimers()
{
@@ -143,9 +143,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private void TimerCallback(object state)
+ private void TimerCallback(object? state)
{
- var timerId = (string)state;
+ var timerId = (string?)state ?? throw new ArgumentNullException(nameof(state));
var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
if (timer != null)
@@ -154,12 +154,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- public TimerInfo GetTimer(string id)
+ public TimerInfo? GetTimer(string id)
{
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
}
- public TimerInfo GetTimerByProgramId(string programId)
+ public TimerInfo? GetTimerByProgramId(string programId)
{
return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 1926e738f2..8125ed57df 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -12,8 +14,9 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common;
-using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Cryptography;
@@ -94,12 +97,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var dates = GetScheduleRequestDates(startDateUtc, endDateUtc);
_logger.LogInformation("Channel Station ID is: {ChannelID}", channelId);
- var requestList = new List()
+ var requestList = new List()
{
- new ScheduleDirect.RequestScheduleForChannel()
+ new RequestScheduleForChannelDto()
{
- stationID = channelId,
- date = dates
+ StationId = channelId,
+ Date = dates
}
};
@@ -111,61 +114,61 @@ namespace Emby.Server.Implementations.LiveTv.Listings
options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var dailySchedules = await JsonSerializer.DeserializeAsync>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var dailySchedules = await JsonSerializer.DeserializeAsync>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
programRequestOptions.Headers.TryAddWithoutValidation("token", token);
- var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
+ var programsID = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var programDetails = await JsonSerializer.DeserializeAsync>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- var programDict = programDetails.ToDictionary(p => p.programID, y => y);
+ var programDetails = await JsonSerializer.DeserializeAsync>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y);
var programIdsWithImages = programDetails
- .Where(p => p.hasImageArtwork).Select(p => p.programID)
+ .Where(p => p.HasImageArtwork).Select(p => p.ProgramId)
.ToList();
var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
var programsInfo = new List();
- foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
+ foreach (ProgramDto schedule in dailySchedules.SelectMany(d => d.Programs))
{
// _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
// " which corresponds to channel " + channelNumber + " and program id " +
- // schedule.programID + " which says it has images? " +
- // programDict[schedule.programID].hasImageArtwork);
+ // schedule.ProgramId + " which says it has images? " +
+ // programDict[schedule.ProgramId].hasImageArtwork);
if (images != null)
{
- var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
+ var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]);
if (imageIndex > -1)
{
- var programEntry = programDict[schedule.programID];
+ var programEntry = programDict[schedule.ProgramId];
- var allImages = images[imageIndex].data ?? new List();
- var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase));
- var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase));
+ var allImages = images[imageIndex].Data ?? new List();
+ var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase));
+ var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase));
const double DesiredAspect = 2.0 / 3;
- programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
+ programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
GetProgramImage(ApiUrl, allImages, true, DesiredAspect);
const double WideAspect = 16.0 / 9;
- programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
+ programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
// Don't supply the same image twice
- if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal))
+ if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
{
- programEntry.thumbImage = null;
+ programEntry.ThumbImage = null;
}
- programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
+ programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
@@ -174,15 +177,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID]));
+ programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.ProgramId]));
}
return programsInfo;
}
- private static int GetSizeOrder(ScheduleDirect.ImageData image)
+ private static int GetSizeOrder(ImageDataDto image)
{
- if (int.TryParse(image.height, out int value))
+ if (int.TryParse(image.Height, out int value))
{
return value;
}
@@ -190,53 +193,53 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return 0;
}
- private static string GetChannelNumber(ScheduleDirect.Map map)
+ private static string GetChannelNumber(MapDto map)
{
- var channelNumber = map.logicalChannelNumber;
+ var channelNumber = map.LogicalChannelNumber;
if (string.IsNullOrWhiteSpace(channelNumber))
{
- channelNumber = map.channel;
+ channelNumber = map.Channel;
}
if (string.IsNullOrWhiteSpace(channelNumber))
{
- channelNumber = map.atscMajor + "." + map.atscMinor;
+ channelNumber = map.AtscMajor + "." + map.AtscMinor;
}
return channelNumber.TrimStart('0');
}
- private static bool IsMovie(ScheduleDirect.ProgramDetails programInfo)
+ private static bool IsMovie(ProgramDetailsDto programInfo)
{
- return string.Equals(programInfo.entityType, "movie", StringComparison.OrdinalIgnoreCase);
+ return string.Equals(programInfo.EntityType, "movie", StringComparison.OrdinalIgnoreCase);
}
- private ProgramInfo GetProgram(string channelId, ScheduleDirect.Program programInfo, ScheduleDirect.ProgramDetails details)
+ private ProgramInfo GetProgram(string channelId, ProgramDto programInfo, ProgramDetailsDto details)
{
- var startAt = GetDate(programInfo.airDateTime);
- var endAt = startAt.AddSeconds(programInfo.duration);
+ var startAt = GetDate(programInfo.AirDateTime);
+ var endAt = startAt.AddSeconds(programInfo.Duration);
var audioType = ProgramAudio.Stereo;
- var programId = programInfo.programID ?? string.Empty;
+ var programId = programInfo.ProgramId ?? string.Empty;
string newID = programId + "T" + startAt.Ticks + "C" + channelId;
- if (programInfo.audioProperties != null)
+ if (programInfo.AudioProperties != null)
{
- if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
+ if (programInfo.AudioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.Atmos;
}
- else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
+ else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.DolbyDigital;
}
- else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase)))
+ else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.DolbyDigital;
}
- else if (programInfo.audioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase)))
+ else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.Stereo;
}
@@ -247,9 +250,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
string episodeTitle = null;
- if (details.episodeTitle150 != null)
+ if (details.EpisodeTitle150 != null)
{
- episodeTitle = details.episodeTitle150;
+ episodeTitle = details.EpisodeTitle150;
}
var info = new ProgramInfo
@@ -258,22 +261,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Id = newID,
StartDate = startAt,
EndDate = endAt,
- Name = details.titles[0].title120 ?? "Unknown",
+ Name = details.Titles[0].Title120 ?? "Unknown",
OfficialRating = null,
CommunityRating = null,
EpisodeTitle = episodeTitle,
Audio = audioType,
// IsNew = programInfo.@new ?? false,
- IsRepeat = programInfo.@new == null,
- IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase),
- ImageUrl = details.primaryImage,
- ThumbImageUrl = details.thumbImage,
- IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
- IsSports = string.Equals(details.entityType, "sports", StringComparison.OrdinalIgnoreCase),
+ IsRepeat = programInfo.New == null,
+ IsSeries = string.Equals(details.EntityType, "episode", StringComparison.OrdinalIgnoreCase),
+ ImageUrl = details.PrimaryImage,
+ ThumbImageUrl = details.ThumbImage,
+ IsKids = string.Equals(details.Audience, "children", StringComparison.OrdinalIgnoreCase),
+ IsSports = string.Equals(details.EntityType, "sports", StringComparison.OrdinalIgnoreCase),
IsMovie = IsMovie(details),
- Etag = programInfo.md5,
- IsLive = string.Equals(programInfo.liveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
- IsPremiere = programInfo.premiere || (programInfo.isPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
+ Etag = programInfo.Md5,
+ IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
+ IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
};
var showId = programId;
@@ -296,15 +299,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
info.ShowId = showId;
- if (programInfo.videoProperties != null)
+ if (programInfo.VideoProperties != null)
{
- info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
- info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
+ info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
+ info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
}
- if (details.contentRating != null && details.contentRating.Count > 0)
+ if (details.ContentRating != null && details.ContentRating.Count > 0)
{
- info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-", StringComparison.Ordinal)
+ info.OfficialRating = details.ContentRating[0].Code.Replace("TV", "TV-", StringComparison.Ordinal)
.Replace("--", "-", StringComparison.Ordinal);
var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
@@ -314,15 +317,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- if (details.descriptions != null)
+ if (details.Descriptions != null)
{
- if (details.descriptions.description1000 != null && details.descriptions.description1000.Count > 0)
+ if (details.Descriptions.Description1000 != null && details.Descriptions.Description1000.Count > 0)
{
- info.Overview = details.descriptions.description1000[0].description;
+ info.Overview = details.Descriptions.Description1000[0].Description;
}
- else if (details.descriptions.description100 != null && details.descriptions.description100.Count > 0)
+ else if (details.Descriptions.Description100 != null && details.Descriptions.Description100.Count > 0)
{
- info.Overview = details.descriptions.description100[0].description;
+ info.Overview = details.Descriptions.Description100[0].Description;
}
}
@@ -332,18 +335,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
info.SeriesProviderIds[MetadataProvider.Zap2It.ToString()] = info.SeriesId;
- if (details.metadata != null)
+ if (details.Metadata != null)
{
- foreach (var metadataProgram in details.metadata)
+ foreach (var metadataProgram in details.Metadata)
{
var gracenote = metadataProgram.Gracenote;
if (gracenote != null)
{
- info.SeasonNumber = gracenote.season;
+ info.SeasonNumber = gracenote.Season;
- if (gracenote.episode > 0)
+ if (gracenote.Episode > 0)
{
- info.EpisodeNumber = gracenote.episode;
+ info.EpisodeNumber = gracenote.Episode;
}
break;
@@ -352,25 +355,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- if (!string.IsNullOrWhiteSpace(details.originalAirDate))
+ if (!string.IsNullOrWhiteSpace(details.OriginalAirDate))
{
- info.OriginalAirDate = DateTime.Parse(details.originalAirDate, CultureInfo.InvariantCulture);
+ info.OriginalAirDate = DateTime.Parse(details.OriginalAirDate, CultureInfo.InvariantCulture);
info.ProductionYear = info.OriginalAirDate.Value.Year;
}
- if (details.movie != null)
+ if (details.Movie != null)
{
- if (!string.IsNullOrEmpty(details.movie.year)
- && int.TryParse(details.movie.year, out int year))
+ if (!string.IsNullOrEmpty(details.Movie.Year)
+ && int.TryParse(details.Movie.Year, out int year))
{
info.ProductionYear = year;
}
}
- if (details.genres != null)
+ if (details.Genres != null)
{
- info.Genres = details.genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
- info.IsNews = details.genres.Contains("news", StringComparer.OrdinalIgnoreCase);
+ info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
+ info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase);
if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase))
{
@@ -393,11 +396,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return date;
}
- private string GetProgramImage(string apiUrl, IEnumerable images, bool returnDefaultImage, double desiredAspect)
+ private string GetProgramImage(string apiUrl, IEnumerable images, bool returnDefaultImage, double desiredAspect)
{
var match = images
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
- .ThenByDescending(GetSizeOrder)
+ .ThenByDescending(i => GetSizeOrder(i))
.FirstOrDefault();
if (match == null)
@@ -405,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return null;
}
- var uri = match.uri;
+ var uri = match.Uri;
if (string.IsNullOrWhiteSpace(uri))
{
@@ -421,19 +424,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- private static double GetAspectRatio(ScheduleDirect.ImageData i)
+ private static double GetAspectRatio(ImageDataDto i)
{
int width = 0;
int height = 0;
- if (!string.IsNullOrWhiteSpace(i.width))
+ if (!string.IsNullOrWhiteSpace(i.Width))
{
- int.TryParse(i.width, out width);
+ _ = int.TryParse(i.Width, out width);
}
- if (!string.IsNullOrWhiteSpace(i.height))
+ if (!string.IsNullOrWhiteSpace(i.Height))
{
- int.TryParse(i.height, out height);
+ _ = int.TryParse(i.Height, out height);
}
if (height == 0 || width == 0)
@@ -446,14 +449,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return result;
}
- private async Task> GetImageForPrograms(
+ private async Task> GetImageForPrograms(
ListingsProviderInfo info,
IReadOnlyList programIds,
CancellationToken cancellationToken)
{
if (programIds.Count == 0)
{
- return new List();
+ return new List();
}
StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
@@ -477,13 +480,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ return await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting image info from schedules direct");
- return new List();
+ return new List();
}
}
@@ -506,18 +509,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (root != null)
{
- foreach (ScheduleDirect.Headends headend in root)
+ foreach (HeadendsDto headend in root)
{
- foreach (ScheduleDirect.Lineup lineup in headend.lineups)
+ foreach (LineupDto lineup in headend.Lineups)
{
lineups.Add(new NameIdPair
{
- Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name,
- Id = lineup.uri.Substring(18)
+ Name = string.IsNullOrWhiteSpace(lineup.Name) ? lineup.Lineup : lineup.Name,
+ Id = lineup.Uri[18..]
});
}
}
@@ -647,14 +650,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- if (string.Equals(root.message, "OK", StringComparison.Ordinal))
+ var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ if (string.Equals(root.Message, "OK", StringComparison.Ordinal))
{
- _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
- return root.token;
+ _logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token);
+ return root.Token;
}
- throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
+ throw new Exception("Could not authenticate with Schedules Direct Error: " + root.Message);
}
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
@@ -703,9 +706,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
httpResponse.EnsureSuccessStatusCode();
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var response = httpResponse.Content;
- var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
+ return root.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase));
}
catch (HttpRequestException ex)
{
@@ -775,38 +778,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
+ var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.Map.Count);
_logger.LogInformation("Mapping Stations to Channel");
- var allStations = root.stations ?? new List();
+ var allStations = root.Stations ?? new List();
- var map = root.map;
+ var map = root.Map;
var list = new List(map.Count);
foreach (var channel in map)
{
var channelNumber = GetChannelNumber(channel);
- var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase));
- if (station == null)
- {
- station = new ScheduleDirect.Station
+ var station = allStations.Find(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase))
+ ?? new StationDto
{
- stationID = channel.stationID
+ StationId = channel.StationId
};
- }
var channelInfo = new ChannelInfo
{
- Id = station.stationID,
- CallSign = station.callsign,
+ Id = station.StationId,
+ CallSign = station.Callsign,
Number = channelNumber,
- Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name
+ Name = string.IsNullOrWhiteSpace(station.Name) ? channelNumber : station.Name
};
- if (station.logo != null)
+ if (station.Logo != null)
{
- channelInfo.ImageUrl = station.logo.URL;
+ channelInfo.ImageUrl = station.Logo.Url;
}
list.Add(channelInfo);
@@ -819,402 +819,5 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
}
-
- public class ScheduleDirect
- {
- public class Token
- {
- public int code { get; set; }
-
- public string message { get; set; }
-
- public string serverID { get; set; }
-
- public string token { get; set; }
- }
-
- public class Lineup
- {
- public string lineup { get; set; }
-
- public string name { get; set; }
-
- public string transport { get; set; }
-
- public string location { get; set; }
-
- public string uri { get; set; }
- }
-
- public class Lineups
- {
- public int code { get; set; }
-
- public string serverID { get; set; }
-
- public string datetime { get; set; }
-
- public List lineups { get; set; }
- }
-
- public class Headends
- {
- public string headend { get; set; }
-
- public string transport { get; set; }
-
- public string location { get; set; }
-
- public List lineups { get; set; }
- }
-
- public class Map
- {
- public string stationID { get; set; }
-
- public string channel { get; set; }
-
- public string logicalChannelNumber { get; set; }
-
- public int uhfVhf { get; set; }
-
- public int atscMajor { get; set; }
-
- public int atscMinor { get; set; }
- }
-
- public class Broadcaster
- {
- public string city { get; set; }
-
- public string state { get; set; }
-
- public string postalcode { get; set; }
-
- public string country { get; set; }
- }
-
- public class Logo
- {
- public string URL { get; set; }
-
- public int height { get; set; }
-
- public int width { get; set; }
-
- public string md5 { get; set; }
- }
-
- public class Station
- {
- public string stationID { get; set; }
-
- public string name { get; set; }
-
- public string callsign { get; set; }
-
- public List broadcastLanguage { get; set; }
-
- public List descriptionLanguage { get; set; }
-
- public Broadcaster broadcaster { get; set; }
-
- public string affiliate { get; set; }
-
- public Logo logo { get; set; }
-
- public bool? isCommercialFree { get; set; }
- }
-
- public class Metadata
- {
- public string lineup { get; set; }
-
- public string modified { get; set; }
-
- public string transport { get; set; }
- }
-
- public class Channel
- {
- public List