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.yml b/.ci/azure-pipelines.yml index 6430503f9a..c028b6e3e8 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -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/label-commenter-config.yml b/.github/label-commenter-config.yml new file mode 100644 index 0000000000..78b75be434 --- /dev/null +++ b/.github/label-commenter-config.yml @@ -0,0 +1,43 @@ +comment: + header: Hello @{{ issue.user.login }} + footer: "\ + ---\n\n + > This is an automated comment created by the [peaceiris/actions-label-commenter]. \ + Responding to the bot or mentioning it won't have any effect.\n\n + [peaceiris/actions-label-commenter]: https://github.com/peaceiris/actions-label-commenter + " + +labels: + - name: stable backport + labeled: + pr: + body: | + This pull request has been tagged as a stable backport. It will be cherry-picked into the next stable point release. + + Please observe the following: + + * Any dependent PRs that this PR requires **must** be tagged for stable backporting as well. + + * Any issue(s) this PR fixes or closes **should** target the current stable release or a previous stable release to which a fix has not yet entered the current stable release. + + * This PR **must** be test cherry-picked against the current release branch (`release-X.Y.z` where X and Y are numbers). It must apply cleanly, or a diff of the expected change must be provided. + + To do this, run the following commands from your local copy of the Jellyfin repository: + + 1. `git checkout master` + + 1. `git merge --no-ff ` + + 1. `git log` -> `commit xxxxxxxxx`, grab hash + + 1. `git checkout release-X.Y.z` replacing X and Y with the *current* stable version (e.g. `release-10.7.z`) + + 1. `git cherry-pick -sx -m1 ` + + Ensure the `cherry-pick` applies cleanly. If it does not, fix any merge conflicts *preserving as much of the original code as possible*, and make note of the resulting diff. + + Test your changes with a build to ensure they are successful. If not, adjust the diff accordingly. + + **Do not** push your merges to either branch. Use `git reset --hard HEAD~1` to revert both branches to their original state. + + Reply to this PR with a comment beginning "Cherry-pick test completed." and including the merge-conflict-fixing diff(s) if applicable. diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index db34693cc7..de1590c746 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -2,8 +2,6 @@ name: Automation on: pull_request: - issues: - issue_comment: jobs: main: @@ -16,31 +14,31 @@ jobs: label: stable backport - name: Remove from 'Current Release' project - uses: alex-page/github-project-automation-plus@v0.5.1 + uses: alex-page/github-project-automation-plus@v0.7.1 if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel 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.7.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 + uses: alex-page/github-project-automation-plus@v0.7.1 if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel continue-on-error: true with: project: Current Release column: In progress - repo-token: ${{ secrets.GH_TOKEN }} + 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 +46,19 @@ jobs: run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)" - name: Move issue to needs triage - uses: alex-page/github-project-automation-plus@v0.5.1 + uses: alex-page/github-project-automation-plus@v0.7.1 if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1 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.7.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/label-commenter.yml b/.github/workflows/label-commenter.yml new file mode 100644 index 0000000000..be9216cc19 --- /dev/null +++ b/.github/workflows/label-commenter.yml @@ -0,0 +1,22 @@ +name: Label Commenter + +on: + issues: + types: + - labeled + - unlabeled + pull_request_target: + types: + - labeled + - unlabeled + +jobs: + comment: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + with: + ref: master + + - name: Label Commenter + uses: peaceiris/actions-label-commenter@v1 diff --git a/.github/workflows/merge-conflicts.yml b/.github/workflows/merge-conflicts.yml index ce808617a1..1b04eab467 100644 --- a/.github/workflows/merge-conflicts.yml +++ b/.github/workflows/merge-conflicts.yml @@ -14,4 +14,4 @@ jobs: - uses: eps1lon/actions-label-merge-conflict@v2.0.1 with: dirtyLabel: 'merge conflict' - repoToken: ${{ secrets.GH_TOKEN }} + repoToken: ${{ secrets.JF_BOT_TOKEN }} diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 3172ec0d9f..3eec9fa033 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -11,17 +11,17 @@ jobs: - name: Notify as seen uses: peter-evans/create-or-update-comment@v1.4.5 with: - token: ${{ secrets.GH_TOKEN }} + 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.GH_TOKEN }} + token: ${{ secrets.JF_BOT_TOKEN }} fetch-depth: 0 - name: Automatic Rebase uses: cirrus-actions/rebase@1.4 env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }} diff --git a/Dockerfile b/Dockerfile index ebe5eb00c6..4e2d06b82a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ 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 diff --git a/Dockerfile.arm b/Dockerfile.arm index d63dbee758..25a0de7db6 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -5,12 +5,12 @@ 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 diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index e95999f2a6..c9f19c5a39 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -5,12 +5,12 @@ 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 diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs index e63a858605..5ceeb55300 100644 --- a/Emby.Dlna/Configuration/DlnaOptions.cs +++ b/Emby.Dlna/Configuration/DlnaOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace Emby.Dlna.Configuration 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..7b8c504409 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 90ba601b4a..27c5b22680 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; 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..a7f2d4a73b 100644 --- a/Emby.Dlna/ControlResponse.cs +++ b/Emby.Dlna/ControlResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 66ae07329e..2982ce97e1 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; @@ -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..a1b1067040 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -111,7 +113,7 @@ namespace Emby.Dlna if (profile != null) { - _logger.LogDebug("Found matching device profile: {0}", profile.Name); + _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name); } else { @@ -138,80 +140,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) { diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 480621dd70..a40578e403 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -21,6 +21,7 @@ false true true + enable diff --git a/Emby.Dlna/EventSubscriptionResponse.cs b/Emby.Dlna/EventSubscriptionResponse.cs index 1b1bd426c5..8c82dcbf68 100644 --- a/Emby.Dlna/EventSubscriptionResponse.cs +++ b/Emby.Dlna/EventSubscriptionResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index ff81e83b5e..2e672b886b 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; 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..0309926abb 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; diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index abd99bbc3c..5fa1fd5896 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; 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..2bc4d8cc24 100644 --- a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs +++ b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index ee09cc65a9..1e6a5fadbf 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; diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 8272e505a0..35bf5927c4 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; @@ -188,7 +190,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..c7d2b28df8 100644 --- a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs index 3f8d552636..f8a14f411f 100644 --- a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs index deeb47918d..6661f92ac7 100644 --- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; 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..904c23d997 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -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)) { @@ -151,7 +151,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/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.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs index 8b47dd12e4..af4aa0059f 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 MediaBrowser.Common.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/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 63116f3680..3224ff4129 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -23,11 +23,12 @@ - + - + + diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index f7df587864..c63aec64e4 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -68,6 +68,11 @@ namespace Emby.Naming.TV 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..f9d06c09be 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -29,72 +29,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 (!new VideoResolver(_options).IsVideoFile(path)) + { + 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/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 79a6da8f7b..27e73208c6 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using Emby.Naming.Common; +using MediaBrowser.Common.Extensions; namespace Emby.Naming.Video { @@ -59,15 +59,15 @@ 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 (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { // It's not supported. Check stub extensions if (!StubResolver.TryResolveFile(path, _options, out stubType)) @@ -86,9 +86,7 @@ namespace Emby.Naming.Video var extraResult = new ExtraResolver(_options).GetExtraInfo(path); - var name = isDirectory - ? Path.GetFileName(path) - : Path.GetFileNameWithoutExtension(path); + var name = Path.GetFileNameWithoutExtension(path); int? year = null; @@ -107,7 +105,7 @@ namespace Emby.Naming.Video return new VideoFileInfo( path: path, - container: container, + container: container.IsEmpty ? null : container.ToString(), isStub: isStub, name: name, year: year, @@ -126,8 +124,8 @@ namespace Emby.Naming.Video /// True if is video file. public bool IsVideoFile(string path) { - var extension = Path.GetExtension(path); - return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + var extension = Path.GetExtension(path.AsSpan()); + return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); } /// @@ -137,8 +135,8 @@ namespace Emby.Naming.Video /// True if is video file stub. public bool IsStubFile(string path) { - var extension = Path.GetExtension(path); - return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + var extension = Path.GetExtension(path.AsSpan()); + return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); } /// 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..8c919db431 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; diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 29bac66340..de770f59ec 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; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 703f8d20da..82995deb30 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; @@ -335,10 +337,7 @@ namespace Emby.Server.Implementations { get { - if (_deviceId == null) - { - _deviceId = new DeviceId(ApplicationPaths, LoggerFactory); - } + _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory); return _deviceId.Value; } @@ -370,10 +369,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) { diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 7324b0ee9f..448f124034 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; 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..82d80fc83c 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -164,7 +166,7 @@ namespace Emby.Server.Implementations.Collections parentFolder.AddChild(collection, CancellationToken.None); - if (options.ItemIdList.Length > 0) + if (options.ItemIdList.Count > 0) { await AddToCollectionAsync( collection.Id, @@ -248,11 +250,7 @@ namespace Emby.Server.Implementations.Collections if (fireEvent) { - ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs - { - Collection = collection, - ItemsChanged = itemList - }); + ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList)); } } } @@ -304,11 +302,7 @@ namespace Emby.Server.Implementations.Collections }, RefreshPriority.High); - ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs - { - Collection = collection, - ItemsChanged = itemList - }); + ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList)); } /// 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..6f23a0888e 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; @@ -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..2d060dd652 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; @@ -502,7 +505,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 +900,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 +971,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 +998,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,21 +1038,15 @@ 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; - } - - var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries); var list = new List(); - foreach (var part in parts) + foreach (var part in value.SpanSplit('|')) { var image = ItemImageInfoFromValueString(part); @@ -1069,15 +1056,14 @@ namespace Emby.Server.Implementations.Data } } - item.ImageInfos = list.ToArray(); + return list.ToArray(); } - 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 +1073,105 @@ 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)) { image.DateModified = new DateTime(ticks, DateTimeKind.Utc); } - 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) + // Optional parameters: width*height*blurhash + if (nextSegment + 1 < value.Length - 1) { - if (int.TryParse(parts[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) - && int.TryParse(parts[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) + 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 +1284,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 +1334,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 +1366,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 +1392,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 +1588,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 +1625,7 @@ namespace Emby.Server.Implementations.Data } } - trailer.TrailerTypes = GetTrailerTypes(reader.GetString(index)).ToArray(); + trailer.TrailerTypes = GetTrailerTypes(trailerTypes).ToArray(); } } @@ -1654,19 +1634,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 +1652,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 +1689,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 +1713,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 +1724,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 +1773,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 +1837,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; } @@ -1977,21 +1929,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 +1958,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; @@ -2116,30 +2068,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) { @@ -2329,9 +2258,32 @@ namespace Emby.Server.Implementations.Data { if (!HasField(query, field)) { - foreach (var fieldToRemove in GetColumnNamesFromField(field)) + switch (field) { - list.Remove(fieldToRemove); + case ItemFields.Settings: + list.Remove("IsLocked"); + list.Remove("PreferredMetadataCountryCode"); + list.Remove("PreferredMetadataLanguage"); + list.Remove("LockedFields"); + break; + case ItemFields.ServiceName: + list.Remove("ExternalServiceId"); + break; + case ItemFields.SortName: + list.Remove("ForcedSortName"); + break; + case ItemFields.Taglines: + list.Remove("Tagline"); + break; + case ItemFields.Tags: + list.Remove("Tags"); + break; + case ItemFields.IsHD: + // do nothing + break; + default: + list.Remove(field.ToString()); + break; } } } @@ -2578,9 +2530,9 @@ namespace Emby.Server.Implementations.Data } var commandText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) - + GetFromText() - + GetJoinUserDataText(query); + + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) + + GetFromText() + + GetJoinUserDataText(query); var whereClauses = GetWhereClauses(query, null); if (whereClauses.Count != 0) @@ -2721,87 +2673,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) @@ -3245,12 +3132,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)); } @@ -3584,11 +3467,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 +4327,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 +4357,7 @@ namespace Emby.Server.Implementations.Data } } - if (query.HasAnyProviderId.Count > 0) + if (query.HasAnyProviderId != null && query.HasAnyProviderId.Count > 0) { var hasProviderIds = new List(); @@ -4532,7 +4415,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb")); } - var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList(); + var includedItemByNameTypes = GetItemByNameTypesInQuery(query); var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; var queryTopParentIds = query.TopParentIds; @@ -4790,27 +4673,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; @@ -4915,15 +4798,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 +4824,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,31 +5158,46 @@ 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 ? @@ -5333,9 +5227,9 @@ AND Type = @InternalPersonType)"); { foreach (var row in statement.ExecuteQuery()) { - if (!row.IsDBNull(0)) + if (row.TryGetString(0, out var result)) { - list.Add(row.GetString(0)); + list.Add(result); } } } @@ -5469,7 +5363,9 @@ AND Type = @InternalPersonType)"); commandText += whereText + " group by PresentationUniqueKey"; - if (query.SimilarTo != null || !string.IsNullOrEmpty(query.SearchTerm)) + if (query.OrderBy.Count != 0 + || query.SimilarTo != null + || !string.IsNullOrEmpty(query.SearchTerm)) { commandText += GetOrderByText(query); } @@ -5607,7 +5503,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 +5512,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 +5697,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 +5734,7 @@ AND Type = @InternalPersonType)"); } } - private PersonInfo GetPerson(IReadOnlyList reader) + private PersonInfo GetPerson(IReadOnlyList reader) { var item = new PersonInfo { @@ -5851,19 +5742,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 +5941,7 @@ AND Type = @InternalPersonType)"); /// /// The reader. /// ChapterInfo. - private MediaStream GetMediaStream(IReadOnlyList reader) + private MediaStream GetMediaStream(IReadOnlyList reader) { var item = new MediaStream { @@ -6059,150 +5950,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 +6152,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 +6242,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..ef9af1dcd0 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; @@ -348,16 +350,16 @@ namespace Emby.Server.Implementations.Data /// Read a row from the specified reader into the provided userData object. /// /// - private UserItemData ReadRow(IReadOnlyList reader) + 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 +367,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..7f1306d15a 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,7 +21,7 @@ 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)) { @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Data /// /// Name of the type. /// Type. - private Type LookupType(string typeName) + private Type? LookupType(string typeName) { return AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType(typeName)) 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 index da5047d244..2637addce7 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 54b18a8c80..7411239a1e 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; @@ -665,10 +667,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 +701,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; } @@ -898,10 +894,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(); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 9248053f54..a72a87462c 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -27,11 +27,11 @@ - + - - - + + + @@ -44,6 +44,7 @@ false true true + enable AD0001 AllEnabledByDefault diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 14201ead29..cc3e4a2c24 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; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index ae1b51b4c3..5bb4100ba9 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; 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..211941f443 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; diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 1989e9ed25..332fb33858 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 024404ceb0..c87f7dbbde 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -2,8 +2,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached)) { - return (AuthorizationInfo)cached; + return (AuthorizationInfo)cached!; // Cache should never contain null } return GetAuthorization(requestContext); @@ -55,15 +55,15 @@ namespace Emby.Server.Implementations.HttpServer.Security } private AuthorizationInfo GetAuthorizationInfoFromDictionary( - in Dictionary auth, + in Dictionary? auth, in IHeaderDictionary headers, in IQueryCollection queryString) { - string deviceId = null; - string device = null; - string client = null; - string version = null; - string token = null; + string? deviceId = null; + string? device = null; + string? client = null; + string? version = null; + string? token = null; if (auth != null) { @@ -206,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorizationDictionary(HttpContext httpReq) + private Dictionary? GetAuthorizationDictionary(HttpContext httpReq) { var auth = httpReq.Request.Headers["X-Emby-Authorization"]; @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security auth = httpReq.Request.Headers[HeaderNames.Authorization]; } - return GetAuthorization(auth); + return GetAuthorization(auth.Count > 0 ? auth[0] : null); } /// @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorizationDictionary(HttpRequest httpReq) + private Dictionary? GetAuthorizationDictionary(HttpRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -232,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security auth = httpReq.Headers[HeaderNames.Authorization]; } - return GetAuthorization(auth); + return GetAuthorization(auth.Count > 0 ? auth[0] : null); } /// @@ -240,43 +240,43 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The authorization header. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorization(string authorizationHeader) + private Dictionary? GetAuthorization(ReadOnlySpan authorizationHeader) { if (authorizationHeader == null) { return null; } - var parts = authorizationHeader.Split(' ', 2); + var firstSpace = authorizationHeader.IndexOf(' '); - // There should be at least to parts - if (parts.Length != 2) + // There should be at least two parts + if (firstSpace == -1) { return null; } - var acceptedNames = new[] { "MediaBrowser", "Emby" }; + var name = authorizationHeader[..firstSpace]; - // It has to be a digest request - if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase)) + if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase) + && !name.Equals("Emby", StringComparison.OrdinalIgnoreCase)) { return null; } - // Remove uptil the first space - authorizationHeader = parts[1]; - parts = authorizationHeader.Split(','); + authorizationHeader = authorizationHeader[(firstSpace + 1)..]; var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var item in parts) + foreach (var item in authorizationHeader.Split(',')) { - var param = item.Trim().Split('=', 2); + var trimmedItem = item.Trim(); + var firstEqualsSign = trimmedItem.IndexOf('='); - if (param.Length == 2) + if (firstEqualsSign > 0) { - var value = NormalizeValue(param[1].Trim('"')); - result[param[0]] = value; + var key = trimmedItem[..firstEqualsSign].ToString(); + var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString()); + result[key] = value; } } diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index dd77b45d89..c375f36ce4 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -36,14 +36,14 @@ namespace Emby.Server.Implementations.HttpServer.Security return GetSession((HttpContext)requestContext); } - public User GetUser(HttpContext requestContext) + public User? GetUser(HttpContext requestContext) { var session = GetSession(requestContext); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public User GetUser(object requestContext) + public User? GetUser(object requestContext) { return GetUser(((HttpRequest)requestContext).HttpContext); } diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 06acb56061..8f7d606692 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; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 1bee1ac310..861c0a95e3 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; 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..6a554e68ab 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -2,11 +2,10 @@ 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 MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; @@ -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 = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 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); } @@ -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] = ' '; + } + } + } + }); } /// @@ -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); @@ -639,7 +655,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); @@ -684,20 +700,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..a430b9e720 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 { diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 5f7e51858a..833fb0b7a1 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; @@ -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..ff5f26ce09 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; 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/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..f8d8197d46 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; @@ -48,6 +50,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 +178,7 @@ namespace Emby.Server.Implementations.Library { lock (_rootFolderSyncLock) { - if (_rootFolder == null) - { - _rootFolder = CreateRootFolder(); - } + _rootFolder ??= CreateRootFolder(); } } @@ -558,7 +558,6 @@ namespace Emby.Server.Implementations.Library var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService) { Parent = parent, - Path = fullPath, FileInfo = fileInfo, CollectionType = collectionType, LibraryOptions = libraryOptions @@ -684,7 +683,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)); @@ -1163,7 +1162,7 @@ namespace Emby.Server.Implementations.Library progress.Report(percent * 100); } - _itemRepository.UpdateInheritedValues(cancellationToken); + _itemRepository.UpdateInheritedValues(); progress.Report(100); } @@ -2517,7 +2516,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 +2528,23 @@ 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 + if (episodeInfo == null && episode.Parent.GetType() == typeof(Folder)) + { + episodeInfo = resolver.Resolve(episode.Parent.Path, true, null, null, isAbsoluteNaming); + if (episodeInfo != null) + { + // add the container + episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.'); + } + } + } + + episodeInfo ??= new EpisodeInfo(episode.Path); try { @@ -2880,6 +2893,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 +2906,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 +3011,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(() => diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index c2951dd155..4ef7923db3 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; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index d0b85f07d4..38e81d14c4 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; @@ -590,18 +592,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..06300adebc 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; @@ -100,8 +102,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..e893d63350 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; 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..a3dcdc9441 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -165,13 +167,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,11 +195,11 @@ 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; } 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/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs index 9ca76095b2..fa45ccf840 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; @@ -11,6 +13,12 @@ namespace Emby.Server.Implementations.Library.Resolvers 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..02c5287646 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -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(); 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..ecd44be477 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; 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/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs index 62268fce90..9599faea4b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index bcdf854ca3..26e615fa08 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 827e3c64b3..667e466133 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; 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/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 7a6b1d8b61..3fcadf5b1b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index c9d9cc49af..7970631201 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; @@ -801,22 +803,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; } } @@ -1621,9 +1623,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) @@ -2239,14 +2239,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..26e4ef1ed9 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; @@ -10,6 +12,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; @@ -307,13 +310,11 @@ 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); } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs index 8c27ca76e3..0ec52a9598 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller.LiveTv; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - internal class EpgChannelData { @@ -39,13 +38,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/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 1cac9cb963..bdab8c3e4b 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; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 142c59542a..1088638691 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -6,7 +8,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 +72,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..6c52a9a73d 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1926e738f2..00d02873c5 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; @@ -787,14 +789,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings { 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 ScheduleDirect.Station { stationID = channel.stationID }; - } var channelInfo = new ChannelInfo { diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 6824aa4423..ebad4eddf1 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 6af49dd45e..21e1409ac0 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 63a3146aa9..1f16289002 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -987,10 +989,7 @@ namespace Emby.Server.Implementations.LiveTv var externalProgramId = programTuple.Item2; string externalSeriesId = programTuple.Item3; - if (timerList == null) - { - timerList = (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; - } + timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase)); var foundSeriesTimer = false; @@ -1018,10 +1017,7 @@ namespace Emby.Server.Implementations.LiveTv continue; } - if (seriesTimerList == null) - { - seriesTimerList = (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items; - } + seriesTimerList ??= (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items; var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase)); @@ -1974,10 +1970,7 @@ namespace Emby.Server.Implementations.LiveTv }; } - if (service == null) - { - service = _services[0]; - } + service ??= _services[0]; var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 3a738fd5d2..ecd28097d3 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index fbcd4ef372..00a37bb022 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs index 740cbb66ee..0f04531896 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { internal class Channels diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs index 09d77f8382..42068cd340 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 1dcc786874..c5700db715 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -74,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var lineup = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken) .ConfigureAwait(false) ?? new List(); @@ -182,16 +184,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var sr = new StreamReader(stream, System.Text.Encoding.UTF8); var tuners = new List(); - while (!sr.EndOfStream) + await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false)) { - string line = StripXML(sr.ReadLine()); - if (line.Contains("Channel", StringComparison.Ordinal)) + string stripedLine = StripXML(line); + if (stripedLine.Contains("Channel", StringComparison.Ordinal)) { LiveTvTunerStatus status; - var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); - var name = line.Substring(0, index - 1); - var currentChannel = line.Substring(index + 7); - if (currentChannel != "none") + var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); + var name = stripedLine.Substring(0, index - 1); + var currentChannel = stripedLine.Substring(index + 7); + if (string.Equals(currentChannel, "none", StringComparison.Ordinal)) { status = LiveTvTunerStatus.LiveTv; } @@ -421,10 +423,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun string audioCodec = channelInfo.AudioCodec; - if (!videoBitrate.HasValue) - { - videoBitrate = isHd ? 15000000 : 2000000; - } + videoBitrate ??= isHd ? 15000000 : 2000000; int? audioBitrate = isHd ? 448000 : 192000; @@ -661,7 +660,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _modelCache.Clear(); } - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token; + using var timedCancellationToken = new CancellationTokenSource(discoveryDurationMs); + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timedCancellationToken.Token, cancellationToken); + cancellationToken = linkedCancellationTokenSource.Token; var list = new List(); // Create udp broadcast discovery message diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index a7fda1d72e..3016eeda24 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index b16ccc561a..50a2d9abb3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 78e62ff0a5..96a678c1d3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -150,7 +152,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token; + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token); + cancellationToken = linkedCancellationTokenSource.Token; // use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039 var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 4b170b2e4e..69035dac96 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index cc30a516d6..48a0c3cd37 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -35,16 +37,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts // Read the file and display it line by line. using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false))) { - return GetChannels(reader, channelIdPrefix, info.Id); - } - } - - public List ParseString(string text, string channelIdPrefix, string tunerHostId) - { - // Read the file and display it line by line. - using (var reader = new StringReader(text)) - { - return GetChannels(reader, channelIdPrefix, tunerHostId); + return await GetChannelsAsync(reader, channelIdPrefix, info.Id).ConfigureAwait(false); } } @@ -70,43 +63,42 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private const string ExtInfPrefix = "#EXTINF:"; - private List GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId) + private async Task> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId) { var channels = new List(); - string line; string extInf = string.Empty; - while ((line = reader.ReadLine()) != null) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { - line = line.Trim(); - if (string.IsNullOrWhiteSpace(line)) + var trimmedLine = line.Trim(); + if (string.IsNullOrWhiteSpace(trimmedLine)) { continue; } - if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase)) + if (trimmedLine.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase)) { continue; } - if (line.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase)) + if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase)) { - extInf = line.Substring(ExtInfPrefix.Length).Trim(); + extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim(); _logger.LogInformation("Found m3u channel: {0}", extInf); } - else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith('#')) + else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#')) { - var channel = GetChannelnfo(extInf, tunerHostId, line); + var channel = GetChannelnfo(extInf, tunerHostId, trimmedLine); if (string.IsNullOrWhiteSpace(channel.Id)) { - channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture); + channel.Id = channelIdPrefix + trimmedLine.GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture); } - channel.Path = line; + channel.Path = trimmedLine; channels.Add(channel); extInf = string.Empty; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index eeb2426f4f..137ed27e20 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json index b029b7042b..4f21c66bc4 100644 --- a/Emby.Server.Implementations/Localization/Core/af.json +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -115,5 +115,7 @@ "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde", "Undefined": "Ongedefineerd", "Forced": "Geforseer", - "Default": "Oorspronklik" + "Default": "Oorspronklik", + "TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.", + "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon" } diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 4b898e6fe0..3d6e159b13 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -113,5 +113,10 @@ "TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.", "TaskRefreshPeople": "إعادة تحميل الأشخاص", "TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.", - "TaskCleanLogs": "حذف دليل السجل" + "TaskCleanLogs": "حذف دليل السجل", + "TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الموضوع.", + "TaskCleanActivityLog": "حذف سجل الأنشطة", + "Default": "الإعدادات الافتراضية", + "Undefined": "غير معرف", + "Forced": "ملحقة" } diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index 9db3b50d91..bc25531d30 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -39,7 +39,7 @@ "MixedContent": "Смесено съдържание", "Movies": "Филми", "Music": "Музика", - "MusicVideos": "Музикални клипове", + "MusicVideos": "Музикални видеа", "NameInstallFailed": "{0} не можа да се инсталира", "NameSeasonNumber": "Сезон {0}", "NameSeasonUnknown": "Неразпознат сезон", @@ -62,7 +62,7 @@ "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно", "Photos": "Снимки", "Playlists": "Списъци", - "Plugin": "Приставка", + "Plugin": "Добавка", "PluginInstalledWithName": "{0} е инсталиранa", "PluginUninstalledWithName": "{0} е деинсталиранa", "PluginUpdatedWithName": "{0} е обновенa", @@ -116,5 +116,7 @@ "TasksMaintenanceCategory": "Поддръжка", "Undefined": "Неопределено", "Forced": "Принудително", - "Default": "По подразбиране" + "Default": "По подразбиране", + "TaskCleanActivityLogDescription": "Изтрива записите в дневника с активност по стари от конфигурираната възраст.", + "TaskCleanActivityLog": "Изчисти дневника с активност" } diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index a23037af8e..c3fbe24082 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -1,7 +1,7 @@ { "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে", "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে", - "Collections": "কলেক্শন", + "Collections": "সংগ্রহ", "ChapterNameValue": "অধ্যায় {0}", "Channels": "চ্যানেল", "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে", @@ -115,7 +115,7 @@ "TaskRefreshLibraryDescription": "নতুন ফাইলের জন্য মিডিয়া লাইব্রেরি স্ক্যান এবং মেটাডাটা রিফ্রেশ করুন।", "Undefined": "অসঙ্গায়িত", "Forced": "জোরকরে", - "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন", + "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন.", "TaskCleanActivityLog": "কাজের ফাইল খালি করুন", "Default": "প্রাথমিক" } diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 7752671837..ff14c1929c 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -39,7 +39,7 @@ "MixedContent": "Smíšený obsah", "Movies": "Filmy", "Music": "Hudba", - "MusicVideos": "Hudební klipy", + "MusicVideos": "Hudební videa", "NameInstallFailed": "Instalace {0} selhala", "NameSeasonNumber": "Sezóna {0}", "NameSeasonUnknown": "Neznámá sezóna", diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 051d6d009f..3453507d9e 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -39,7 +39,7 @@ "MixedContent": "Blandet indhold", "Movies": "Film", "Music": "Musik", - "MusicVideos": "Musikvideoer", + "MusicVideos": "Musik videoer", "NameInstallFailed": "{0} installationen mislykkedes", "NameSeasonNumber": "Sæson {0}", "NameSeasonUnknown": "Ukendt Sæson", diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index e9e4f61b8c..8ab657e5b8 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -34,7 +34,7 @@ "Latest": "جدیدترین‌ها", "MessageApplicationUpdated": "سرور Jellyfin بروزرسانی شد", "MessageApplicationUpdatedTo": "سرور Jellyfin به نسخه {0} بروزرسانی شد", - "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", + "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", "MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد", "MixedContent": "محتوای مخلوط", "Movies": "فیلم‌ها", diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index fd6148e789..633968d26d 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -1,5 +1,5 @@ { - "HeaderLiveTV": "Live TV", + "HeaderLiveTV": "Suora TV", "NewVersionIsAvailable": "Uusi versio Jellyfin-palvelimesta on ladattavissa.", "NameSeasonUnknown": "Tuntematon kausi", "NameSeasonNumber": "Kausi {0}", diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 1e195378ff..ce1493be8a 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -15,7 +15,7 @@ "Favorites": "Favoris", "Folders": "Dossiers", "Genres": "Genres", - "HeaderAlbumArtists": "Artistes", + "HeaderAlbumArtists": "Artistes de l'album", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes préférés", @@ -39,7 +39,7 @@ "MixedContent": "Contenu mixte", "Movies": "Films", "Music": "Musique", - "MusicVideos": "Vidéos musicales", + "MusicVideos": "Clips musicaux", "NameInstallFailed": "{0} échec de l'installation", "NameSeasonNumber": "Saison {0}", "NameSeasonUnknown": "Saison Inconnue", @@ -99,7 +99,7 @@ "TaskRefreshChannels": "Rafraîchir les chaines", "TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.", "TaskCleanTranscode": "Nettoyer les dossier des transcodages", - "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour être mises à jour automatiquement.", + "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurées pour être mises à jour automatiquement.", "TaskUpdatePlugins": "Mettre à jour les extensions", "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.", "TaskRefreshPeople": "Rafraîchir les acteurs", @@ -107,7 +107,7 @@ "TaskCleanLogs": "Nettoyer le répertoire des journaux", "TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.", "TaskRefreshLibrary": "Scanner toutes les Bibliothèques", - "TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.", + "TaskRefreshChapterImagesDescription": "Crée des vignettes pour les vidéos ayant des chapitres.", "TaskRefreshChapterImages": "Extraire les images de chapitre", "TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.", "TaskCleanCache": "Vider le répertoire cache", diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json index 11139d32a8..0398e1c9e5 100644 --- a/Emby.Server.Implementations/Localization/Core/gl.json +++ b/Emby.Server.Implementations/Localization/Core/gl.json @@ -79,5 +79,14 @@ "PluginUninstalledWithName": "{0} foi desinstalado", "PluginInstalledWithName": "{0} foi instalado", "Playlists": "Listas de reproducción", - "Photos": "Fotos" + "Photos": "Fotos", + "UserLockedOutWithName": "O usuario {0} foi bloqueado", + "UserDownloadingItemWithValues": "{0} está a ser transferido {1}", + "UserDeletedWithName": "O usuario {0} foi borrado", + "UserCreatedWithName": "O usuario {0} foi creado", + "Plugin": "Plugin", + "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo parada", + "NotificationOptionVideoPlayback": "Reproducción de vídeo iniciada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionTaskFailed": "Falla na tarefa axendada" } diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json index ef3697b15f..82dc601bcb 100644 --- a/Emby.Server.Implementations/Localization/Core/hi.json +++ b/Emby.Server.Implementations/Localization/Core/hi.json @@ -51,5 +51,14 @@ "Latest": "सबसे नया", "LabelIpAddressValue": "आई पी एड्रेस: {0}", "ItemRemovedWithName": "{0} लाइब्रेरी में से निकाल दिया है", - "HomeVideos": "होम वीडियोस" + "HomeVideos": "होम वीडियोस", + "NotificationOptionVideoPlayback": "वीडियो प्लेबैक शुरू हुआ", + "NotificationOptionUserLockedOut": "उपयोगकर्ता लॉक हो गया", + "NotificationOptionTaskFailed": "निर्धारित कार्य विफलता", + "NotificationOptionServerRestartRequired": "सर्वर पुनरारंभ आवश्यक है", + "NotificationOptionPluginUpdateInstalled": "प्लगइन अद्यतन स्थापित", + "NotificationOptionNewLibraryContent": "नई सामग्री जोड़ी गई", + "LabelRunningTimeValue": "चलने का समय: {0}", + "ItemAddedWithName": "{0} को लाइब्रेरी में जोड़ा गया", + "Inherit": "इनहेरिट" } diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index ef80705031..85848fed60 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -39,7 +39,7 @@ "MixedContent": "Vegyes tartalom", "Movies": "Filmek", "Music": "Zene", - "MusicVideos": "Zenei videók", + "MusicVideos": "Zenei videóklippek", "NameInstallFailed": "{0} sikertelen telepítés", "NameSeasonNumber": "{0}. évad", "NameSeasonUnknown": "Ismeretlen évad", @@ -74,7 +74,7 @@ "Songs": "Dalok", "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}", + "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}", "Sync": "Szinkronizál", "System": "Rendszer", "TvShows": "TV műsorok", @@ -82,12 +82,12 @@ "UserCreatedWithName": "{0} felhasználó létrehozva", "UserDeletedWithName": "{0} felhasználó törölve", "UserDownloadingItemWithValues": "{0} letölti {1}", - "UserLockedOutWithName": "{0} felhasználó zárolva van", - "UserOfflineFromDevice": "{0} kijelentkezett innen: {1}", + "UserLockedOutWithName": "{0} felhasználó zárolva van", + "UserOfflineFromDevice": "{0} kijelentkezett innen: {1}", "UserOnlineFromDevice": "{0} online innen: {1}", "UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}", "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}", - "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}", + "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}", "UserStoppedPlayingItemWithValues": "{0} befejezte {1} lejátászását itt: {2}", "ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz", "ValueSpecialEpisodeName": "Special - {0}", diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json index 0f769eaada..b262a8b424 100644 --- a/Emby.Server.Implementations/Localization/Core/is.json +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -25,7 +25,7 @@ "Channels": "Stöðvar", "CameraImageUploadedFrom": "Ný ljósmynd frá myndavél hefur verið hlaðið upp frá {0}", "Books": "Bækur", - "AuthenticationSucceededWithUserName": "{0} náði að auðkennast", + "AuthenticationSucceededWithUserName": "{0} auðkenning tókst", "Artists": "Listamaður", "Application": "Forrit", "AppDeviceValues": "Snjallforrit: {0}, Tæki: {1}", @@ -106,5 +106,6 @@ "TasksChannelsCategory": "Netrásir", "TasksApplicationCategory": "Forrit", "TasksLibraryCategory": "Miðlasafn", - "TasksMaintenanceCategory": "Viðhald" + "TasksMaintenanceCategory": "Viðhald", + "Default": "Sjálfgefið" } diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 110f8043d7..bd06f0a258 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -62,7 +62,7 @@ "NotificationOptionVideoPlaybackStopped": "La riproduzione video è stata interrotta", "Photos": "Foto", "Playlists": "Playlist", - "Plugin": "Plug-in", + "Plugin": "Plugin", "PluginInstalledWithName": "{0} è stato Installato", "PluginUninstalledWithName": "{0} è stato disinstallato", "PluginUpdatedWithName": "{0} è stato aggiornato", @@ -87,7 +87,7 @@ "UserOnlineFromDevice": "{0} è online su {1}", "UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}", "UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}", - "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}", + "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di \"{1}\" su {2}", "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}", "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale", "ValueSpecialEpisodeName": "Speciale - {0}", diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 829a29ad42..4eee369892 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -5,23 +5,23 @@ "Artists": "Oryndauşylar", "AuthenticationSucceededWithUserName": "{0} tüpnūsqalyq rastaluy sättı aiaqtaldy", "Books": "Kıtaptar", - "CameraImageUploadedFrom": "{0} kamerasynan jaŋa suret jüktep salyndy", + "CameraImageUploadedFrom": "{0} kamerasynan jaña suret jüktep salyndy", "Channels": "Arnalar", "ChapterNameValue": "{0}-sahna", "Collections": "Jiyntyqtar", "DeviceOfflineWithName": "{0} ajyratylğan", "DeviceOnlineWithName": "{0} qosylğan", "FailedLoginAttemptWithUserName": "{0} tarapynan kıru äreketı sätsız aiaqtaldy", - "Favorites": "Taŋdaulylar", + "Favorites": "Tañdaulylar", "Folders": "Qaltalar", "Genres": "Janrlar", "HeaderAlbumArtists": "Älbom oryndauşylary", "HeaderContinueWatching": "Qaraudy jalğastyru", - "HeaderFavoriteAlbums": "Taŋdauly älbomdar", - "HeaderFavoriteArtists": "Taŋdauly oryndauşylar", - "HeaderFavoriteEpisodes": "Taŋdauly telebölımder", - "HeaderFavoriteShows": "Taŋdauly körsetımder", - "HeaderFavoriteSongs": "Taŋdauly äuender", + "HeaderFavoriteAlbums": "Tañdauly älbomdar", + "HeaderFavoriteArtists": "Tañdauly oryndauşylar", + "HeaderFavoriteEpisodes": "Tañdauly telebölımder", + "HeaderFavoriteShows": "Tañdauly körsetımder", + "HeaderFavoriteSongs": "Tañdauly äuender", "HeaderLiveTV": "Efir", "HeaderNextUp": "Kezektı", "HeaderRecordingGroups": "Jazba toptary", @@ -31,11 +31,11 @@ "ItemRemovedWithName": "{0} tasyğyşhanadan alastaldy", "LabelIpAddressValue": "IP-mekenjaiy: {0}", "LabelRunningTimeValue": "Oinatu uaqyty: {0}", - "Latest": "Eŋ keiıngı", - "MessageApplicationUpdated": "Jellyfin Serverı jaŋartyldy", - "MessageApplicationUpdatedTo": "Jellyfin Serverı {0} nūsqasyna jaŋartyldy", - "MessageNamedServerConfigurationUpdatedWithValue": "Server teŋşelımderınıŋ {0} bölımı jaŋartyldy", - "MessageServerConfigurationUpdated": "Server teŋşelımderı jaŋartyldy", + "Latest": "Eñ keiıngı", + "MessageApplicationUpdated": "Jellyfin Serverı jañartyldy", + "MessageApplicationUpdatedTo": "Jellyfin Serverı {0} nūsqasyna jañartyldy", + "MessageNamedServerConfigurationUpdatedWithValue": "Server teñşelımderınıñ {0} bölımı jañartyldy", + "MessageServerConfigurationUpdated": "Server teñşelımderı jañartyldy", "MixedContent": "Aralas mazmūn", "Movies": "Filmder", "Music": "Muzyka", @@ -43,18 +43,18 @@ "NameInstallFailed": "{0} ornatyluy sätsız", "NameSeasonNumber": "{0}-mausym", "NameSeasonUnknown": "Belgısız mausym", - "NewVersionIsAvailable": "Jaŋa Jellyfin Server nūsqasy jüktep aluğa qoljetımdı.", - "NotificationOptionApplicationUpdateAvailable": "Qoldanba jaŋartuy qoljetımdı", - "NotificationOptionApplicationUpdateInstalled": "Qoldanba jaŋartuy ornatyldy", + "NewVersionIsAvailable": "Jaña Jellyfin Server nūsqasy jüktep aluğa qoljetımdı.", + "NotificationOptionApplicationUpdateAvailable": "Qoldanba jañartuy qoljetımdı", + "NotificationOptionApplicationUpdateInstalled": "Qoldanba jañartuy ornatyldy", "NotificationOptionAudioPlayback": "Dybys oinatuy bastaldy", "NotificationOptionAudioPlaybackStopped": "Dybys oinatuy toqtatyldy", "NotificationOptionCameraImageUploaded": "Kameradan fotosuret jüktep salynğan", "NotificationOptionInstallationFailed": "Ornatu sätsızdıgı", - "NotificationOptionNewLibraryContent": "Jaŋa mazmūn üstelıngen", + "NotificationOptionNewLibraryContent": "Jaña mazmūn üstelıngen", "NotificationOptionPluginError": "Plagin sätsızdıgı", "NotificationOptionPluginInstalled": "Plagin ornatyldy", "NotificationOptionPluginUninstalled": "Plagin ornatuy boldyrylmady", - "NotificationOptionPluginUpdateInstalled": "Plagin jaŋartuy ornatyldy", + "NotificationOptionPluginUpdateInstalled": "Plagin jañartuy ornatyldy", "NotificationOptionServerRestartRequired": "Serverdı qaita ıske qosu qajet", "NotificationOptionTaskFailed": "Josparlağan tapsyrma sätsızdıgı", "NotificationOptionUserLockedOut": "Paidalanuşy qūrsauly", @@ -65,14 +65,14 @@ "Plugin": "Plagin", "PluginInstalledWithName": "{0} ornatyldy", "PluginUninstalledWithName": "{0} joiyldy", - "PluginUpdatedWithName": "{0} jaŋartyldy", + "PluginUpdatedWithName": "{0} jañartyldy", "ProviderValue": "Jetkızuşı: {0}", "ScheduledTaskFailedWithName": "{0} sätsız", "ScheduledTaskStartedWithName": "{0} ıske qosyldy", "ServerNameNeedsToBeRestarted": "{0} qaita ıske qosu qajet", "Shows": "Körsetımder", "Songs": "Äuender", - "StartupEmbyServerIsLoading": "Jellyfin Server jüktelude. Ärekettı köp ūzamai qaitalaŋyz.", + "StartupEmbyServerIsLoading": "Jellyfin Server jüktelude. Ärekettı köp ūzamai qaitalañyz.", "SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз", "SubtitleDownloadFailureFromForItem": "{1} üşın subtitrlerdı {0} közınen jüktep alu sätsız", "Sync": "Ündestıru", @@ -86,7 +86,7 @@ "UserOfflineFromDevice": "{0} — {1} tarapynan ajyratyldy", "UserOnlineFromDevice": "{0} — {1} tarapynan qosyldy", "UserPasswordChangedWithName": "Paidalanuşy {0} üşın paröl özgertıldı", - "UserPolicyUpdatedWithName": "Paidalanuşy {0} üşın saiasattary jaŋartyldy", + "UserPolicyUpdatedWithName": "Paidalanuşy {0} üşın saiasattary jañartyldy", "UserStartedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuda", "UserStoppedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuyn toqtatty", "ValueHasBeenAddedToLibrary": "{0} tasyğyşhanağa üstelındı", @@ -94,10 +94,10 @@ "VersionNumber": "Nūsqasy {0}", "Default": "Ädepkı", "TaskDownloadMissingSubtitles": "Joq subtitrlerdı jüktep alu", - "TaskRefreshChannels": "Arnalardy jaŋğyrtu", + "TaskRefreshChannels": "Arnalardy jañğyrtu", "TaskCleanTranscode": "Qaita kodtau katalogyn tazalau", - "TaskUpdatePlugins": "Plaginderdı jaŋartu", - "TaskRefreshPeople": "Adamdardy jaŋğyrtu", + "TaskUpdatePlugins": "Plaginderdı jañartu", + "TaskRefreshPeople": "Adamdardy jañğyrtu", "TaskCleanLogs": "Jūrnal katalogyn tazalau", "TaskRefreshLibrary": "Tasyğyşhanany skanerleu", "TaskRefreshChapterImages": "Sahna suretterın şyğaryp alu", @@ -109,14 +109,14 @@ "TasksMaintenanceCategory": "Qyzmet körsetu", "Undefined": "Anyqtalmağan", "Forced": "Mäjbürlı", - "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.", - "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.", + "TaskDownloadMissingSubtitlesDescription": "Metaderekter teñşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.", + "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jañğyrtady.", "TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.", - "TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.", - "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.", + "TaskUpdatePluginsDescription": "Avtomatty türde jañartuğa teñşelgen plaginder üşın jañartulardy jüktep alady jäne ornatady.", + "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jañartady.", "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.", - "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.", + "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaña faildardy skanerleidі jäne metaderekterdı jañğyrtady.", "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşın nobailar jasaidy.", "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.", - "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady." + "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teñşelgen jasynan asqan jazbalary joiady." } diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index 9920ef4d51..f3a131d405 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -114,8 +114,9 @@ "TasksApplicationCategory": "Programa", "TasksLibraryCategory": "Mediateka", "TasksMaintenanceCategory": "Priežiūra", - "TaskCleanActivityLog": "Švarus veiklos žurnalas", + "TaskCleanActivityLog": "Išvalyti veiklos žurnalą", "Undefined": "Neapibrėžtas", "Forced": "Priverstas", - "Default": "Numatytas" + "Default": "Numatytas", + "TaskCleanActivityLogDescription": "Ištrina veiklos žuranlo įrašus, kurie yra senesni nei nustatytas amžius." } diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 5e3d095ff1..5b4c8ae107 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -39,29 +39,29 @@ "MixedContent": "Kandungan campuran", "Movies": "Filem", "Music": "Muzik", - "MusicVideos": "Video muzik", + "MusicVideos": "Muzik video", "NameInstallFailed": "{0} pemasangan gagal", "NameSeasonNumber": "Musim {0}", "NameSeasonUnknown": "Musim Tidak Diketahui", "NewVersionIsAvailable": "Versi terbaru Jellyfin Server bersedia untuk dimuat turunkan.", "NotificationOptionApplicationUpdateAvailable": "Kemas kini aplikasi telah sedia", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionApplicationUpdateInstalled": "Kemas kini aplikasi telah dipasang", + "NotificationOptionAudioPlayback": "Ulangmain audio bermula", + "NotificationOptionAudioPlaybackStopped": "Ulangmain audio dihentikan", + "NotificationOptionCameraImageUploaded": "Imej kamera telah dimuatnaik", "NotificationOptionInstallationFailed": "Pemasangan gagal", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", + "NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah", + "NotificationOptionPluginError": "Kegagalan plugin", + "NotificationOptionPluginInstalled": "Plugin telah dipasang", "NotificationOptionPluginUninstalled": "Plugin uninstalled", "NotificationOptionPluginUpdateInstalled": "Plugin update installed", "NotificationOptionServerRestartRequired": "Server restart required", "NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionUserLockedOut": "User locked out", "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", - "Playlists": "Playlists", + "NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan", + "Photos": "Gambar-gambar", + "Playlists": "Senarai main", "Plugin": "Plugin", "PluginInstalledWithName": "{0} was installed", "PluginUninstalledWithName": "{0} was uninstalled", @@ -71,10 +71,10 @@ "ScheduledTaskStartedWithName": "{0} bermula", "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "Shows": "Series", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "Songs": "Lagu-lagu", + "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", "Sync": "Sync", "System": "Sistem", "TvShows": "TV Shows", @@ -82,14 +82,24 @@ "UserCreatedWithName": "User {0} has been created", "UserDeletedWithName": "User {0} has been deleted", "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", + "UserLockedOutWithName": "Pengguna {0} telah dikunci", + "UserOfflineFromDevice": "{0} telah terputus dari {1}", + "UserOnlineFromDevice": "{0} berada dalam talian dari {1}", + "UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}", + "UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}", "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueSpecialEpisodeName": "Khas - {0}", - "VersionNumber": "Versi {0}" + "VersionNumber": "Versi {0}", + "TaskCleanActivityLog": "Log Aktiviti Bersih", + "TasksChannelsCategory": "Saluran Internet", + "TasksApplicationCategory": "Aplikasi", + "TasksLibraryCategory": "Perpustakaan", + "TasksMaintenanceCategory": "Penyelenggaraan", + "Undefined": "Tidak ditentukan", + "Forced": "Paksa", + "Default": "Asal", + "TaskCleanCache": "Bersihkan Direktori Cache", + "TaskCleanActivityLogDescription": "Padamkan entri log aktiviti yang lebih tua daripada usia yang dikonfigurasi." } diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index d5bca9f6c9..fbe1f7c4d5 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -30,20 +30,20 @@ "ItemAddedWithName": "{0} ble lagt til i biblioteket", "ItemRemovedWithName": "{0} ble fjernet fra biblioteket", "LabelIpAddressValue": "IP-adresse: {0}", - "LabelRunningTimeValue": "Kjøretid {0}", + "LabelRunningTimeValue": "Spilletid {0}", "Latest": "Siste", - "MessageApplicationUpdated": "Jellyfin Server har blitt oppdatert", - "MessageApplicationUpdatedTo": "Jellyfin Server ble oppdatert til {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurasjon seksjon {0} har blitt oppdatert", - "MessageServerConfigurationUpdated": "Serverkonfigurasjon er oppdatert", + "MessageApplicationUpdated": "Jellyfin-tjeneren har blitt oppdatert", + "MessageApplicationUpdatedTo": "Jellyfin-tjeneren ble oppdatert til {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Tjenerkonfigurasjonsseksjon {0} har blitt oppdatert", + "MessageServerConfigurationUpdated": "Tjenerkonfigurasjon er oppdatert", "MixedContent": "Blandet innhold", "Movies": "Filmer", "Music": "Musikk", "MusicVideos": "Musikkvideoer", - "NameInstallFailed": "{0}-installasjonen mislyktes", + "NameInstallFailed": "Installasjonen av {0} mislyktes", "NameSeasonNumber": "Sesong {0}", - "NameSeasonUnknown": "Sesong ukjent", - "NewVersionIsAvailable": "En ny versjon av Jellyfin Server er tilgjengelig for nedlasting.", + "NameSeasonUnknown": "Ukjent sesong", + "NewVersionIsAvailable": "En ny versjon av Jellyfin-tjeneren er tilgjengelig for nedlasting.", "NotificationOptionApplicationUpdateAvailable": "En programvareoppdatering er tilgjengelig", "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering installert", "NotificationOptionAudioPlayback": "Lydavspilling startet", @@ -51,18 +51,18 @@ "NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp", "NotificationOptionInstallationFailed": "Installasjonen feilet", "NotificationOptionNewLibraryContent": "Nytt innhold lagt til", - "NotificationOptionPluginError": "Pluginfeil", - "NotificationOptionPluginInstalled": "Plugin installert", - "NotificationOptionPluginUninstalled": "Plugin avinstallert", - "NotificationOptionPluginUpdateInstalled": "Pluginoppdatering installert", - "NotificationOptionServerRestartRequired": "Serveromstart er nødvendig", + "NotificationOptionPluginError": "Programvareutvidelsesfeil", + "NotificationOptionPluginInstalled": "Programvareutvidelse installert", + "NotificationOptionPluginUninstalled": "Programvareutvidelse avinstallert", + "NotificationOptionPluginUpdateInstalled": "Programvareutvidelsesoppdatering installert", + "NotificationOptionServerRestartRequired": "Tjeneromstart er nødvendig", "NotificationOptionTaskFailed": "Feil under utføring av planlagt oppgave", "NotificationOptionUserLockedOut": "Bruker er utestengt", "NotificationOptionVideoPlayback": "Videoavspilling startet", "NotificationOptionVideoPlaybackStopped": "Videoavspilling stoppet", "Photos": "Bilder", "Playlists": "Spillelister", - "Plugin": "Plugin", + "Plugin": "Programvareutvidelse", "PluginInstalledWithName": "{0} ble installert", "PluginUninstalledWithName": "{0} ble avinstallert", "PluginUpdatedWithName": "{0} ble oppdatert", @@ -72,7 +72,7 @@ "ServerNameNeedsToBeRestarted": "{0} må startes på nytt", "Shows": "Program", "Songs": "Sanger", - "StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.", + "StartupEmbyServerIsLoading": "Jellyfin-tjener laster. Prøv igjen snart.", "SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}", "SubtitleDownloadFailureFromForItem": "Kunne ikke laste ned undertekster fra {0} for {1}", "Sync": "Synkroniser", @@ -86,37 +86,37 @@ "UserOfflineFromDevice": "{0} har koblet fra {1}", "UserOnlineFromDevice": "{0} er tilkoblet fra {1}", "UserPasswordChangedWithName": "Passordet for {0} er oppdatert", - "UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}", + "UserPolicyUpdatedWithName": "Brukerretningslinjene har blitt oppdatert for {0}", "UserStartedPlayingItemWithValues": "{0} har startet avspilling {1} på {2}", - "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}", + "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}", "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt", "ValueSpecialEpisodeName": "Spesialepisode - {0}", "VersionNumber": "Versjon {0}", - "TasksChannelsCategory": "Internett kanaler", + "TasksChannelsCategory": "Internettkanaler", "TasksApplicationCategory": "Applikasjon", "TasksLibraryCategory": "Bibliotek", "TasksMaintenanceCategory": "Vedlikehold", - "TaskCleanCache": "Tøm buffer katalog", + "TaskCleanCache": "Tøm hurtigbuffer", "TaskRefreshLibrary": "Skann mediebibliotek", "TaskRefreshChapterImagesDescription": "Lager forhåndsvisningsbilder for videoer som har kapitler.", - "TaskRefreshChapterImages": "Trekk ut Kapittelbilder", + "TaskRefreshChapterImages": "Trekk ut kapittelbilder", "TaskCleanCacheDescription": "Sletter mellomlagrede filer som ikke lengre trengs av systemet.", - "TaskDownloadMissingSubtitlesDescription": "Søker etter manglende underteksting på nett basert på metadatakonfigurasjon.", - "TaskDownloadMissingSubtitles": "Last ned manglende underteksting", - "TaskRefreshChannelsDescription": "Frisker opp internettkanalinformasjon.", - "TaskRefreshChannels": "Oppfrisk kanaler", + "TaskDownloadMissingSubtitlesDescription": "Søker etter manglende undertekster på nett basert på metadatakonfigurasjon.", + "TaskDownloadMissingSubtitles": "Last ned manglende undertekster", + "TaskRefreshChannelsDescription": "Oppdaterer internettkanalinformasjon.", + "TaskRefreshChannels": "Oppdater kanaler", "TaskCleanTranscodeDescription": "Sletter omkodede filer som er mer enn én dag gamle.", "TaskCleanTranscode": "Tøm transkodingmappe", - "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringer for utvidelser som er stilt inn til å oppdatere automatisk.", - "TaskUpdatePlugins": "Oppdater utvidelser", + "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringer for programvareutvidelser som er stilt inn til å oppdatere automatisk.", + "TaskUpdatePlugins": "Oppdater programvareutvidelse", "TaskRefreshPeopleDescription": "Oppdaterer metadata for skuespillere og regissører i mediebiblioteket ditt.", - "TaskRefreshPeople": "Oppfrisk personer", + "TaskRefreshPeople": "Oppdater personer", "TaskCleanLogsDescription": "Sletter loggfiler som er eldre enn {0} dager gamle.", "TaskCleanLogs": "Tøm loggmappe", "TaskRefreshLibraryDescription": "Skanner mediebibliotekene dine for nye filer og oppdaterer metadata.", "TaskCleanActivityLog": "Tøm aktivitetslogg", "Undefined": "Udefinert", - "Forced": "Tvungen", + "Forced": "Tvunget", "Default": "Standard", "TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen." } diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index ffc329e35b..2973c8c6ea 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -3,9 +3,9 @@ "AppDeviceValues": "App: {0}, Apparaat: {1}", "Application": "Applicatie", "Artists": "Artiesten", - "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", + "AuthenticationSucceededWithUserName": "{0} is succesvol geauthenticeerd", "Books": "Boeken", - "CameraImageUploadedFrom": "Er is een nieuwe camera afbeelding toegevoegd via {0}", + "CameraImageUploadedFrom": "Nieuwe camera afbeelding toegevoegd vanaf {0}", "Channels": "Kanalen", "ChapterNameValue": "Hoofdstuk {0}", "Collections": "Verzamelingen", diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json index 6236515b2c..32d4f3a8b6 100644 --- a/Emby.Server.Implementations/Localization/Core/nn.json +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -1,25 +1,25 @@ { - "MessageServerConfigurationUpdated": "Tenar konfigurasjonen har blitt oppdatert", + "MessageServerConfigurationUpdated": "Tenarkonfigurasjonen har blitt oppdatert", "MessageNamedServerConfigurationUpdatedWithValue": "Tenar konfigurasjon seksjon {0} har blitt oppdatert", - "MessageApplicationUpdatedTo": "Jellyfin Tenaren har blitt oppdatert til {0}", - "MessageApplicationUpdated": "Jellyfin Tenaren har blitt oppdatert", + "MessageApplicationUpdatedTo": "Jellyfin-tenaren har blitt oppdatert til {0}", + "MessageApplicationUpdated": "Jellyfin-tenaren har blitt oppdatert", "Latest": "Nyaste", "LabelRunningTimeValue": "Speletid: {0}", - "LabelIpAddressValue": "IP adresse: {0}", + "LabelIpAddressValue": "IP-adresse: {0}", "ItemRemovedWithName": "{0} vart fjerna frå biblioteket", "ItemAddedWithName": "{0} vart lagt til i biblioteket", - "Inherit": "Arv", - "HomeVideos": "Heime Videoar", + "Inherit": "Arve", + "HomeVideos": "Heimevideoar", "HeaderRecordingGroups": "Innspelingsgrupper", "HeaderNextUp": "Neste", "HeaderLiveTV": "Direkte TV", - "HeaderFavoriteSongs": "Favoritt Songar", - "HeaderFavoriteShows": "Favoritt Seriar", - "HeaderFavoriteEpisodes": "Favoritt Episodar", - "HeaderFavoriteArtists": "Favoritt Artistar", - "HeaderFavoriteAlbums": "Favoritt Album", + "HeaderFavoriteSongs": "Favorittsongar", + "HeaderFavoriteShows": "Favorittseriar", + "HeaderFavoriteEpisodes": "Favorittepisodar", + "HeaderFavoriteArtists": "Favorittartistar", + "HeaderFavoriteAlbums": "Favorittalbum", "HeaderContinueWatching": "Fortsett å sjå", - "HeaderAlbumArtists": "Album Artist", + "HeaderAlbumArtists": "Albumartist", "Genres": "Sjangrar", "Folders": "Mapper", "Favorites": "Favorittar", @@ -29,18 +29,18 @@ "Collections": "Samlingar", "ChapterNameValue": "Kapittel {0}", "Channels": "Kanalar", - "CameraImageUploadedFrom": "Eit nytt kamera bilete har blitt lasta opp frå {0}", + "CameraImageUploadedFrom": "Eit nytt kamerabilete har blitt lasta opp frå {0}", "Books": "Bøker", - "AuthenticationSucceededWithUserName": "{0} Har logga inn", + "AuthenticationSucceededWithUserName": "{0} har logga inn", "Artists": "Artistar", "Application": "Program", "AppDeviceValues": "App: {0}, Eining: {1}", "Albums": "Album", "NotificationOptionServerRestartRequired": "Tenaren krev omstart", - "NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert", - "NotificationOptionPluginUninstalled": "Tilleggsprogram avinstallert", - "NotificationOptionPluginInstalled": "Tilleggsprogram installert", - "NotificationOptionPluginError": "Tilleggsprogram feila", + "NotificationOptionPluginUpdateInstalled": "Programvaretilleggoppdatering vart installert", + "NotificationOptionPluginUninstalled": "Programvaretillegg avinstallert", + "NotificationOptionPluginInstalled": "Programvaretillegg installert", + "NotificationOptionPluginError": "Programvaretillegg feila", "NotificationOptionNewLibraryContent": "Nytt innhald er lagt til", "NotificationOptionInstallationFailed": "Installasjonsfeil", "NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp", @@ -48,33 +48,33 @@ "NotificationOptionAudioPlayback": "Lydavspilling påbyrja", "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering er installert", "NotificationOptionApplicationUpdateAvailable": "Applikasjonsoppdatering er tilgjengeleg", - "NewVersionIsAvailable": "Ein ny versjon av Jellyfin serveren er tilgjengeleg for nedlasting.", + "NewVersionIsAvailable": "Ein ny versjon av Jellyfin-tjenaren er tilgjengeleg for nedlasting.", "NameSeasonUnknown": "Ukjend sesong", "NameSeasonNumber": "Sesong {0}", - "NameInstallFailed": "{0} Installasjonen feila", + "NameInstallFailed": "Installasjonen av {0} feila", "MusicVideos": "Musikkvideoar", "Music": "Musikk", "Movies": "Filmar", "MixedContent": "Blanda innhald", - "Sync": "Synkronisera", + "Sync": "Synkroniser", "TaskDownloadMissingSubtitlesDescription": "Søk Internettet for manglande undertekstar basert på metadatainnstillingar.", "TaskDownloadMissingSubtitles": "Last ned manglande undertekstar", "TaskRefreshChannelsDescription": "Oppdater internettkanalinformasjon.", "TaskRefreshChannels": "Oppdater kanalar", - "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gamal.", - "TaskCleanTranscode": "Reins transkodemappe", + "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gammal.", + "TaskCleanTranscode": "Fjern transkodemappe", "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringar for programtillegg som er sette opp til å oppdaterast automatisk.", - "TaskUpdatePlugins": "Oppdaterer programtillegg", + "TaskUpdatePlugins": "Oppdaterer programvaretillegg", "TaskRefreshPeopleDescription": "Oppdaterer metadata for skodespelarar og regissørar i mediebiblioteket ditt.", "TaskRefreshPeople": "Oppdater personar", "TaskCleanLogsDescription": "Slett loggfiler som er meir enn {0} dagar gamle.", - "TaskCleanLogs": "Reins loggmappe", + "TaskCleanLogs": "Slett loggmappa", "TaskRefreshLibraryDescription": "Skannar mediebiblioteket ditt for nye filer og oppdaterer metadata.", "TaskRefreshLibrary": "Skann mediebibliotek", "TaskRefreshChapterImagesDescription": "Lager miniatyrbilete for videoar som har kapittel.", "TaskRefreshChapterImages": "Trekk ut kapittelbilete", - "TaskCleanCacheDescription": "Slettar mellomlagra filer som ikkje lengre trengst av systemet.", - "TaskCleanCache": "Rens mappe for hurtiglager", + "TaskCleanCacheDescription": "Sletter mellomlagra filer som ikkje lengre trengst av systemet.", + "TaskCleanCache": "Fjern hurtigbuffer", "TasksChannelsCategory": "Internettkanalar", "TasksApplicationCategory": "Applikasjon", "TasksLibraryCategory": "Bibliotek", @@ -96,9 +96,9 @@ "TvShows": "TV-seriar", "System": "System", "SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}", - "StartupEmbyServerIsLoading": "Jellyfintenaren laster. Prøv igjen om litt.", - "Songs": "Songar", - "Shows": "Program", + "StartupEmbyServerIsLoading": "Jellyfin-tenaren laster. Prøv igjen seinare.", + "Songs": "Sangar", + "Shows": "Seriar", "ServerNameNeedsToBeRestarted": "{0} må omstartast", "ScheduledTaskStartedWithName": "{0} starta", "ScheduledTaskFailedWithName": "{0} feila", @@ -106,11 +106,16 @@ "PluginUpdatedWithName": "{0} blei oppdatert", "PluginUninstalledWithName": "{0} blei avinstallert", "PluginInstalledWithName": "{0} blei installert", - "Plugin": "Programtillegg", - "Playlists": "Speleliste", - "Photos": "Foto", + "Plugin": "Programvaretillegg", + "Playlists": "Spelelister", + "Photos": "Bilete", "NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa", "NotificationOptionVideoPlayback": "Videoavspeling starta", "NotificationOptionUserLockedOut": "Brukar er utestengd", - "NotificationOptionTaskFailed": "Planlagt oppgåve feila" + "NotificationOptionTaskFailed": "Planlagt oppgåve feila", + "TaskCleanActivityLogDescription": "Sletter aktivitetslogginnlegg som er eldre enn den konfigurerte alderen.", + "TaskCleanActivityLog": "Slett aktivitetslogg", + "Undefined": "Udefinert", + "Forced": "Tvungen", + "Default": "Standard" } diff --git a/Emby.Server.Implementations/Localization/Core/pa.json b/Emby.Server.Implementations/Localization/Core/pa.json index 469fa89b6e..d1db09232e 100644 --- a/Emby.Server.Implementations/Localization/Core/pa.json +++ b/Emby.Server.Implementations/Localization/Core/pa.json @@ -1,5 +1,5 @@ { - "TaskRefreshChapterImages": "ਐਬਸਟਰੈਕਟ ਅਧਿਆਇ ਅਧਿਆਇ", + "TaskRefreshChapterImages": "ਐਕਸਟਰੈਕਟ ਚੈਪਟਰ ਚਿੱਤਰ", "TaskDownloadMissingSubtitlesDescription": "ਮੈਟਾਡੇਟਾ ਕੌਂਫਿਗਰੇਸ਼ਨ ਦੇ ਅਧਾਰ ਤੇ ਗਾਇਬ ਉਪਸਿਰਲੇਖਾਂ ਲਈ ਇੰਟਰਨੈਟ ਦੀ ਭਾਲ ਕਰਦਾ ਹੈ.", "TaskDownloadMissingSubtitles": "ਗਾਇਬ ਉਪਸਿਰਲੇਖ ਡਾ Download ਨਲੋਡ ਕਰੋ", "TaskRefreshChannelsDescription": "ਇੰਟਰਨੈੱਟ ਚੈਨਲ ਦੀ ਜਾਣਕਾਰੀ ਨੂੰ ਤਾਜ਼ਾ ਕਰਦਾ ਹੈ.", diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 46b47cf4a8..e58f8c39d0 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -39,7 +39,7 @@ "MixedContent": "Смешанное содержимое", "Movies": "Кино", "Music": "Музыка", - "MusicVideos": "Музыкальные клипы", + "MusicVideos": "Муз. видео", "NameInstallFailed": "Установка {0} неудачна", "NameSeasonNumber": "Сезон {0}", "NameSeasonUnknown": "Сезон неопознан", @@ -75,7 +75,7 @@ "StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.", "SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить", "SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}", - "Sync": "Синхронизация", + "Sync": "Синхро", "System": "Система", "TvShows": "ТВ", "User": "Пользователь", diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index b5a7fa5b8f..d992bf79b1 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -39,7 +39,7 @@ "MixedContent": "Blandat innehåll", "Movies": "Filmer", "Music": "Musik", - "MusicVideos": "Musikvideos", + "MusicVideos": "Musikvideor", "NameInstallFailed": "{0} installationen misslyckades", "NameSeasonNumber": "Säsong {0}", "NameSeasonUnknown": "Okänd säsong", diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index c737ba42b8..129986ed0e 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -69,7 +69,7 @@ "NameSeasonUnknown": "அறியப்படாத பருவம்", "NameSeasonNumber": "பருவம் {0}", "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது", - "MusicVideos": "இசைப்படங்கள்", + "MusicVideos": "இசை கானொளி", "Music": "இசை", "Movies": "திரைப்படங்கள்", "Latest": "புதியவை", diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json index 5bf58baf8c..e260104236 100644 --- a/Emby.Server.Implementations/Localization/Core/th.json +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -113,5 +113,9 @@ "Sync": "ซิงค์", "SubtitleDownloadFailureFromForItem": "ไม่สามารถดาวน์โหลดคำบรรยายจาก {0} สำหรับ {1} ได้", "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่", - "Default": "ค่าเริ่มต้น" + "Default": "ค่าเริ่มต้น", + "TaskCleanActivityLogDescription": "ลบบันทึกกิจกรรมที่เก่ากว่าค่าที่กำหนดไว้", + "TaskCleanActivityLog": "ล้างบันทึกกิจกรรม", + "Undefined": "ไม่ได้กำหนด", + "Forced": "บังคับใช้" } diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json index b6073bf6a8..5a2069df56 100644 --- a/Emby.Server.Implementations/Localization/Core/uk.json +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -1,5 +1,5 @@ { - "MusicVideos": "Музичні кліпи", + "MusicVideos": "Музичні відеокліпи", "Music": "Музика", "Movies": "Фільми", "MessageApplicationUpdatedTo": "Jellyfin Server оновлено до версії {0}", @@ -113,7 +113,7 @@ "MessageNamedServerConfigurationUpdatedWithValue": "Розділ конфігурації сервера {0} оновлено", "Inherit": "Успадкувати", "HeaderRecordingGroups": "Групи запису", - "Forced": "Примусово", + "Forced": "Форсовані", "TaskCleanActivityLogDescription": "Видаляє старші за встановлений термін записи з журналу активності.", "TaskCleanActivityLog": "Очистити журнал активності", "Undefined": "Не визначено", diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index affb0e099b..c3b223f631 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -37,7 +37,7 @@ "MixedContent": "混合內容", "Movies": "電影", "Music": "音樂", - "MusicVideos": "音樂MV", + "MusicVideos": "音樂錄影帶", "NameInstallFailed": "{0} 安裝失敗", "NameSeasonNumber": "第 {0} 季", "NameSeasonUnknown": "未知季數", diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 2fdc2b4d91..dd5dee1d16 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -7,6 +9,7 @@ using System.Linq; using System.Reflection; using System.Text.Json; using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; @@ -72,8 +75,7 @@ namespace Emby.Server.Implementations.Localization using (var str = _assembly.GetManifestResourceStream(resource)) using (var reader = new StreamReader(str)) { - string line; - while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { if (string.IsNullOrWhiteSpace(line)) { @@ -118,10 +120,8 @@ namespace Emby.Server.Implementations.Localization using (var stream = _assembly.GetManifestResourceStream(ResourcePath)) using (var reader = new StreamReader(stream)) { - while (!reader.EndOfStream) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(line)) { continue; @@ -179,7 +179,7 @@ namespace Emby.Server.Implementations.Localization /// public IEnumerable GetCountries() { - StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json")); + using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json")); return JsonSerializer.Deserialize>(reader.ReadToEnd(), _jsonOptions); } @@ -315,10 +315,9 @@ namespace Emby.Server.Implementations.Localization } const string Prefix = "Core"; - var key = Prefix + culture; return _dictionaries.GetOrAdd( - key, + culture, f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); } diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 031b5d2e72..8aaa1f7bbe 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 0781a0e333..1377286168 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 4e25768cf6..a8b18d2925 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 932f721ab4..9a1ca99467 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -215,7 +217,7 @@ namespace Emby.Server.Implementations.Playlists // Create a list of the new linked children to add to the playlist var childrenToAdd = newItems - .Select(i => LinkedChild.Create(i)) + .Select(LinkedChild.Create) .ToList(); // Log duplicates that have been ignored, if any diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 3a8296455b..48281b75f6 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Globalization; @@ -44,12 +42,7 @@ namespace Emby.Server.Implementations.Plugins { get { - if (_httpClientFactory == null) - { - _httpClientFactory = _appHost.Resolve(); - } - - return _httpClientFactory; + return _httpClientFactory ?? (_httpClientFactory = _appHost.Resolve()); } } @@ -166,9 +159,7 @@ namespace Emby.Server.Implementations.Plugins /// public void CreatePlugins() { - _ = _appHost.GetExports(CreatePluginInstance) - .Where(i => i != null) - .ToArray(); + _ = _appHost.GetExports(CreatePluginInstance); } /// @@ -278,11 +269,7 @@ namespace Emby.Server.Implementations.Plugins // If no version is given, return the current instance. var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); - plugin = plugins.FirstOrDefault(p => p.Instance != null); - if (plugin == null) - { - plugin = plugins.OrderByDescending(p => p.Version).FirstOrDefault(); - } + plugin = plugins.FirstOrDefault(p => p.Instance != null) ?? plugins.OrderByDescending(p => p.Version).FirstOrDefault(); } else { diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 22739a0081..7cfd1fced9 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Globalization; @@ -257,20 +259,17 @@ namespace Emby.Server.Implementations.QuickConnect } // Expire stale connection requests - var code = string.Empty; - var values = _currentRequests.Values.ToList(); - - for (int i = 0; i < values.Count; i++) + foreach (var (_, currentRequest) in _currentRequests) { - var added = values[i].DateAdded ?? DateTime.UnixEpoch; - if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll) + var added = currentRequest.DateAdded ?? DateTime.UnixEpoch; + if (expireAll || DateTime.UtcNow > added.AddMinutes(Timeout)) { - code = values[i].Code; - _logger.LogDebug("Removing expired request {code}", code); + var code = currentRequest.Code; + _logger.LogDebug("Removing expired request {Code}", code); if (!_currentRequests.TryRemove(code, out _)) { - _logger.LogWarning("Request {code} already expired", code); + _logger.LogWarning("Request {Code} already expired", code); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 9c0e927058..ccbd4289ea 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -301,12 +303,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { get { - if (_id == null) - { - _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); - } - - return _id; + return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); } } @@ -348,9 +345,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { var trigger = (ITaskTrigger)sender; - var configurableTask = ScheduledTask as IConfigurableScheduledTask; - - if (configurableTask != null && !configurableTask.IsEnabled) + if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled) { return; } diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index af316e1083..4f0df75bf8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 2312c85d97..baeb86a221 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -140,8 +140,10 @@ namespace Emby.Server.Implementations.ScheduledTasks previouslyFailedImages.Add(key); var parentPath = Path.GetDirectoryName(failHistoryPath); - - Directory.CreateDirectory(parentPath); + if (parentPath != null) + { + Directory.CreateDirectory(parentPath); + } string text = string.Join('|', previouslyFailedImages); File.WriteAllText(failHistoryPath, text); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs index 4abbf784b2..50ba9bc899 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -75,4 +75,4 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Enumerable.Empty(); } } -} \ No newline at end of file +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 3b40320ab8..3b63536a42 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index b04fd7c7e2..e13782fe02 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index 7cd5493da8..ced14195bb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 0c0ebec082..a67f940b75 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 4bc12f44a4..e8eac315b6 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -289,7 +291,7 @@ namespace Emby.Server.Implementations.Security return result; } - private static AuthenticationInfo Get(IReadOnlyList reader) + private static AuthenticationInfo Get(IReadOnlyList reader) { var info = new AuthenticationInfo { @@ -297,50 +299,49 @@ namespace Emby.Server.Implementations.Security AccessToken = reader[1].ToString() }; - if (reader[2].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(2, out var deviceId)) { - info.DeviceId = reader[2].ToString(); + info.DeviceId = deviceId; } - if (reader[3].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(3, out var appName)) { - info.AppName = reader[3].ToString(); + info.AppName = appName; } - if (reader[4].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(4, out var appVersion)) { - info.AppVersion = reader[4].ToString(); + info.AppVersion = appVersion; } - if (reader[5].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(6, out var userId)) { - info.DeviceName = reader[5].ToString(); + info.UserId = new Guid(userId); } - if (reader[6].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(7, out var userName)) { - info.UserId = new Guid(reader[6].ToString()); - } - - if (reader[7].SQLiteType != SQLiteType.Null) - { - info.UserName = reader[7].ToString(); + info.UserName = userName; } info.DateCreated = reader[8].ReadDateTime(); - if (reader[9].SQLiteType != SQLiteType.Null) + if (reader.TryReadDateTime(9, out var dateLastActivity)) { - info.DateLastActivity = reader[9].ReadDateTime(); + info.DateLastActivity = dateLastActivity; } else { info.DateLastActivity = info.DateCreated; } - if (reader[10].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(10, out var customName)) { - info.DeviceName = reader[10].ToString(); + info.DeviceName = customName; + } + else if (reader.TryGetString(5, out var deviceName)) + { + info.DeviceName = deviceName; } return info; @@ -361,9 +362,9 @@ namespace Emby.Server.Implementations.Security foreach (var row in statement.ExecuteQuery()) { - if (row[0].SQLiteType != SQLiteType.Null) + if (row.TryGetString(0, out var customName)) { - result.CustomName = row[0].ToString(); + result.CustomName = customName; } } diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs index 27024e4e1c..8d8b82f0a4 100644 --- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -19,7 +19,9 @@ namespace Emby.Server.Implementations.Serialization new ConcurrentDictionary(); private static XmlSerializer GetSerializer(Type type) - => _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type)); + => _serializers.GetOrAdd( + type.FullName ?? throw new ArgumentException($"Invalid type {type}."), + _ => new XmlSerializer(type)); /// /// Serializes to writer. @@ -38,7 +40,7 @@ namespace Emby.Server.Implementations.Serialization /// The type. /// The stream. /// System.Object. - public object DeserializeFromStream(Type type, Stream stream) + public object? DeserializeFromStream(Type type, Stream stream) { using (var reader = XmlReader.Create(stream)) { @@ -81,7 +83,7 @@ namespace Emby.Server.Implementations.Serialization /// The type. /// The file. /// System.Object. - public object DeserializeFromFile(Type type, string file) + public object? DeserializeFromFile(Type type, string file) { using (var stream = File.OpenRead(file)) { @@ -95,7 +97,7 @@ namespace Emby.Server.Implementations.Serialization /// The type. /// The buffer. /// System.Object. - public object DeserializeFromBytes(Type type, byte[] buffer) + public object? DeserializeFromBytes(Type type, byte[] buffer) { using (var stream = new MemoryStream(buffer, 0, buffer.Length, false, true)) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6f21ec31e5..ef467da7ed 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -1475,10 +1477,7 @@ namespace Emby.Server.Implementations.Session user = _userManager.GetUserById(request.UserId); } - if (user == null) - { - user = _userManager.GetUserByName(request.Username); - } + user ??= _userManager.GetUserByName(request.Username); if (enforcePassword) { diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 39c369a01d..e9e3ca7f4b 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index a653b58c2b..ed1dfca592 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 #pragma warning disable SA1600 -#nullable enable using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 60698e803d..2b0ab536f9 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 7657cc74e1..42e644970c 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as IHasAlbumArtist; diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs index 7dfdd9ecff..1db3f5e9ca 100644 --- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as Audio; diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs index 756d3c5b6c..98bee3fd9f 100644 --- a/Emby.Server.Implementations/Sorting/ArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting public string Name => ItemSortBy.Artist; /// - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { if (!(x is Audio audio)) { diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs index 980954ba03..5f142fa4bb 100644 --- a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs index fa136c36d0..d20dedc2d4 100644 --- a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -15,14 +15,14 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } - private static float GetValue(BaseItem x) + private static float GetValue(BaseItem? x) { - return x.CriticRating ?? 0; + return x?.CriticRating ?? 0; } /// diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs index cbca300d2f..d3f10f78cb 100644 --- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 03ff19d21c..b1cb123ce1 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 16bd2aff86..08a44319f2 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index 0c4e82d011..73e628cf75 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs index a35192eff8..3c5ddeefaa 100644 --- a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -30,9 +30,9 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { - return x.IsFolder ? 0 : 1; + return x?.IsFolder ?? true ? 0 : 1; } } } diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index d95948406f..7d77a8bc5a 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index 1632c5a7a9..926835f906 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index da020d8d8e..4de81a69e3 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index 76bb798b5f..a81f78ebfc 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index 5c28303229..04e4865cbf 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs index 92ac04dc66..c98f97bf1e 100644 --- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetDate(x).CompareTo(GetDate(y)); } @@ -26,8 +26,13 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// DateTime. - private static DateTime GetDate(BaseItem x) + private static DateTime GetDate(BaseItem? x) { + if (x == null) + { + return DateTime.MinValue; + } + if (x.PremiereDate.HasValue) { return x.PremiereDate.Value; diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs index e2857df0b9..df9f9957d6 100644 --- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs +++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -25,8 +25,13 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// DateTime. - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { + if (x == null) + { + return 0; + } + if (x.ProductionYear.HasValue) { return x.ProductionYear.Value; diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs index 7739d04182..af3bc27508 100644 --- a/Emby.Server.Implementations/Sorting/RandomComparer.cs +++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return Guid.NewGuid().CompareTo(Guid.NewGuid()); } diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs index dde44333d5..1293153031 100644 --- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs +++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index b9205ee076..4123a59f8b 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs index f745e193b4..8d30716d39 100644 --- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/StartDateComparer.cs b/Emby.Server.Implementations/Sorting/StartDateComparer.cs index 558a3d3513..c3df7c47e6 100644 --- a/Emby.Server.Implementations/Sorting/StartDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/StartDateComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index 5766dc542b..01445c5254 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index 7c2ad2477a..12efff261b 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index aee959c53c..993456196d 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -87,7 +89,7 @@ namespace Emby.Server.Implementations.SyncPlay _sessionManager = sessionManager; _libraryManager = libraryManager; _logger = loggerFactory.CreateLogger(); - _sessionManager.SessionControllerConnected += OnSessionControllerConnected; + _sessionManager.SessionEnded += OnSessionEnded; } /// @@ -269,14 +271,17 @@ namespace Emby.Server.Implementations.SyncPlay var user = _userManager.GetUserById(session.UserId); List list = new List(); - foreach (var group in _groups.Values) + lock (_groupsLock) { - // Locking required as group is not thread-safe. - lock (group) + foreach (var (_, group) in _groups) { - if (group.HasAccessToPlayQueue(user)) + // Locking required as group is not thread-safe. + lock (group) { - list.Add(group.GetInfo()); + if (group.HasAccessToPlayQueue(user)) + { + list.Add(group.GetInfo()); + } } } } @@ -352,18 +357,18 @@ namespace Emby.Server.Implementations.SyncPlay return; } - _sessionManager.SessionControllerConnected -= OnSessionControllerConnected; + _sessionManager.SessionEnded -= OnSessionEnded; _disposed = true; } - private void OnSessionControllerConnected(object sender, SessionEventArgs e) + private void OnSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; if (_sessionToGroupMap.TryGetValue(session.Id, out var group)) { - var request = new JoinGroupRequest(group.GroupId); - JoinGroup(session, request, CancellationToken.None); + var leaveGroupRequest = new LeaveGroupRequest(); + LeaveGroup(session, leaveGroupRequest, CancellationToken.None); } } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index d3f6fa34d8..a837f09cac 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -43,9 +45,7 @@ namespace Emby.Server.Implementations.TV string presentationUniqueKey = null; if (!string.IsNullOrEmpty(request.SeriesId)) { - var series = _libraryManager.GetItemById(request.SeriesId) as Series; - - if (series != null) + if (_libraryManager.GetItemById(request.SeriesId) is Series series) { presentationUniqueKey = GetUniqueSeriesKey(series); } @@ -95,9 +95,7 @@ namespace Emby.Server.Implementations.TV int? limit = null; if (!string.IsNullOrEmpty(request.SeriesId)) { - var series = _libraryManager.GetItemById(request.SeriesId) as Series; - - if (series != null) + if (_libraryManager.GetItemById(request.SeriesId) is Series series) { presentationUniqueKey = GetUniqueSeriesKey(series); limit = 1; diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index db5265e79d..750f001680 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 653b1381b9..2351b7d8c6 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #nullable enable using System; diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs index 7d68aecf99..392498c530 100644 --- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs @@ -77,8 +77,9 @@ namespace Jellyfin.Api.Auth return false; } - var ip = _httpContextAccessor.HttpContext.GetNormalizedRemoteIp(); - var isInLocalNetwork = _networkManager.IsInLocalNetwork(ip); + var isInLocalNetwork = _httpContextAccessor.HttpContext != null + && _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIp()); + // User cannot access remotely and user is remote if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !isInLocalNetwork) { diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 4b2e5e7ea6..85d7c50d34 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -77,6 +77,8 @@ namespace Jellyfin.Api.Controllers /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. + /// Optional. Specify one or more sort orders, comma delimited. + /// Sort Order - Ascending,Descending. /// Optional, include image information in output. /// Total record count. /// Artists returned. @@ -112,6 +114,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { @@ -150,7 +154,8 @@ namespace Jellyfin.Api.Controllers MinCommunityRating = minCommunityRating, DtoOptions = dtoOptions, SearchTerm = searchTerm, - EnableTotalRecordCount = enableTotalRecordCount + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) }; if (parentId.HasValue) diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs index 049a4bed7b..b6309baab0 100644 --- a/Jellyfin.Api/Controllers/ConfigurationController.cs +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel.DataAnnotations; using System.Net.Mime; using System.Text.Json; @@ -94,6 +95,11 @@ namespace Jellyfin.Api.Controllers { var configurationType = _configurationManager.GetConfigurationType(key); var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false); + if (configuration == null) + { + throw new ArgumentException("Body doesn't contain a valid configuration"); + } + _configurationManager.SaveConfiguration(key, configuration); return NoContent(); } diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index b4154b361e..555062e556 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -28,7 +28,6 @@ using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -545,7 +544,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { - var cancellationTokenSource = new CancellationTokenSource(); + using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new VideoRequestDto { Id = itemId, @@ -710,7 +709,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { - var cancellationTokenSource = new CancellationTokenSource(); + using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new StreamingRequestDto { Id = itemId, @@ -1138,7 +1137,7 @@ namespace Jellyfin.Api.Controllers var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase); var hlsVersion = isHlsInFmp4 ? "7" : "3"; - var builder = new StringBuilder(); + var builder = new StringBuilder(128); builder.AppendLine("#EXTM3U") .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD") @@ -1191,7 +1190,7 @@ namespace Jellyfin.Api.Controllers throw new ArgumentException("StartTimeTicks is not allowed."); } - var cancellationTokenSource = new CancellationTokenSource(); + using var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; using var state = await StreamingHelpers.GetStreamingState( @@ -1208,7 +1207,7 @@ namespace Jellyfin.Api.Controllers _deviceManager, _transcodingJobHelper, TranscodingJobType, - cancellationTokenSource.Token) + cancellationToken) .ConfigureAwait(false); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); @@ -1227,7 +1226,7 @@ namespace Jellyfin.Api.Controllers } var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath); - await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false); var released = false; var startTranscoding = false; @@ -1323,24 +1322,28 @@ namespace Jellyfin.Api.Controllers return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); } - private double[] GetSegmentLengths(StreamState state) + private static double[] GetSegmentLengths(StreamState state) + => GetSegmentLengthsInternal(state.RunTimeTicks ?? 0, state.SegmentLength); + + internal static double[] GetSegmentLengthsInternal(long runtimeTicks, int segmentlength) { - var result = new List(); + var segmentLengthTicks = TimeSpan.FromSeconds(segmentlength).Ticks; + var wholeSegments = runtimeTicks / segmentLengthTicks; + var remainingTicks = runtimeTicks % segmentLengthTicks; - var ticks = state.RunTimeTicks ?? 0; - - var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks; - - while (ticks > 0) + var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1); + var segments = new double[segmentsLen]; + for (int i = 0; i < wholeSegments; i++) { - var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks; - - result.Add(TimeSpan.FromTicks(length).TotalSeconds); - - ticks -= length; + segments[i] = segmentlength; } - return result.ToArray(); + if (remainingTicks != 0) + { + segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds; + } + + return segments; } private string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding, int startNumber) @@ -1376,18 +1379,13 @@ namespace Jellyfin.Api.Controllers } else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)) { - var outputFmp4HeaderArg = string.Empty; - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - if (isWindows) + var outputFmp4HeaderArg = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) switch { // on Windows, the path of fmp4 header file needs to be configured - outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\""; - } - else - { + true => " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"", // on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder - outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""; - } + false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\"" + }; segmentFormat = "fmp4" + outputFmp4HeaderArg; } @@ -1762,9 +1760,9 @@ namespace Jellyfin.Api.Controllers private static FileSystemMetadata? GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem) { - var folder = Path.GetDirectoryName(playlist); + var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException("Path can't be a root directory.", nameof(playlist)); - var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty; + var filePrefix = Path.GetFileNameWithoutExtension(playlist); try { diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 7bcf4674c6..5aa457153c 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -63,6 +63,8 @@ namespace Jellyfin.Api.Controllers /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. + /// Optional. Specify one or more sort orders, comma delimited. + /// Sort Order - Ascending,Descending. /// Optional, include image information in output. /// Optional. Include total record count. /// Genres returned. @@ -84,6 +86,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { @@ -107,7 +111,8 @@ namespace Jellyfin.Api.Controllers NameStartsWithOrGreater = nameStartsWithOrGreater, DtoOptions = dtoOptions, SearchTerm = searchTerm, - EnableTotalRecordCount = enableTotalRecordCount + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) }; if (parentId.HasValue) diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index dabd4deb79..9fa307858f 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -237,48 +237,6 @@ namespace Jellyfin.Api.Controllers return Ok(results); } - /// - /// Gets a remote image. - /// - /// The image url. - /// The provider name. - /// Remote image retrieved. - /// - /// A that represents the asynchronous operation to get the remote search results. - /// The task result contains an containing the images file stream. - /// - [HttpGet("Items/RemoteSearch/Image")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesImageFile] - public async Task GetRemoteSearchImage( - [FromQuery, Required] string imageUrl, - [FromQuery, Required] string providerName) - { - var urlHash = imageUrl.GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); - - try - { - var contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - if (System.IO.File.Exists(contentPath)) - { - return PhysicalFile(contentPath, MimeTypes.GetMimeType(contentPath)); - } - } - catch (FileNotFoundException) - { - // Means the file isn't cached yet - } - catch (IOException) - { - // Means the file isn't cached yet - } - - await DownloadImage(providerName, imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - var updatedContentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - return PhysicalFile(updatedContentPath, MimeTypes.GetMimeType(updatedContentPath)); - } - /// /// Applies search criteria to an item and refreshes metadata. /// @@ -320,54 +278,5 @@ namespace Jellyfin.Api.Controllers return NoContent(); } - - /// - /// Downloads the image. - /// - /// Name of the provider. - /// The URL. - /// The URL hash. - /// The pointer cache path. - /// Task. - private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath) - { - using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false); - if (result.Content.Headers.ContentType?.MediaType == null) - { - throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType)); - } - - var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1]; - var fullCachePath = GetFullCachePath(urlHash + "." + ext); - - var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); - Directory.CreateDirectory(directory); - using (var stream = result.Content) - { - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var fileStream = new FileStream( - fullCachePath, - FileMode.Create, - FileAccess.Write, - FileShare.None, - IODefaults.FileStreamBufferSize, - true); - - await stream.CopyToAsync(fileStream).ConfigureAwait(false); - } - - var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath)); - - Directory.CreateDirectory(pointerCacheDirectory); - await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false); - } - - /// - /// Gets the full cache path. - /// - /// The filename. - /// System.String. - private string GetFullCachePath(string filename) - => Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); } } diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 1d4bbe61e8..4ed15e1d51 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -600,7 +600,7 @@ namespace Jellyfin.Api.Controllers { foreach (var item in dto.Updates) { - _libraryMonitor.ReportFileSystemChanged(item.Path); + _libraryMonitor.ReportFileSystemChanged(item.Path ?? throw new ArgumentException("Item path can't be null.")); } return NoContent(); diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index d0a2358ae2..010a3b19a7 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers @@ -300,9 +301,8 @@ namespace Jellyfin.Api.Controllers private IEnumerable GetActors(IEnumerable items) { - var people = _libraryManager.GetPeople(new InternalPeopleQuery + var people = _libraryManager.GetPeople(new InternalPeopleQuery(Array.Empty(), new[] { PersonType.Director }) { - ExcludePersonTypes = new[] { PersonType.Director }, MaxListOrder = 3 }); @@ -316,10 +316,9 @@ namespace Jellyfin.Api.Controllers private IEnumerable GetDirectors(IEnumerable items) { - var people = _libraryManager.GetPeople(new InternalPeopleQuery - { - PersonTypes = new[] { PersonType.Director } - }); + var people = _libraryManager.GetPeople(new InternalPeopleQuery( + new[] { PersonType.Director }, + Array.Empty())); var itemIds = items.Select(i => i.Id).ToList(); diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 7f7058b5ef..27eec2b9a8 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -63,6 +63,8 @@ namespace Jellyfin.Api.Controllers /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. + /// Optional. Specify one or more sort orders, comma delimited. + /// Sort Order - Ascending,Descending. /// Optional, include image information in output. /// Optional. Include total record count. /// Music genres returned. @@ -84,6 +86,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { @@ -107,7 +111,8 @@ namespace Jellyfin.Api.Controllers NameStartsWithOrGreater = nameStartsWithOrGreater, DtoOptions = dtoOptions, SearchTerm = searchTerm, - EnableTotalRecordCount = enableTotalRecordCount + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) }; if (parentId.HasValue) diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 70a94e27cb..b98307f879 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -94,10 +94,10 @@ namespace Jellyfin.Api.Controllers } var isFavoriteInFilters = filters.Any(f => f == ItemFilter.IsFavorite); - var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery + var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery( + personTypes, + excludePersonTypes) { - PersonTypes = personTypes, - ExcludePersonTypes = excludePersonTypes, NameContains = searchTerm, User = user, IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite, diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index adec86a102..7a61307195 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -207,12 +207,7 @@ namespace Jellyfin.Api.Controllers var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)); // Select the un-instanced one first. - var plugin = plugins.FirstOrDefault(p => p.Instance == null); - if (plugin == null) - { - // Then by the status. - plugin = plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); - } + var plugin = plugins.FirstOrDefault(p => p.Instance == null) ?? plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); if (plugin != null) { diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index e226adc646..ec836f43e3 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -145,58 +145,6 @@ namespace Jellyfin.Api.Controllers return Ok(_providerManager.GetRemoteImageProviderInfo(item)); } - /// - /// Gets a remote image. - /// - /// The image url. - /// Remote image returned. - /// Remote image not found. - /// Image Stream. - [HttpGet("Images/Remote")] - [Produces(MediaTypeNames.Application.Octet)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesImageFile] - public async Task GetRemoteImage([FromQuery, Required] Uri imageUrl) - { - var urlHash = imageUrl.ToString().GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); - - string? contentPath = null; - var hasFile = false; - - try - { - contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - if (System.IO.File.Exists(contentPath)) - { - hasFile = true; - } - } - catch (FileNotFoundException) - { - // The file isn't cached yet - } - catch (IOException) - { - // The file isn't cached yet - } - - if (!hasFile) - { - await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - } - - if (string.IsNullOrEmpty(contentPath)) - { - return NotFound(); - } - - var contentType = MimeTypes.GetMimeType(contentPath); - return PhysicalFile(contentPath, contentType); - } - /// /// Downloads a remote image for an item. /// diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 6c22050a79..73bdf90182 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -228,10 +228,7 @@ namespace Jellyfin.Api.Controllers itemWithImage = GetParentWithImage(item, ImageType.Thumb); } - if (itemWithImage == null) - { - itemWithImage = GetParentWithImage(item, ImageType.Thumb); - } + itemWithImage ??= GetParentWithImage(item, ImageType.Thumb); if (itemWithImage != null) { diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index dcdd8b3677..679f055bc2 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -298,9 +298,9 @@ namespace Jellyfin.Api.Controllers { Type = DlnaProfileType.Audio, Context = EncodingContext.Streaming, - Container = transcodingContainer, - AudioCodec = audioCodec, - Protocol = transcodingProtocol, + Container = transcodingContainer ?? "mp3", + AudioCodec = audioCodec ?? "mp3", + Protocol = transcodingProtocol ?? "http", BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture) } diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 8dbb6aaa58..e544d001ed 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -527,7 +527,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The dlna device profile id to utilize. /// The play session id. /// The segment container. - /// The segment lenght. + /// The segment length. /// The minimum number of segments. /// The media version id, if playing an alternate version. /// The device id of the client requesting. Used to stop encoding processes when needed. @@ -556,7 +556,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum video bit depth. /// Optional. Whether to require avc. /// Optional. Whether to deinterlace the video. - /// Optional. Whether to require a non anamporphic stream. + /// Optional. Whether to require a non anamorphic stream. /// Optional. The maximum number of audio channels to transcode. /// Optional. The limit of how many cpu cores to use. /// The live stream id. @@ -570,8 +570,8 @@ namespace Jellyfin.Api.Controllers /// Optional. The streaming options. /// Video stream returned. /// A containing the audio file. - [HttpGet("{itemId}/{stream=stream}.{container}")] - [HttpHead("{itemId}/{stream=stream}.{container}", Name = "HeadVideoStreamByContainer")] + [HttpGet("{itemId}/stream.{container}")] + [HttpHead("{itemId}/stream.{container}", Name = "HeadVideoStreamByContainer")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesVideoFile] public Task GetVideoStreamByContainer( diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index 18e23fb5c8..d0666034e9 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -118,10 +118,7 @@ namespace Jellyfin.Api.Helpers /// The playlist text as a string. public static string GetLivePlaylistText(string path, StreamState state) { - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var reader = new StreamReader(stream); - - var text = reader.ReadToEnd(); + var text = File.ReadAllText(path); var segmentFormat = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)) diff --git a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs index 8bddf00d5f..963e177245 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs @@ -71,7 +71,8 @@ namespace Jellyfin.Api.Helpers /// A . public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) { - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token; + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken); + cancellationToken = linkedCancellationTokenSource.Token; try { diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 9218cb2020..8cffe9c4c9 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -116,14 +116,14 @@ namespace Jellyfin.Api.Helpers if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec)) { state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); - state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToAudioCodec(i)) + state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec) ?? state.SupportedAudioCodecs.FirstOrDefault(); } if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec)) { state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); - state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToSubtitleCodec(i)) + state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleCodec) ?? state.SupportedSubtitleCodecs.FirstOrDefault(); } @@ -292,10 +292,7 @@ namespace Jellyfin.Api.Helpers } } - if (profile == null) - { - profile = dlnaManager.GetDefaultProfile(); - } + profile ??= dlnaManager.GetDefaultProfile(); var audioCodec = state.ActualOutputAudioCodec; diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 0879cbd18f..b4db0c83e9 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -22,7 +22,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Helpers @@ -380,7 +379,9 @@ namespace Jellyfin.Api.Helpers /// The output file path. private void DeleteHlsPartialStreamFiles(string outputFilePath) { - var directory = Path.GetDirectoryName(outputFilePath); + var directory = Path.GetDirectoryName(outputFilePath) + ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath)); + var name = Path.GetFileNameWithoutExtension(outputFilePath); var filesToDelete = _fileSystem.GetFilePaths(directory) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index d6dc5a2dc3..eb9fc4f14c 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -15,10 +15,10 @@ - + - - + + diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs index e33e552edb..7b32d76ba7 100644 --- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs +++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs @@ -145,7 +145,8 @@ namespace Jellyfin.Api.Models.PlaybackDtos var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var downloadPositionTicks = job.DownloadPositionTicks ?? 0; - var path = job.Path; + var path = job.Path ?? throw new ArgumentException("Path can't be null."); + var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs index 7974d3addb..befc4ca02c 100644 --- a/Jellyfin.Data/Entities/AccessSchedule.cs +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -26,20 +26,20 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id of this instance. + /// Gets the id of this instance. /// /// /// Identity, Indexed, Required. /// [XmlIgnore] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the id of the associated user. + /// Gets the id of the associated user. /// [XmlIgnore] - public Guid UserId { get; protected set; } + public Guid UserId { get; private set; } /// /// Gets or sets the day of week. diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs index e4534e8b5e..1d1b86552f 100644 --- a/Jellyfin.Data/Entities/ActivityLog.cs +++ b/Jellyfin.Data/Entities/ActivityLog.cs @@ -38,11 +38,10 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the identity of this instance. - /// This is the key in the backing database. + /// Gets the identity of this instance. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -120,7 +119,7 @@ namespace Jellyfin.Data.Entities /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs index de37fb5446..b07b7c7315 100644 --- a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs @@ -27,13 +27,13 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the Id. + /// Gets the Id. /// /// /// Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the user Id. diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index 64cd6812a4..646961238b 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -1,6 +1,4 @@ -#pragma warning disable CA2227 - -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -35,13 +33,13 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the Id. + /// Gets the Id. /// /// /// Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the user Id. @@ -145,8 +143,8 @@ namespace Jellyfin.Data.Entities public string? TvHome { get; set; } /// - /// Gets or sets the home sections. + /// Gets the home sections. /// - public virtual ICollection HomeSections { get; protected set; } + public virtual ICollection HomeSections { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index b14e22b7b3..14da0bb155 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -33,12 +31,12 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id of this group. + /// Gets the id of this group. /// /// /// Identity, Indexed, Required. /// - public Guid Id { get; protected set; } + public Guid Id { get; private set; } /// /// Gets or sets the group's name. @@ -52,17 +50,17 @@ namespace Jellyfin.Data.Entities /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the group's permissions. + /// Gets a collection containing the group's permissions. /// - public virtual ICollection Permissions { get; protected set; } + public virtual ICollection Permissions { get; private set; } /// - /// Gets or sets a collection containing the group's preferences. + /// Gets a collection containing the group's preferences. /// - public virtual ICollection Preferences { get; protected set; } + public virtual ICollection Preferences { get; private set; } /// public bool HasPermission(PermissionKind kind) diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs index d03d0f7a86..e194aa537c 100644 --- a/Jellyfin.Data/Entities/HomeSection.cs +++ b/Jellyfin.Data/Entities/HomeSection.cs @@ -9,13 +9,13 @@ namespace Jellyfin.Data.Entities public class HomeSection { /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity. Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the Id of the associated display preferences. diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs index e0c37047d8..b5c7a1c120 100644 --- a/Jellyfin.Data/Entities/ImageInfo.cs +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -20,18 +20,18 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the user id. + /// Gets the user id. /// - public Guid? UserId { get; protected set; } + public Guid? UserId { get; private set; } /// /// Gets or sets the path of the image. diff --git a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs index 4bfeb2fa30..948126d0a3 100644 --- a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs @@ -29,13 +29,13 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the Id. + /// Gets the id. /// /// /// Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the user Id. diff --git a/Jellyfin.Data/Entities/Libraries/Artwork.cs b/Jellyfin.Data/Entities/Libraries/Artwork.cs index 84a524de2c..923525fc52 100644 --- a/Jellyfin.Data/Entities/Libraries/Artwork.cs +++ b/Jellyfin.Data/Entities/Libraries/Artwork.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -30,13 +28,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the path. @@ -58,7 +56,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Book.cs b/Jellyfin.Data/Entities/Libraries/Book.cs index aea3d58d58..a838686d05 100644 --- a/Jellyfin.Data/Entities/Libraries/Book.cs +++ b/Jellyfin.Data/Entities/Libraries/Book.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -21,11 +19,11 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets a collection containing the metadata for this book. + /// Gets a collection containing the metadata for this book. /// - public virtual ICollection BookMetadata { get; protected set; } + public virtual ICollection BookMetadata { get; private set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs index 1ff4327b01..4a350d200e 100644 --- a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -26,9 +24,9 @@ namespace Jellyfin.Data.Entities.Libraries public long? Isbn { get; set; } /// - /// Gets or sets a collection of the publishers for this book. + /// Gets a collection of the publishers for this book. /// - public virtual ICollection Publishers { get; protected set; } + public virtual ICollection Publishers { get; private set; } /// public ICollection Companies => Publishers; diff --git a/Jellyfin.Data/Entities/Libraries/Chapter.cs b/Jellyfin.Data/Entities/Libraries/Chapter.cs index 11f53ae20a..3d81f713d6 100644 --- a/Jellyfin.Data/Entities/Libraries/Chapter.cs +++ b/Jellyfin.Data/Entities/Libraries/Chapter.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -29,13 +27,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -74,7 +72,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; protected set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Collection.cs b/Jellyfin.Data/Entities/Libraries/Collection.cs index 4253b7eccf..7de6019692 100644 --- a/Jellyfin.Data/Entities/Libraries/Collection.cs +++ b/Jellyfin.Data/Entities/Libraries/Collection.cs @@ -1,5 +1,4 @@ #pragma warning disable CA1711 // Identifiers should not have incorrect suffix -#pragma warning disable CA2227 using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -22,13 +21,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -42,12 +41,12 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing this collection's items. + /// Gets a collection containing this collection's items. /// - public virtual ICollection Items { get; protected set; } + public virtual ICollection Items { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs index e19362bdf1..0cb4716dbe 100644 --- a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs +++ b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs @@ -29,7 +29,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the library item. diff --git a/Jellyfin.Data/Entities/Libraries/Company.cs b/Jellyfin.Data/Entities/Libraries/Company.cs index 09050bb522..1abbee4458 100644 --- a/Jellyfin.Data/Entities/Libraries/Company.cs +++ b/Jellyfin.Data/Entities/Libraries/Company.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -22,27 +20,27 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the metadata. + /// Gets a collection containing the metadata. /// - public virtual ICollection CompanyMetadata { get; protected set; } + public virtual ICollection CompanyMetadata { get; private set; } /// - /// Gets or sets a collection containing this company's child companies. + /// Gets a collection containing this company's child companies. /// - public virtual ICollection ChildCompanies { get; protected set; } + public virtual ICollection ChildCompanies { get; private set; } /// public ICollection Companies => ChildCompanies; diff --git a/Jellyfin.Data/Entities/Libraries/CustomItem.cs b/Jellyfin.Data/Entities/Libraries/CustomItem.cs index 88d1a0c258..e27d01d860 100644 --- a/Jellyfin.Data/Entities/Libraries/CustomItem.cs +++ b/Jellyfin.Data/Entities/Libraries/CustomItem.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -21,11 +19,11 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets a collection containing the metadata for this item. + /// Gets a collection containing the metadata for this item. /// - public virtual ICollection CustomItemMetadata { get; protected set; } + public virtual ICollection CustomItemMetadata { get; private set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Episode.cs b/Jellyfin.Data/Entities/Libraries/Episode.cs index 458c7d9f5a..ce2f0c6178 100644 --- a/Jellyfin.Data/Entities/Libraries/Episode.cs +++ b/Jellyfin.Data/Entities/Libraries/Episode.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -26,11 +24,11 @@ namespace Jellyfin.Data.Entities.Libraries public int? EpisodeNumber { get; set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } /// - /// Gets or sets a collection containing the metadata for this episode. + /// Gets a collection containing the metadata for this episode. /// - public virtual ICollection EpisodeMetadata { get; protected set; } + public virtual ICollection EpisodeMetadata { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Genre.cs b/Jellyfin.Data/Entities/Libraries/Genre.cs index 9f3d650283..3b822ee828 100644 --- a/Jellyfin.Data/Entities/Libraries/Genre.cs +++ b/Jellyfin.Data/Entities/Libraries/Genre.cs @@ -19,13 +19,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -39,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; protected set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs index d12e011a8b..d429a90c61 100644 --- a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -43,13 +41,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the title. @@ -99,12 +97,12 @@ namespace Jellyfin.Data.Entities.Libraries public DateTimeOffset? ReleaseDate { get; set; } /// - /// Gets or sets the date added. + /// Gets the date added. /// /// /// Required. /// - public DateTime DateAdded { get; protected set; } + public DateTime DateAdded { get; private set; } /// /// Gets or sets the date modified. @@ -114,37 +112,32 @@ namespace Jellyfin.Data.Entities.Libraries /// public DateTime DateModified { get; set; } - /// - /// Gets or sets the row version. - /// - /// - /// Required, ConcurrencyToken. - /// + /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the person roles for this item. + /// Gets a collection containing the person roles for this item. /// - public virtual ICollection PersonRoles { get; protected set; } + public virtual ICollection PersonRoles { get; private set; } /// - /// Gets or sets a collection containing the genres for this item. + /// Gets a collection containing the genres for this item. /// - public virtual ICollection Genres { get; protected set; } + public virtual ICollection Genres { get; private set; } /// - public virtual ICollection Artwork { get; protected set; } + public virtual ICollection Artwork { get; private set; } /// - /// Gets or sets a collection containing the ratings for this item. + /// Gets a collection containing the ratings for this item. /// - public virtual ICollection Ratings { get; protected set; } + public virtual ICollection Ratings { get; private set; } /// - /// Gets or sets a collection containing the metadata sources for this item. + /// Gets a collection containing the metadata sources for this item. /// - public virtual ICollection Sources { get; protected set; } + public virtual ICollection Sources { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Library.cs b/Jellyfin.Data/Entities/Libraries/Library.cs index e453849020..0db42a1c7b 100644 --- a/Jellyfin.Data/Entities/Libraries/Library.cs +++ b/Jellyfin.Data/Entities/Libraries/Library.cs @@ -21,13 +21,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -49,7 +49,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs index 67ffad944b..d889b871ed 100644 --- a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs +++ b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs @@ -21,22 +21,22 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the date this library item was added. + /// Gets the date this library item was added. /// - public DateTime DateAdded { get; protected set; } + public DateTime DateAdded { get; private set; } /// [ConcurrencyCheck] - public uint RowVersion { get; protected set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the library of this item. diff --git a/Jellyfin.Data/Entities/Libraries/MediaFile.cs b/Jellyfin.Data/Entities/Libraries/MediaFile.cs index f3e2fe6539..36e1e47776 100644 --- a/Jellyfin.Data/Entities/Libraries/MediaFile.cs +++ b/Jellyfin.Data/Entities/Libraries/MediaFile.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -33,13 +31,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the path relative to the library root. @@ -61,12 +59,12 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the streams in this file. + /// Gets a collection containing the streams in this file. /// - public virtual ICollection MediaFileStreams { get; protected set; } + public virtual ICollection MediaFileStreams { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs index ba21294fc9..e24e73ecb7 100644 --- a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs +++ b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs @@ -21,13 +21,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the stream number. @@ -39,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs index fb2587882d..b271960787 100644 --- a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs +++ b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs @@ -25,13 +25,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -45,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs index 2a9c904c88..44c1985181 100644 --- a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs +++ b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs @@ -27,13 +27,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the provider id. @@ -47,7 +47,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the metadata provider. diff --git a/Jellyfin.Data/Entities/Libraries/Movie.cs b/Jellyfin.Data/Entities/Libraries/Movie.cs index f89cacff44..499fafd0e1 100644 --- a/Jellyfin.Data/Entities/Libraries/Movie.cs +++ b/Jellyfin.Data/Entities/Libraries/Movie.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -21,11 +19,11 @@ namespace Jellyfin.Data.Entities.Libraries } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } /// - /// Gets or sets a collection containing the metadata for this movie. + /// Gets a collection containing the metadata for this movie. /// - public virtual ICollection MovieMetadata { get; protected set; } + public virtual ICollection MovieMetadata { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs index fb181dea60..44b5f34d7f 100644 --- a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Jellyfin.Data.Interfaces; @@ -62,9 +60,9 @@ namespace Jellyfin.Data.Entities.Libraries public string? Country { get; set; } /// - /// Gets or sets the studios that produced this movie. + /// Gets the studios that produced this movie. /// - public virtual ICollection Studios { get; protected set; } + public virtual ICollection Studios { get; private set; } /// public ICollection Companies => Studios; diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs index 4049cdac8e..d6231bbf02 100644 --- a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs +++ b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; namespace Jellyfin.Data.Entities.Libraries @@ -20,13 +18,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets a collection containing the album metadata. + /// Gets a collection containing the album metadata. /// - public virtual ICollection MusicAlbumMetadata { get; protected set; } + public virtual ICollection MusicAlbumMetadata { get; private set; } /// - /// Gets or sets a collection containing the tracks. + /// Gets a collection containing the tracks. /// - public virtual ICollection Tracks { get; protected set; } + public virtual ICollection Tracks { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs index 3080bd692d..691f3504fa 100644 --- a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -51,8 +49,8 @@ namespace Jellyfin.Data.Entities.Libraries public string? Country { get; set; } /// - /// Gets or sets a collection containing the labels. + /// Gets a collection containing the labels. /// - public virtual ICollection Labels { get; protected set; } + public virtual ICollection Labels { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Person.cs b/Jellyfin.Data/Entities/Libraries/Person.cs index 159bd47be7..8b67d920d2 100644 --- a/Jellyfin.Data/Entities/Libraries/Person.cs +++ b/Jellyfin.Data/Entities/Libraries/Person.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -32,13 +30,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -61,12 +59,12 @@ namespace Jellyfin.Data.Entities.Libraries public string? SourceId { get; set; } /// - /// Gets or sets the date added. + /// Gets the date added. /// /// /// Required. /// - public DateTime DateAdded { get; protected set; } + public DateTime DateAdded { get; private set; } /// /// Gets or sets the date modified. @@ -78,12 +76,12 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a list of metadata sources for this person. + /// Gets a list of metadata sources for this person. /// - public virtual ICollection Sources { get; protected set; } + public virtual ICollection Sources { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/PersonRole.cs b/Jellyfin.Data/Entities/Libraries/PersonRole.cs index 988aa84bae..7d40bdf448 100644 --- a/Jellyfin.Data/Entities/Libraries/PersonRole.cs +++ b/Jellyfin.Data/Entities/Libraries/PersonRole.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -27,13 +25,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name of the person's role. @@ -55,7 +53,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; protected set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the person. @@ -66,12 +64,12 @@ namespace Jellyfin.Data.Entities.Libraries public virtual Person Person { get; set; } /// - public virtual ICollection Artwork { get; protected set; } + public virtual ICollection Artwork { get; private set; } /// - /// Gets or sets a collection containing the metadata sources for this person role. + /// Gets a collection containing the metadata sources for this person role. /// - public virtual ICollection Sources { get; protected set; } + public virtual ICollection Sources { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Photo.cs b/Jellyfin.Data/Entities/Libraries/Photo.cs index eb5c962675..4b459432bc 100644 --- a/Jellyfin.Data/Entities/Libraries/Photo.cs +++ b/Jellyfin.Data/Entities/Libraries/Photo.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -21,11 +19,11 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets a collection containing the photo metadata. + /// Gets a collection containing the photo metadata. /// - public virtual ICollection PhotoMetadata { get; protected set; } + public virtual ICollection PhotoMetadata { get; private set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Rating.cs b/Jellyfin.Data/Entities/Libraries/Rating.cs index 6862012a80..58c8fa49ef 100644 --- a/Jellyfin.Data/Entities/Libraries/Rating.cs +++ b/Jellyfin.Data/Entities/Libraries/Rating.cs @@ -19,13 +19,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the value. @@ -42,7 +42,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the rating type. diff --git a/Jellyfin.Data/Entities/Libraries/RatingSource.cs b/Jellyfin.Data/Entities/Libraries/RatingSource.cs index ae0d806ff0..0f3a073244 100644 --- a/Jellyfin.Data/Entities/Libraries/RatingSource.cs +++ b/Jellyfin.Data/Entities/Libraries/RatingSource.cs @@ -21,13 +21,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -57,7 +57,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the metadata source. diff --git a/Jellyfin.Data/Entities/Libraries/Release.cs b/Jellyfin.Data/Entities/Libraries/Release.cs index 21d4039793..d3d52bf5cd 100644 --- a/Jellyfin.Data/Entities/Libraries/Release.cs +++ b/Jellyfin.Data/Entities/Libraries/Release.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -31,13 +29,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -51,17 +49,17 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the media files for this release. + /// Gets a collection containing the media files for this release. /// - public virtual ICollection MediaFiles { get; protected set; } + public virtual ICollection MediaFiles { get; private set; } /// - /// Gets or sets a collection containing the chapters for this release. + /// Gets a collection containing the chapters for this release. /// - public virtual ICollection Chapters { get; protected set; } + public virtual ICollection Chapters { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Season.cs b/Jellyfin.Data/Entities/Libraries/Season.cs index 04f723a1d1..fc110b49da 100644 --- a/Jellyfin.Data/Entities/Libraries/Season.cs +++ b/Jellyfin.Data/Entities/Libraries/Season.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; namespace Jellyfin.Data.Entities.Libraries @@ -25,13 +23,13 @@ namespace Jellyfin.Data.Entities.Libraries public int? SeasonNumber { get; set; } /// - /// Gets or sets the season metadata. + /// Gets the season metadata. /// - public virtual ICollection SeasonMetadata { get; protected set; } + public virtual ICollection SeasonMetadata { get; private set; } /// - /// Gets or sets a collection containing the number of episodes. + /// Gets a collection containing the number of episodes. /// - public virtual ICollection Episodes { get; protected set; } + public virtual ICollection Episodes { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Series.cs b/Jellyfin.Data/Entities/Libraries/Series.cs index 59508831e4..0354433e08 100644 --- a/Jellyfin.Data/Entities/Libraries/Series.cs +++ b/Jellyfin.Data/Entities/Libraries/Series.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; @@ -16,7 +14,6 @@ namespace Jellyfin.Data.Entities.Libraries /// The library. public Series(Library library) : base(library) { - DateAdded = DateTime.UtcNow; Seasons = new HashSet(); SeriesMetadata = new HashSet(); } @@ -37,13 +34,13 @@ namespace Jellyfin.Data.Entities.Libraries public DateTime? FirstAired { get; set; } /// - /// Gets or sets a collection containing the series metadata. + /// Gets a collection containing the series metadata. /// - public virtual ICollection SeriesMetadata { get; protected set; } + public virtual ICollection SeriesMetadata { get; private set; } /// - /// Gets or sets a collection containing the seasons. + /// Gets a collection containing the seasons. /// - public virtual ICollection Seasons { get; protected set; } + public virtual ICollection Seasons { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs index cc04d033aa..42115802c5 100644 --- a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Jellyfin.Data.Interfaces; @@ -62,9 +60,9 @@ namespace Jellyfin.Data.Entities.Libraries public string? Country { get; set; } /// - /// Gets or sets a collection containing the networks. + /// Gets a collection containing the networks. /// - public virtual ICollection Networks { get; protected set; } + public virtual ICollection Networks { get; private set; } /// public ICollection Companies => Networks; diff --git a/Jellyfin.Data/Entities/Libraries/Track.cs b/Jellyfin.Data/Entities/Libraries/Track.cs index 86a3edff83..d354000337 100644 --- a/Jellyfin.Data/Entities/Libraries/Track.cs +++ b/Jellyfin.Data/Entities/Libraries/Track.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -26,11 +24,11 @@ namespace Jellyfin.Data.Entities.Libraries public int? TrackNumber { get; set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } /// - /// Gets or sets a collection containing the track metadata. + /// Gets a collection containing the track metadata. /// - public virtual ICollection TrackMetadata { get; protected set; } + public virtual ICollection TrackMetadata { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index f059dedfa2..6d2e68077c 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -1,5 +1,6 @@ #pragma warning disable CA1711 // Identifiers should not have incorrect suffix +using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Enums; @@ -25,21 +26,26 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id of this permission. + /// Gets the id of this permission. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the type of this permission. + /// Gets or sets the id of the associated user. + /// + public Guid? UserId { get; set; } + + /// + /// Gets the type of this permission. /// /// /// Required. /// - public PermissionKind Kind { get; protected set; } + public PermissionKind Kind { get; private set; } /// /// Gets or sets a value indicating whether the associated user has this permission. @@ -51,7 +57,7 @@ namespace Jellyfin.Data.Entities /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index a8813ab88d..a6ab275d31 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -24,21 +24,26 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id of this preference. + /// Gets the id of this preference. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the type of this preference. + /// Gets or sets the id of the associated user. + /// + public Guid? UserId { get; set; } + + /// + /// Gets the type of this preference. /// /// /// Required. /// - public PreferenceKind Kind { get; protected set; } + public PreferenceKind Kind { get; private set; } /// /// Gets or sets the value of this preference. @@ -52,7 +57,7 @@ namespace Jellyfin.Data.Entities /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 74331726cc..e309e54de9 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel; @@ -302,64 +300,54 @@ namespace Jellyfin.Data.Entities public virtual ImageInfo? ProfileImage { get; set; } /// - /// Gets or sets the user's display preferences. + /// Gets the user's display preferences. /// - /// - /// Required. - /// - public virtual ICollection DisplayPreferences { get; set; } + public virtual ICollection DisplayPreferences { get; private set; } /// /// Gets or sets the level of sync play permissions this user has. /// public SyncPlayUserAccessType SyncPlayAccess { get; set; } - /// - /// Gets or sets the row version. - /// - /// - /// Required, Concurrency Token. - /// + /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets the list of access schedules this user has. + /// Gets the list of access schedules this user has. /// - public virtual ICollection AccessSchedules { get; protected set; } + public virtual ICollection AccessSchedules { get; private set; } /// - /// Gets or sets the list of item display preferences. + /// Gets the list of item display preferences. /// - public virtual ICollection ItemDisplayPreferences { get; protected set; } + public virtual ICollection ItemDisplayPreferences { get; private set; } /* /// - /// Gets or sets the list of groups this user is a member of. + /// Gets the list of groups this user is a member of. /// - [ForeignKey("Group_Groups_Guid")] - public virtual ICollection Groups { get; protected set; } + public virtual ICollection Groups { get; private set; } */ /// - /// Gets or sets the list of permissions this user has. + /// Gets the list of permissions this user has. /// [ForeignKey("Permission_Permissions_Guid")] - public virtual ICollection Permissions { get; protected set; } + public virtual ICollection Permissions { get; private set; } /* /// - /// Gets or sets the list of provider mappings this user has. + /// Gets the list of provider mappings this user has. /// - [ForeignKey("ProviderMapping_ProviderMappings_Id")] - public virtual ICollection ProviderMappings { get; protected set; } + public virtual ICollection ProviderMappings { get; private set; } */ /// - /// Gets or sets the list of preferences this user has. + /// Gets the list of preferences this user has. /// [ForeignKey("Preference_Preferences_Guid")] - public virtual ICollection Preferences { get; protected set; } + public virtual ICollection Preferences { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Enums/HomeSectionType.cs b/Jellyfin.Data/Enums/HomeSectionType.cs index e597c9431a..9bcd097dc6 100644 --- a/Jellyfin.Data/Enums/HomeSectionType.cs +++ b/Jellyfin.Data/Enums/HomeSectionType.cs @@ -48,6 +48,11 @@ /// /// Live TV. /// - LiveTv = 8 + LiveTv = 8, + + /// + /// Continue Reading. + /// + ResumeBook = 9 } } diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index e9f9aad57d..09a3702385 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -68,7 +68,7 @@ namespace Jellyfin.Drawing.Skia /// The path at which to place the resulting collage image. /// The desired width of the collage. /// The desired height of the collage. - public void BuildSquareCollage(string[] paths, string outputPath, int width, int height) + public void BuildSquareCollage(IReadOnlyList paths, string outputPath, int width, int height) { using var bitmap = BuildSquareCollageBitmap(paths, width, height); using var outputStream = new SKFileWStream(outputPath); @@ -84,7 +84,7 @@ namespace Jellyfin.Drawing.Skia /// The desired width of the collage. /// The desired height of the collage. /// The name of the library to draw on the collage. - public void BuildThumbCollage(string[] paths, string outputPath, int width, int height, string? libraryName) + public void BuildThumbCollage(IReadOnlyList paths, string outputPath, int width, int height, string? libraryName) { using var bitmap = BuildThumbCollageBitmap(paths, width, height, libraryName); using var outputStream = new SKFileWStream(outputPath); @@ -92,7 +92,7 @@ namespace Jellyfin.Drawing.Skia pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); } - private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height, string? libraryName) + private SKBitmap BuildThumbCollageBitmap(IReadOnlyList paths, int width, int height, string? libraryName) { var bitmap = new SKBitmap(width, height); @@ -152,14 +152,14 @@ namespace Jellyfin.Drawing.Skia return bitmap; } - private SKBitmap? GetNextValidImage(string[] paths, int currentIndex, out int newIndex) + private SKBitmap? GetNextValidImage(IReadOnlyList paths, int currentIndex, out int newIndex) { var imagesTested = new Dictionary(); SKBitmap? bitmap = null; - while (imagesTested.Count < paths.Length) + while (imagesTested.Count < paths.Count) { - if (currentIndex >= paths.Length) + if (currentIndex >= paths.Count) { currentIndex = 0; } @@ -180,7 +180,7 @@ namespace Jellyfin.Drawing.Skia return bitmap; } - private SKBitmap BuildSquareCollageBitmap(string[] paths, int width, int height) + private SKBitmap BuildSquareCollageBitmap(IReadOnlyList paths, int width, int height) { var bitmap = new SKBitmap(width, height); var imageIndex = 0; diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 73e8b2cd72..4078fd1269 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -1067,7 +1067,7 @@ namespace Jellyfin.Networking.Manager } // Internal interfaces must be private, not excluded and part of the LocalNetworkSubnet. - _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i))); + _internalInterfaces = CreateCollection(_interfaceAddresses.Where(IsInLocalNetwork)); } _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString()); diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 2c6a176b69..d24c73526a 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -27,13 +27,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 39f842354f..db648472d1 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -149,21 +149,77 @@ namespace Jellyfin.Server.Implementations modelBuilder.HasDefaultSchema("jellyfin"); + // Collations + + modelBuilder.Entity() + .Property(user => user.Username) + .UseCollation("NOCASE"); + + // Delete behavior + + modelBuilder.Entity() + .HasOne(u => u.ProfileImage) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.Permissions) + .WithOne() + .HasForeignKey(p => p.UserId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.Preferences) + .WithOne() + .HasForeignKey(p => p.UserId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.AccessSchedules) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.DisplayPreferences) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.ItemDisplayPreferences) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() - .HasIndex(entity => entity.UserId) - .IsUnique(false); + .HasMany(d => d.HomeSections) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + // Indexes + + modelBuilder.Entity() + .HasIndex(entity => entity.Username) + .IsUnique(); modelBuilder.Entity() .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client }) .IsUnique(); - modelBuilder.Entity() - .HasIndex(entity => entity.UserId) - .IsUnique(false); - modelBuilder.Entity() .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client, entity.Key }) .IsUnique(); + + // Used to get a user's permissions or a specific permission for a user. + // Also prevents multiple values being created for a user. + // Filtered over non-null user ids for when other entities (groups, API keys) get permissions + modelBuilder.Entity() + .HasIndex(p => new { p.UserId, p.Kind }) + .HasFilter("[UserId] IS NOT NULL") + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(p => new { p.UserId, p.Kind }) + .HasFilter("[UserId] IS NOT NULL") + .IsUnique(); } } } diff --git a/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs new file mode 100644 index 0000000000..8696768245 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs @@ -0,0 +1,535 @@ +#pragma warning disable CS1591 + +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20210320181425_AddIndexesAndCollations")] + partial class AddIndexesAndCollations + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client", "Key") + .IsUnique(); + + b.ToTable("CustomItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChromecastVersion") + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("DashboardTheme") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("EnableNextVideoInfoOverlay") + .HasColumnType("INTEGER"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("ScrollDirection") + .HasColumnType("INTEGER"); + + b.Property("ShowBackdrop") + .HasColumnType("INTEGER"); + + b.Property("ShowSidebar") + .HasColumnType("INTEGER"); + + b.Property("SkipBackwardLength") + .HasColumnType("INTEGER"); + + b.Property("SkipForwardLength") + .HasColumnType("INTEGER"); + + b.Property("TvHome") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client") + .IsUnique(); + + b.ToTable("DisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DisplayPreferencesId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisplayPreferencesId"); + + b.ToTable("HomeSection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxActiveSessions") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null) + .WithMany("HomeSections") + .HasForeignKey("DisplayPreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ItemDisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Navigation("HomeSections"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Navigation("AccessSchedules"); + + b.Navigation("DisplayPreferences"); + + b.Navigation("ItemDisplayPreferences"); + + b.Navigation("Permissions"); + + b.Navigation("Preferences"); + + b.Navigation("ProfileImage"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.cs b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.cs new file mode 100644 index 0000000000..506e4ae661 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.cs @@ -0,0 +1,240 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class AddIndexesAndCollations : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ImageInfos_Users_UserId", + schema: "jellyfin", + table: "ImageInfos"); + + migrationBuilder.DropForeignKey( + name: "FK_Permissions_Users_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.DropForeignKey( + name: "FK_Preferences_Users_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropIndex( + name: "IX_Preferences_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropIndex( + name: "IX_Permissions_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.DropIndex( + name: "IX_DisplayPreferences_UserId", + schema: "jellyfin", + table: "DisplayPreferences"); + + migrationBuilder.DropIndex( + name: "IX_CustomItemDisplayPreferences_UserId", + schema: "jellyfin", + table: "CustomItemDisplayPreferences"); + + migrationBuilder.AlterColumn( + name: "Username", + schema: "jellyfin", + table: "Users", + type: "TEXT", + maxLength: 255, + nullable: false, + collation: "NOCASE", + oldClrType: typeof(string), + oldType: "TEXT", + oldMaxLength: 255); + + migrationBuilder.AddColumn( + name: "UserId", + schema: "jellyfin", + table: "Preferences", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "UserId", + schema: "jellyfin", + table: "Permissions", + type: "TEXT", + nullable: true); + + migrationBuilder.Sql("UPDATE Preferences SET UserId = Preference_Preferences_Guid"); + migrationBuilder.Sql("UPDATE Permissions SET UserId = Permission_Permissions_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Users_Username", + schema: "jellyfin", + table: "Users", + column: "Username", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Preferences_UserId_Kind", + schema: "jellyfin", + table: "Preferences", + columns: new[] { "UserId", "Kind" }, + unique: true, + filter: "[UserId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_UserId_Kind", + schema: "jellyfin", + table: "Permissions", + columns: new[] { "UserId", "Kind" }, + unique: true, + filter: "[UserId] IS NOT NULL"); + + migrationBuilder.AddForeignKey( + name: "FK_ImageInfos_Users_UserId", + schema: "jellyfin", + table: "ImageInfos", + column: "UserId", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Permissions_Users_UserId", + schema: "jellyfin", + table: "Permissions", + column: "UserId", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Preferences_Users_UserId", + schema: "jellyfin", + table: "Preferences", + column: "UserId", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ImageInfos_Users_UserId", + schema: "jellyfin", + table: "ImageInfos"); + + migrationBuilder.DropForeignKey( + name: "FK_Permissions_Users_UserId", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.DropForeignKey( + name: "FK_Preferences_Users_UserId", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropIndex( + name: "IX_Users_Username", + schema: "jellyfin", + table: "Users"); + + migrationBuilder.DropIndex( + name: "IX_Preferences_UserId_Kind", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropIndex( + name: "IX_Permissions_UserId_Kind", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.DropColumn( + name: "UserId", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropColumn( + name: "UserId", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.AlterColumn( + name: "Username", + schema: "jellyfin", + table: "Users", + type: "TEXT", + maxLength: 255, + nullable: false, + oldClrType: typeof(string), + oldType: "TEXT", + oldMaxLength: 255, + oldCollation: "NOCASE"); + + migrationBuilder.CreateIndex( + name: "IX_Preferences_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences", + column: "Preference_Preferences_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions", + column: "Permission_Permissions_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_DisplayPreferences_UserId", + schema: "jellyfin", + table: "DisplayPreferences", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomItemDisplayPreferences_UserId", + schema: "jellyfin", + table: "CustomItemDisplayPreferences", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_ImageInfos_Users_UserId", + schema: "jellyfin", + table: "ImageInfos", + column: "UserId", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Permissions_Users_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions", + column: "Permission_Permissions_Guid", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Preferences_Users_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences", + column: "Preference_Preferences_Guid", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 6a523ba685..286eb7468a 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -114,8 +114,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("UserId"); - b.HasIndex("UserId", "ItemId", "Client", "Key") .IsUnique(); @@ -173,8 +171,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("UserId"); - b.HasIndex("UserId", "ItemId", "Client") .IsUnique(); @@ -288,12 +284,17 @@ namespace Jellyfin.Server.Implementations.Migrations .IsConcurrencyToken() .HasColumnType("INTEGER"); + b.Property("UserId") + .HasColumnType("TEXT"); + b.Property("Value") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("Permission_Permissions_Guid"); + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); b.ToTable("Permissions"); }); @@ -314,6 +315,9 @@ namespace Jellyfin.Server.Implementations.Migrations .IsConcurrencyToken() .HasColumnType("INTEGER"); + b.Property("UserId") + .HasColumnType("TEXT"); + b.Property("Value") .IsRequired() .HasMaxLength(65535) @@ -321,7 +325,9 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("Preference_Preferences_Guid"); + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); b.ToTable("Preferences"); }); @@ -428,10 +434,14 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Username") .IsRequired() .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); + b.HasIndex("Username") + .IsUnique(); + b.ToTable("Users"); }); @@ -466,7 +476,8 @@ namespace Jellyfin.Server.Implementations.Migrations { b.HasOne("Jellyfin.Data.Entities.User", null) .WithOne("ProfileImage") - .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => @@ -482,14 +493,16 @@ namespace Jellyfin.Server.Implementations.Migrations { b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Permissions") - .HasForeignKey("Permission_Permissions_Guid"); + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => { b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Preferences") - .HasForeignKey("Preference_Preferences_Guid"); + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index a2c11cb8a8..27d4f40d30 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -144,7 +144,13 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("The new and old names must be different."); } - if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) + await using var dbContext = _dbProvider.CreateContext(); + + if (await dbContext.Users + .AsQueryable() + .Where(u => u.Username == newName && u.Id != user.Id) + .AnyAsync() + .ConfigureAwait(false)) { throw new ArgumentException(string.Format( CultureInfo.InvariantCulture, @@ -251,16 +257,6 @@ namespace Jellyfin.Server.Implementations.Users } await using var dbContext = _dbProvider.CreateContext(); - - // Clear all entities related to the user from the database. - if (user.ProfileImage != null) - { - dbContext.Remove(user.ProfileImage); - } - - dbContext.RemoveRange(user.Permissions); - dbContext.RemoveRange(user.Preferences); - dbContext.RemoveRange(user.AccessSchedules); dbContext.Users.Remove(user); await dbContext.SaveChangesAsync().ConfigureAwait(false); _users.Remove(userId); diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 3496cabe8e..f83de7ac83 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -38,8 +38,8 @@ - - + + diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 6821630db7..ee4f8b0bab 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -75,7 +75,7 @@ namespace Jellyfin.Server.Migrations.Routines { var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath)); return _defaultConfigHistory - .Select(historicalConfigText => JToken.Parse(historicalConfigText)) + .Select(JToken.Parse) .Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson)); } } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index a15a381772..96bd2ccc40 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -82,11 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name); - var config = File.Exists(Path.Combine(userDataDir, "config.xml")) - ? (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml")) + var configPath = Path.Combine(userDataDir, "config.xml"); + var config = File.Exists(configPath) + ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration() : new UserConfiguration(); - var policy = File.Exists(Path.Combine(userDataDir, "policy.xml")) - ? (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml")) + + var policyPath = Path.Combine(userDataDir, "policy.xml"); + var policy = File.Exists(policyPath) + ? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy() : new UserPolicy(); policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace( "Emby.Server.Implementations.Library", diff --git a/Jellyfin.sln b/Jellyfin.sln index 8626a4b1ba..9fbd9d266e 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -81,6 +81,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -223,6 +225,10 @@ Global {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -240,6 +246,7 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/MediaBrowser.Common/Configuration/ConfigurationStore.cs b/MediaBrowser.Common/Configuration/ConfigurationStore.cs index d31d45e4c3..050ab1ab51 100644 --- a/MediaBrowser.Common/Configuration/ConfigurationStore.cs +++ b/MediaBrowser.Common/Configuration/ConfigurationStore.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace MediaBrowser.Common.Configuration diff --git a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs index 344aecf530..2df87d8792 100644 --- a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs +++ b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index 57c6546675..1370e6d79e 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace MediaBrowser.Common.Configuration { /// diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index ec21d05803..0e20653029 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#nullable enable using System; using System.Collections.Generic; diff --git a/MediaBrowser.Common/Events/EventHelper.cs b/MediaBrowser.Common/Events/EventHelper.cs index c9d3226ac8..a9cf86fbc3 100644 --- a/MediaBrowser.Common/Events/EventHelper.cs +++ b/MediaBrowser.Common/Events/EventHelper.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Common.Events /// The sender. /// The instance containing the event data. /// The logger. - public static void QueueEventIfNotNull(EventHandler handler, object sender, EventArgs args, ILogger logger) + public static void QueueEventIfNotNull(EventHandler? handler, object sender, EventArgs args, ILogger logger) { if (handler != null) { @@ -43,7 +43,7 @@ namespace MediaBrowser.Common.Events /// The sender. /// The args. /// The logger. - public static void QueueEventIfNotNull(EventHandler handler, object sender, T args, ILogger logger) + public static void QueueEventIfNotNull(EventHandler? handler, object sender, T args, ILogger logger) { if (handler != null) { diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs index 40020093b6..08964420e7 100644 --- a/MediaBrowser.Common/Extensions/BaseExtensions.cs +++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Security.Cryptography; using System.Text; diff --git a/MediaBrowser.Common/Extensions/CopyToExtensions.cs b/MediaBrowser.Common/Extensions/CopyToExtensions.cs index 94bf7c7401..2ecbc6539b 100644 --- a/MediaBrowser.Common/Extensions/CopyToExtensions.cs +++ b/MediaBrowser.Common/Extensions/CopyToExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Collections.Generic; namespace MediaBrowser.Common.Extensions diff --git a/MediaBrowser.Common/Extensions/EnumerableExtensions.cs b/MediaBrowser.Common/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000000..2b8a6c395b --- /dev/null +++ b/MediaBrowser.Common/Extensions/EnumerableExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Static extensions for the interface. + /// + public static class EnumerableExtensions + { + /// + /// Determines whether the value is contained in the source collection. + /// + /// An instance of the interface. + /// The value to look for in the collection. + /// The string comparison. + /// A value indicating whether the value is contained in the collection. + /// The source is null. + public static bool Contains(this IEnumerable source, ReadOnlySpan value, StringComparison stringComparison) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source is IList list) + { + int len = list.Count; + for (int i = 0; i < len; i++) + { + if (value.Equals(list[i], stringComparison)) + { + return true; + } + } + + return false; + } + + foreach (string element in source) + { + if (value.Equals(element, stringComparison)) + { + return true; + } + } + + return false; + } + } +} diff --git a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs index e51ad42d12..1e5877c84c 100644 --- a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs +++ b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Common.Extensions { return (context.Connection.LocalIpAddress == null && context.Connection.RemoteIpAddress == null) - || context.Connection.LocalIpAddress.Equals(context.Connection.RemoteIpAddress); + || Equals(context.Connection.LocalIpAddress, context.Connection.RemoteIpAddress); } /// diff --git a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs index 258bd6662c..48e758ee4c 100644 --- a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs +++ b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; namespace MediaBrowser.Common.Extensions diff --git a/MediaBrowser.Common/Extensions/ProcessExtensions.cs b/MediaBrowser.Common/Extensions/ProcessExtensions.cs index 2f52ba196a..c747871222 100644 --- a/MediaBrowser.Common/Extensions/ProcessExtensions.cs +++ b/MediaBrowser.Common/Extensions/ProcessExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Diagnostics; using System.Threading; diff --git a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs index 7c7bdaa92f..95802a4626 100644 --- a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs +++ b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs index ebac9d8e6b..22130c5a1e 100644 --- a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs +++ b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; namespace MediaBrowser.Common.Extensions diff --git a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs b/MediaBrowser.Common/Extensions/ShuffleExtensions.cs index 6f0ea9bd58..2604abf85c 100644 --- a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs +++ b/MediaBrowser.Common/Extensions/ShuffleExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; diff --git a/MediaBrowser.Common/Extensions/SplitStringExtensions.cs b/MediaBrowser.Common/Extensions/SplitStringExtensions.cs new file mode 100644 index 0000000000..9c9108495f --- /dev/null +++ b/MediaBrowser.Common/Extensions/SplitStringExtensions.cs @@ -0,0 +1,95 @@ +/* +MIT License + +Copyright (c) 2019 Gérald Barré + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +#pragma warning disable CS1591 +#pragma warning disable CA1034 +using System; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Extension class for splitting lines without unnecessary allocations. + /// + public static class SplitStringExtensions + { + /// + /// Creates a new string split enumerator. + /// + /// The string to split. + /// The separator to split on. + /// The enumerator struct. + [Pure] + public static SplitEnumerator SpanSplit(this string str, char separator) => new (str.AsSpan(), separator); + + /// + /// Creates a new span split enumerator. + /// + /// The span to split. + /// The separator to split on. + /// The enumerator struct. + [Pure] + public static SplitEnumerator Split(this ReadOnlySpan str, char separator) => new (str, separator); + + [StructLayout(LayoutKind.Auto)] + public ref struct SplitEnumerator + { + private readonly char _separator; + private ReadOnlySpan _str; + + public SplitEnumerator(ReadOnlySpan str, char separator) + { + _str = str; + _separator = separator; + Current = default; + } + + public ReadOnlySpan Current { get; private set; } + + public readonly SplitEnumerator GetEnumerator() => this; + + public bool MoveNext() + { + if (_str.Length == 0) + { + return false; + } + + var span = _str; + var index = span.IndexOf(_separator); + if (index == -1) + { + _str = ReadOnlySpan.Empty; + Current = span; + return true; + } + + Current = span.Slice(0, index); + _str = span[(index + 1)..]; + return true; + } + } + } +} diff --git a/MediaBrowser.Common/Extensions/StreamExtensions.cs b/MediaBrowser.Common/Extensions/StreamExtensions.cs index cd77be7b2b..5cbf57d988 100644 --- a/MediaBrowser.Common/Extensions/StreamExtensions.cs +++ b/MediaBrowser.Common/Extensions/StreamExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Collections.Generic; using System.IO; using System.Linq; @@ -35,11 +33,11 @@ namespace MediaBrowser.Common.Extensions } /// - /// Reads all lines in the . + /// Reads all lines in the . /// - /// The to read from. + /// The to read from. /// All lines in the stream. - public static IEnumerable ReadAllLines(this StreamReader reader) + public static IEnumerable ReadAllLines(this TextReader reader) { string? line; while ((line = reader.ReadLine()) != null) @@ -47,5 +45,19 @@ namespace MediaBrowser.Common.Extensions yield return line; } } + + /// + /// Reads all lines in the . + /// + /// The to read from. + /// All lines in the stream. + public static async IAsyncEnumerable ReadAllLinesAsync(this TextReader reader) + { + string? line; + while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) + { + yield return line; + } + } } } diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index c3e4ed6db8..46d93e4940 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Reflection; diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs index 2ec702165d..127a41a06b 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -9,67 +9,16 @@ namespace MediaBrowser.Common.Json.Converters /// Convert comma delimited string to array of type. /// /// Type to convert to. - public class JsonCommaDelimitedArrayConverter : JsonConverter + public sealed class JsonCommaDelimitedArrayConverter : JsonDelimitedArrayConverter { - private readonly TypeConverter _typeConverter; - /// /// Initializes a new instance of the class. /// - public JsonCommaDelimitedArrayConverter() + public JsonCommaDelimitedArrayConverter() : base() { - _typeConverter = TypeDescriptor.GetConverter(typeof(T)); } /// - public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.String) - { - var stringEntries = reader.GetString().Split(',', StringSplitOptions.RemoveEmptyEntries); - if (stringEntries.Length == 0) - { - return Array.Empty(); - } - - var parsedValues = new object[stringEntries.Length]; - var convertedCount = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - try - { - parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()); - convertedCount++; - } - catch (FormatException) - { - // TODO log when upgraded to .Net6 - // https://github.com/dotnet/runtime/issues/42975 - // _logger.LogDebug(e, "Error converting value."); - } - } - - var typedValues = new T[convertedCount]; - var typedValueIndex = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - if (parsedValues[i] != null) - { - typedValues.SetValue(parsedValues[i], typedValueIndex); - typedValueIndex++; - } - } - - return typedValues; - } - - return JsonSerializer.Deserialize(ref reader, options); - } - - /// - public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } + protected override char Delimiter => ','; } } diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs index 24ed3ea19e..de41348dda 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs @@ -19,10 +19,10 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0]; - return (JsonConverter)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType)); + return (JsonConverter?)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType)); } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs new file mode 100644 index 0000000000..b691798c98 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs @@ -0,0 +1,81 @@ +using System; +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Convert delimited string to array of type. + /// + /// Type to convert to. + public abstract class JsonDelimitedArrayConverter : JsonConverter + { + private readonly TypeConverter _typeConverter; + + /// + /// Initializes a new instance of the class. + /// + protected JsonDelimitedArrayConverter() + { + _typeConverter = TypeDescriptor.GetConverter(typeof(T)); + } + + /// + /// Gets the array delimiter. + /// + protected virtual char Delimiter { get; } + + /// + public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + // GetString can't return null here because we already handled it above + var stringEntries = reader.GetString()?.Split(Delimiter, StringSplitOptions.RemoveEmptyEntries); + if (stringEntries == null || stringEntries.Length == 0) + { + return Array.Empty(); + } + + var parsedValues = new object[stringEntries.Length]; + var convertedCount = 0; + for (var i = 0; i < stringEntries.Length; i++) + { + try + { + parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()); + convertedCount++; + } + catch (FormatException) + { + // TODO log when upgraded to .Net6 + // https://github.com/dotnet/runtime/issues/42975 + // _logger.LogDebug(e, "Error converting value."); + } + } + + var typedValues = new T[convertedCount]; + var typedValueIndex = 0; + for (var i = 0; i < stringEntries.Length; i++) + { + if (parsedValues[i] != null) + { + typedValues.SetValue(parsedValues[i], typedValueIndex); + typedValueIndex++; + } + } + + return typedValues; + } + + return JsonSerializer.Deserialize(ref reader, options); + } + + /// + public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs index d5b54e3ca8..e2a3d798a2 100644 --- a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs +++ b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs @@ -18,10 +18,10 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { var structType = typeToConvert.GenericTypeArguments[0]; - return (JsonConverter)Activator.CreateInstance(typeof(JsonNullableStructConverter<>).MakeGenericType(structType)); + return (JsonConverter?)Activator.CreateInstance(typeof(JsonNullableStructConverter<>).MakeGenericType(structType)); } } -} \ No newline at end of file +} diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs index 6a8790374b..77cf46b706 100644 --- a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs @@ -7,15 +7,21 @@ namespace MediaBrowser.Common.Json.Converters /// /// Converts a string N/A to string.Empty. /// - public class JsonOmdbNotAvailableStringConverter : JsonConverter + public class JsonOmdbNotAvailableStringConverter : JsonConverter { /// - public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + if (reader.TokenType == JsonTokenType.String) { - var str = reader.GetString(); - if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) + // GetString can't return null here because we already handled it above + var str = reader.GetString()!; + if (str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) { return null; } @@ -23,11 +29,11 @@ namespace MediaBrowser.Common.Json.Converters return str; } - return JsonSerializer.Deserialize(ref reader, options); + return JsonSerializer.Deserialize(ref reader, options); } /// - public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) { writer.WriteStringValue(value); } diff --git a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs index c408a3be13..a8f6cfbecf 100644 --- a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs @@ -9,67 +9,16 @@ namespace MediaBrowser.Common.Json.Converters /// Convert Pipe delimited string to array of type. /// /// Type to convert to. - public class JsonPipeDelimitedArrayConverter : JsonConverter + public sealed class JsonPipeDelimitedArrayConverter : JsonDelimitedArrayConverter { - private readonly TypeConverter _typeConverter; - /// /// Initializes a new instance of the class. /// - public JsonPipeDelimitedArrayConverter() + public JsonPipeDelimitedArrayConverter() : base() { - _typeConverter = TypeDescriptor.GetConverter(typeof(T)); } /// - public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.String) - { - var stringEntries = reader.GetString()?.Split('|', StringSplitOptions.RemoveEmptyEntries); - if (stringEntries == null || stringEntries.Length == 0) - { - return Array.Empty(); - } - - var parsedValues = new object[stringEntries.Length]; - var convertedCount = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - try - { - parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()); - convertedCount++; - } - catch (FormatException) - { - // TODO log when upgraded to .Net6 - // https://github.com/dotnet/runtime/issues/42975 - // _logger.LogDebug(e, "Error converting value."); - } - } - - var typedValues = new T[convertedCount]; - var typedValueIndex = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - if (parsedValues[i] != null) - { - typedValues.SetValue(parsedValues[i], typedValueIndex); - typedValueIndex++; - } - } - - return typedValues; - } - - return JsonSerializer.Deserialize(ref reader, options); - } - - /// - public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } + protected override char Delimiter => '|'; } } diff --git a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs index 5e77223ef2..1bebc49ecc 100644 --- a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs +++ b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs @@ -19,10 +19,10 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0]; - return (JsonConverter)Activator.CreateInstance(typeof(JsonPipeDelimitedArrayConverter<>).MakeGenericType(structType)); + return (JsonConverter?)Activator.CreateInstance(typeof(JsonPipeDelimitedArrayConverter<>).MakeGenericType(structType)); } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs index 669b3cd077..6cd980e48a 100644 --- a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs @@ -9,10 +9,10 @@ namespace MediaBrowser.Common.Json.Converters /// /// Converter to allow the serializer to read strings. /// - public class JsonStringConverter : JsonConverter + public class JsonStringConverter : JsonConverter { /// - public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.TokenType switch { @@ -23,7 +23,7 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) { writer.WriteStringValue(value); } @@ -36,4 +36,4 @@ namespace MediaBrowser.Common.Json.Converters return Encoding.UTF8.GetString(utf8Bytes); } } -} \ No newline at end of file +} diff --git a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs index f69e868cc4..81c093c541 100644 --- a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Common.Json.Converters { /// public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => new Version(reader.GetString()); + => new Version(reader.GetString()!); // Will throw ArgumentNullException on null /// public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 0d9f78704e..0299a84563 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -33,6 +33,7 @@ false true true + enable AllEnabledByDefault ../jellyfin.ruleset true diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 185df5b773..b939397301 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index fb3ef9b12b..5db8817ee6 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Diagnostics; using System.Linq; @@ -400,10 +399,7 @@ namespace MediaBrowser.Common.Net private bool ResolveHost() { // When was the last time we resolved? - if (_lastResolved == null) - { - _lastResolved = DateTime.UtcNow; - } + _lastResolved ??= DateTime.UtcNow; // If we haven't resolved before, or our timer has run out... if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout))) diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs index 589aad4b09..f6e3971bf3 100644 --- a/MediaBrowser.Common/Net/IPNetAddress.cs +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Net; using System.Net.Sockets; diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs index 3542dcd75e..2612268fd8 100644 --- a/MediaBrowser.Common/Net/IPObject.cs +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Net; using System.Net.Sockets; diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index ad5a7338dd..8972089a8e 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using System.Reflection; diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index 99c226f509..8a6d28e0f6 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -1,4 +1,6 @@ +#nullable disable #pragma warning disable SA1649 // File name should match first type name + using System; using System.IO; using System.Runtime.InteropServices; @@ -105,10 +107,7 @@ namespace MediaBrowser.Common.Plugins { lock (_configurationSyncLock) { - if (_configuration == null) - { - _configuration = LoadConfiguration(); - } + _configuration ??= LoadConfiguration(); } } diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs index b2ba1179c1..01e0a536da 100644 --- a/MediaBrowser.Common/Plugins/IPlugin.cs +++ b/MediaBrowser.Common/Plugins/IPlugin.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Model.Plugins; diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index 0e2e814cb0..176bcbbd54 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Reflection; diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index 12a1ad1ec8..4c8e2d5044 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Collections.Generic; using MediaBrowser.Model.Plugins; diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index 4c724f6949..2910dbe144 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Text.Json.Serialization; using MediaBrowser.Model.Plugins; diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs index fe7cb10788..0ba46ea3ba 100644 --- a/MediaBrowser.Common/Progress/ActionableProgress.cs +++ b/MediaBrowser.Common/Progress/ActionableProgress.cs @@ -14,9 +14,9 @@ namespace MediaBrowser.Common.Progress /// /// The _actions. /// - private Action _action; + private Action? _action; - public event EventHandler ProgressChanged; + public event EventHandler? ProgressChanged; /// /// Registers the action. diff --git a/MediaBrowser.Common/Progress/SimpleProgress.cs b/MediaBrowser.Common/Progress/SimpleProgress.cs index 988d8ad34a..7071f2bc3d 100644 --- a/MediaBrowser.Common/Progress/SimpleProgress.cs +++ b/MediaBrowser.Common/Progress/SimpleProgress.cs @@ -7,7 +7,7 @@ namespace MediaBrowser.Common.Progress { public class SimpleProgress : IProgress { - public event EventHandler ProgressChanged; + public event EventHandler? ProgressChanged; public void Report(T value) { diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs index 64c2e19766..33d09ed385 100644 --- a/MediaBrowser.Common/Providers/ProviderIdParsers.cs +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -1,6 +1,4 @@ -#nullable enable - -using System; +using System; using System.Diagnostics.CodeAnalysis; namespace MediaBrowser.Common.Providers diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 0844c2d792..c2a28e0a2c 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs index adf336313f..f4f7599559 100644 --- a/MediaBrowser.Common/Updates/InstallationEventArgs.cs +++ b/MediaBrowser.Common/Updates/InstallationEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Model.Updates; diff --git a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs index 46f10c84fd..d37146195c 100644 --- a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs +++ b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs index 4249a9a667..635e4eb3d7 100644 --- a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs +++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index ecdffa2ebe..a56d3c8223 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Threading.Tasks; diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs index 6729b91157..8c9d1baf88 100644 --- a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index a233c358e4..68119cfed6 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading; diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs index 8a87364270..b2b36c040b 100644 --- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index b2315bda47..26a936be0c 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -82,7 +84,7 @@ namespace MediaBrowser.Controller.Channels internal static bool IsChannelVisible(BaseItem channelItem, User user) { - var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString("")); + var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString(string.Empty)); return channel.IsVisible(user); } diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs index 476992cbd4..4d1e35f9ea 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -11,6 +13,19 @@ namespace MediaBrowser.Controller.Channels { public class ChannelItemInfo : IHasProviderIds { + public ChannelItemInfo() + { + MediaSources = new List(); + TrailerTypes = new List(); + Genres = new List(); + Studios = new List(); + People = new List(); + Tags = new List(); + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + Artists = new List(); + AlbumArtists = new List(); + } + public string Name { get; set; } public string SeriesName { get; set; } @@ -78,18 +93,5 @@ namespace MediaBrowser.Controller.Channels public bool IsLiveStream { get; set; } public string Etag { get; set; } - - public ChannelItemInfo() - { - MediaSources = new List(); - TrailerTypes = new List(); - Genres = new List(); - Studios = new List(); - People = new List(); - Tags = new List(); - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - Artists = new List(); - AlbumArtists = new List(); - } } } diff --git a/MediaBrowser.Controller/Channels/ChannelItemResult.cs b/MediaBrowser.Controller/Channels/ChannelItemResult.cs index cee7b20039..6b20776629 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemResult.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemResult.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; @@ -6,13 +8,13 @@ namespace MediaBrowser.Controller.Channels { public class ChannelItemResult { - public List Items { get; set; } - - public int? TotalRecordCount { get; set; } - public ChannelItemResult() { Items = new List(); } + + public List Items { get; set; } + + public int? TotalRecordCount { get; set; } } } diff --git a/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs b/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs new file mode 100644 index 0000000000..6f0761e64b --- /dev/null +++ b/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs @@ -0,0 +1,11 @@ +#nullable disable + +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public class ChannelLatestMediaSearch + { + public string UserId { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs index 32469d4d7b..990b025bcb 100644 --- a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Channels @@ -8,9 +10,4 @@ namespace MediaBrowser.Controller.Channels public string UserId { get; set; } } - - public class ChannelLatestMediaSearch - { - public string UserId { get; set; } - } } diff --git a/MediaBrowser.Controller/Channels/IChannel.cs b/MediaBrowser.Controller/Channels/IChannel.cs index 2c0eadf950..01bf8d5c85 100644 --- a/MediaBrowser.Controller/Channels/IChannel.cs +++ b/MediaBrowser.Controller/Channels/IChannel.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index ddae7dbd3e..49be897ef3 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -49,32 +51,47 @@ namespace MediaBrowser.Controller.Channels /// Gets the channels internal. /// /// The query. + /// The channels. QueryResult GetChannelsInternal(ChannelQuery query); /// /// Gets the channels. /// /// The query. + /// The channels. QueryResult GetChannels(ChannelQuery query); /// - /// Gets the latest media. + /// Gets the latest channel items. /// + /// The item query. + /// The cancellation token. + /// The latest channels. Task> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); /// - /// Gets the latest media. + /// Gets the latest channel items. /// + /// The item query. + /// The cancellation token. + /// The latest channels. Task> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken); /// /// Gets the channel items. /// + /// The query. + /// The cancellation token. + /// The channel items. Task> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); /// - /// Gets the channel items internal. + /// Gets the channel items. /// + /// The query. + /// The progress to report to. + /// The cancellation token. + /// The channel items. Task> GetChannelItemsInternal(InternalItemsQuery query, IProgress progress, CancellationToken cancellationToken); /// @@ -82,9 +99,14 @@ namespace MediaBrowser.Controller.Channels /// /// The item. /// The cancellation token. - /// Task{IEnumerable{MediaSourceInfo}}. + /// The item media sources. IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken); + /// + /// Whether the item supports media probe. + /// + /// The item. + /// Whether media probe should be enabled. bool EnableMediaProbe(BaseItem item); } } diff --git a/MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs b/MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs new file mode 100644 index 0000000000..a2dc5682dd --- /dev/null +++ b/MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs @@ -0,0 +1,8 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public interface IDisableMediaSourceDisplay + { + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/IHasCacheKey.cs b/MediaBrowser.Controller/Channels/IHasCacheKey.cs index bf895a0eca..9fae43033e 100644 --- a/MediaBrowser.Controller/Channels/IHasCacheKey.cs +++ b/MediaBrowser.Controller/Channels/IHasCacheKey.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Channels diff --git a/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs b/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs new file mode 100644 index 0000000000..47277a8cc8 --- /dev/null +++ b/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs @@ -0,0 +1,9 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public interface IHasFolderAttributes + { + string[] Attributes { get; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs b/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs index 589295543c..eeaa6b622e 100644 --- a/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs +++ b/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -7,11 +5,17 @@ using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Channels { + /// + /// The channel requires a media info callback. + /// public interface IRequiresMediaInfoCallback { /// /// Gets the channel item media information. /// + /// The channel item id. + /// The cancellation token. + /// The enumerable of media source info. Task> GetChannelItemMediaInfo(string id, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Channels/ISearchableChannel.cs b/MediaBrowser.Controller/Channels/ISearchableChannel.cs index b627ca1c25..b87943a6ed 100644 --- a/MediaBrowser.Controller/Channels/ISearchableChannel.cs +++ b/MediaBrowser.Controller/Channels/ISearchableChannel.cs @@ -1,9 +1,10 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Channels { @@ -17,35 +18,4 @@ namespace MediaBrowser.Controller.Channels /// Task{IEnumerable{ChannelItemInfo}}. Task> Search(ChannelSearchInfo searchInfo, CancellationToken cancellationToken); } - - public interface ISupportsLatestMedia - { - /// - /// Gets the latest media. - /// - /// The request. - /// The cancellation token. - /// Task{IEnumerable{ChannelItemInfo}}. - Task> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken); - } - - public interface ISupportsDelete - { - bool CanDelete(BaseItem item); - - Task DeleteItem(string id, CancellationToken cancellationToken); - } - - public interface IDisableMediaSourceDisplay - { - } - - public interface ISupportsMediaProbe - { - } - - public interface IHasFolderAttributes - { - string[] Attributes { get; } - } } diff --git a/MediaBrowser.Controller/Channels/ISupportsDelete.cs b/MediaBrowser.Controller/Channels/ISupportsDelete.cs new file mode 100644 index 0000000000..204054374e --- /dev/null +++ b/MediaBrowser.Controller/Channels/ISupportsDelete.cs @@ -0,0 +1,15 @@ +#pragma warning disable CS1591 + +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Channels +{ + public interface ISupportsDelete + { + bool CanDelete(BaseItem item); + + Task DeleteItem(string id, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs new file mode 100644 index 0000000000..dbba7cba2d --- /dev/null +++ b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs @@ -0,0 +1,21 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Channels +{ + public interface ISupportsLatestMedia + { + /// + /// Gets the latest media. + /// + /// The request. + /// The cancellation token. + /// The latest media. + Task> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs b/MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs new file mode 100644 index 0000000000..2682de51c4 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs @@ -0,0 +1,8 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public interface ISupportsMediaProbe + { + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs index 137f5d095b..45cd08173a 100644 --- a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs +++ b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; @@ -28,7 +30,7 @@ namespace MediaBrowser.Controller.Channels public List ContentTypes { get; set; } /// - /// Represents the maximum number of records the channel allows retrieving at a time. + /// Gets or sets the maximum number of records the channel allows retrieving at a time. /// public int? MaxPageSize { get; set; } @@ -39,7 +41,7 @@ namespace MediaBrowser.Controller.Channels public List DefaultSortFields { get; set; } /// - /// Indicates if a sort ascending/descending toggle is supported or not. + /// Gets or sets a value indicating whether a sort ascending/descending toggle is supported or not. /// public bool SupportsSortOrderToggle { get; set; } diff --git a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs index 7e9bb28ed6..0d837faca2 100644 --- a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs +++ b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs b/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs new file mode 100644 index 0000000000..82b3a49773 --- /dev/null +++ b/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs @@ -0,0 +1,24 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using MediaBrowser.Controller.Entities.Movies; + +namespace MediaBrowser.Controller.Collections +{ + public class CollectionCreatedEventArgs : EventArgs + { + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the options. + /// + /// The options. + public CollectionCreationOptions Options { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index f6037d05e1..30f5f4efa2 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -23,8 +25,8 @@ namespace MediaBrowser.Controller.Collections public Dictionary ProviderIds { get; set; } - public string[] ItemIdList { get; set; } + public IReadOnlyList ItemIdList { get; set; } - public Guid[] UserIds { get; set; } + public IReadOnlyList UserIds { get; set; } } } diff --git a/MediaBrowser.Controller/Collections/CollectionEvents.cs b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs similarity index 55% rename from MediaBrowser.Controller/Collections/CollectionEvents.cs rename to MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs index ce59b4ada2..8155cf3dbd 100644 --- a/MediaBrowser.Controller/Collections/CollectionEvents.cs +++ b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -7,23 +9,14 @@ using MediaBrowser.Controller.Entities.Movies; namespace MediaBrowser.Controller.Collections { - public class CollectionCreatedEventArgs : EventArgs - { - /// - /// Gets or sets the collection. - /// - /// The collection. - public BoxSet Collection { get; set; } - - /// - /// Gets or sets the options. - /// - /// The options. - public CollectionCreationOptions Options { get; set; } - } - public class CollectionModifiedEventArgs : EventArgs { + public CollectionModifiedEventArgs(BoxSet collection, IReadOnlyCollection itemsChanged) + { + Collection = collection; + ItemsChanged = itemsChanged; + } + /// /// Gets or sets the collection. /// @@ -34,6 +27,6 @@ namespace MediaBrowser.Controller.Collections /// Gets or sets the items changed. /// /// The items changed. - public List ItemsChanged { get; set; } + public IReadOnlyCollection ItemsChanged { get; set; } } } diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index a6991e2eac..46bc37e7f6 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index 43ad04dbac..44e2c45dd4 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 8f0872dba9..8096be1bd3 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -18,7 +20,6 @@ namespace MediaBrowser.Controller.Devices /// /// The reported identifier. /// The capabilities. - /// Task. void SaveCapabilities(string reportedId, ClientCapabilities capabilities); /// @@ -45,6 +46,9 @@ namespace MediaBrowser.Controller.Devices /// /// Determines whether this instance [can access device] the specified user identifier. /// + /// The user to test. + /// The device id to test. + /// Whether the user can access the device. bool CanAccessDevice(User user, string deviceId); void UpdateDeviceOptions(string deviceId, DeviceOptions options); diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index dc2d5a356a..b51dc255ce 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 770c6dc2d2..800f7a8bb9 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 #nullable enable diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 142cebd0c0..9bfead8b3b 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 #nullable enable diff --git a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs index fe0465d0d7..e9c88ffb56 100644 --- a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs @@ -1,3 +1,7 @@ +#nullable disable + +using System.Collections.Generic; + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Drawing @@ -8,7 +12,7 @@ namespace MediaBrowser.Controller.Drawing /// Gets or sets the input paths. /// /// The input paths. - public string[] InputPaths { get; set; } + public IReadOnlyList InputPaths { get; set; } /// /// Gets or sets the output path. diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index 596fcbc8cf..204175ed5a 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 #nullable enable diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 230a0af60c..11e6633011 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs index d3a2b4dbf2..b036425ab3 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Drawing/ImageStream.cs b/MediaBrowser.Controller/Drawing/ImageStream.cs index 46f58ec159..5ee781ffa9 100644 --- a/MediaBrowser.Controller/Drawing/ImageStream.cs +++ b/MediaBrowser.Controller/Drawing/ImageStream.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Drawing /// Gets or sets the stream. /// /// The stream. - public Stream Stream { get; set; } + public Stream? Stream { get; set; } /// /// Gets or sets the format. @@ -22,9 +22,15 @@ namespace MediaBrowser.Controller.Drawing public void Dispose() { - if (Stream != null) + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) { - Stream.Dispose(); + Stream?.Dispose(); } } } diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index 3567837507..ecc833154d 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -16,6 +18,28 @@ namespace MediaBrowser.Controller.Dto ItemFields.RefreshState }; + private static readonly ImageType[] AllImageTypes = Enum.GetValues(); + + private static readonly ItemFields[] AllItemFields = Enum.GetValues() + .Except(DefaultExcludedFields) + .ToArray(); + + public DtoOptions() + : this(true) + { + } + + public DtoOptions(bool allFields) + { + ImageTypeLimit = int.MaxValue; + EnableImages = true; + EnableUserData = true; + AddCurrentProgram = true; + + Fields = allFields ? AllItemFields : Array.Empty(); + ImageTypes = AllImageTypes; + } + public IReadOnlyList Fields { get; set; } public IReadOnlyList ImageTypes { get; set; } @@ -30,34 +54,9 @@ namespace MediaBrowser.Controller.Dto public bool AddCurrentProgram { get; set; } - public DtoOptions() - : this(true) - { - } - - private static readonly ImageType[] AllImageTypes = Enum.GetNames(typeof(ImageType)) - .Select(i => (ImageType)Enum.Parse(typeof(ImageType), i, true)) - .ToArray(); - - private static readonly ItemFields[] AllItemFields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .Except(DefaultExcludedFields) - .ToArray(); - public bool ContainsField(ItemFields field) => Fields.Contains(field); - public DtoOptions(bool allFields) - { - ImageTypeLimit = int.MaxValue; - EnableImages = true; - EnableUserData = true; - AddCurrentProgram = true; - - Fields = allFields ? AllItemFields : Array.Empty(); - ImageTypes = AllImageTypes; - } - public int GetImageLimit(ImageType type) { if (EnableImages && ImageTypes.Contains(type)) diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 988557f42c..61d7962357 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; @@ -34,11 +36,17 @@ namespace MediaBrowser.Controller.Dto /// The options. /// The user. /// The owner. + /// The of . IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null); /// /// Gets the item by name dto. /// + /// The item. + /// The dto options. + /// The list of tagged items. + /// The user. + /// The item dto. BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null); } } diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 6ebea5f449..533130fc8e 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -20,6 +22,8 @@ namespace MediaBrowser.Controller.Entities /// public class AggregateFolder : Folder { + private bool _requiresRefresh; + public AggregateFolder() { PhysicalLocationsList = Array.Empty(); @@ -83,7 +87,6 @@ namespace MediaBrowser.Controller.Entities } } - private bool _requiresRefresh; public override bool RequiresRefresh() { var changed = base.RequiresRefresh() || _requiresRefresh; @@ -103,11 +106,11 @@ namespace MediaBrowser.Controller.Entities return changed; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { ClearCache(); - var changed = base.BeforeMetadataRefresh(replaceAllMetdata) || _requiresRefresh; + var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh; _requiresRefresh = false; return changed; } @@ -120,8 +123,7 @@ namespace MediaBrowser.Controller.Entities var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) { - FileInfo = FileSystem.GetDirectoryInfo(path), - Path = path + FileInfo = FileSystem.GetDirectoryInfo(path) }; // Gather child folder and files diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 8220464b39..4c2b7cb7c7 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs index 20fad4cb0e..1625c748a8 100644 --- a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs index ac4dd1688b..db60c3071d 100644 --- a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs +++ b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Entities.Audio diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 9a33ad9d74..610bce4f5f 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 8a9bb12c7b..0928a8073b 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -112,7 +114,7 @@ namespace MediaBrowser.Controller.Entities.Audio } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. @@ -206,9 +208,9 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (IsAccessedByName) { diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index f0c076108e..a682a2e58c 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -36,7 +38,7 @@ namespace MediaBrowser.Controller.Entities.Audio public override bool IsDisplayedAsFolder => true; /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. @@ -104,9 +106,9 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index f4bd851e1b..4052846228 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 53d45261e6..6e46b4cec8 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -90,7 +92,8 @@ namespace MediaBrowser.Controller.Entities public const string ShortsFolderName = "shorts"; public const string FeaturettesFolderName = "featurettes"; - public static readonly string[] AllExtrasTypesFolderNames = { + public static readonly string[] AllExtrasTypesFolderNames = + { ExtrasFolderName, BehindTheScenesFolderName, DeletedScenesFolderName, @@ -106,15 +109,10 @@ namespace MediaBrowser.Controller.Entities { get { - if (_themeSongIds == null) - { - _themeSongIds = GetExtras() - .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong) - .Select(song => song.Id) - .ToArray(); - } - - return _themeSongIds; + return _themeSongIds ??= GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong) + .Select(song => song.Id) + .ToArray(); } private set @@ -128,15 +126,10 @@ namespace MediaBrowser.Controller.Entities { get { - if (_themeVideoIds == null) - { - _themeVideoIds = GetExtras() - .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo) - .Select(song => song.Id) - .ToArray(); - } - - return _themeVideoIds; + return _themeVideoIds ??= GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo) + .Select(song => song.Id) + .ToArray(); } private set @@ -185,7 +178,7 @@ namespace MediaBrowser.Controller.Entities public virtual bool AlwaysScanInternalMetadataPath => false; /// - /// Gets a value indicating whether this instance is in mixed folder. + /// Gets or sets a value indicating whether this instance is in mixed folder. /// /// true if this instance is in mixed folder; otherwise, false. [JsonIgnore] @@ -252,7 +245,7 @@ namespace MediaBrowser.Controller.Entities public ProgramAudio? Audio { get; set; } /// - /// Return the id that should be used to key display prefs for this item. + /// Gets the id that should be used to key display prefs for this item. /// Default is based on the type for everything except actual generic folders. /// /// The display prefs id. @@ -288,7 +281,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// [JsonIgnore] @@ -313,8 +306,11 @@ namespace MediaBrowser.Controller.Entities public string ServiceName { get; set; } /// - /// If this content came from an external service, the id of the content on that service. + /// Gets or sets the external id. /// + /// + /// If this content came from an external service, the id of the content on that service. + /// [JsonIgnore] public string ExternalId { get; set; } @@ -338,7 +334,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Gets or sets the type of the location. + /// Gets the type of the location. /// /// The type of the location. [JsonIgnore] @@ -347,9 +343,9 @@ namespace MediaBrowser.Controller.Entities get { // if (IsOffline) - //{ + // { // return LocationType.Offline; - //} + // } var path = Path; if (string.IsNullOrEmpty(path)) @@ -457,8 +453,11 @@ namespace MediaBrowser.Controller.Entities } /// - /// This is just a helper for convenience. + /// Gets the primary image path. /// + /// + /// This is just a helper for convenience. + /// /// The primary image path. [JsonIgnore] public string PrimaryImagePath => this.GetImagePath(ImageType.Primary); @@ -549,7 +548,7 @@ namespace MediaBrowser.Controller.Entities public DateTime DateLastRefreshed { get; set; } /// - /// The logger. + /// Gets or sets the logger. /// public static ILogger Logger { get; set; } @@ -629,7 +628,7 @@ namespace MediaBrowser.Controller.Entities private Guid[] _themeVideoIds; /// - /// Gets the name of the sort. + /// Gets or sets the name of the sort. /// /// The name of the sort. [JsonIgnore] @@ -856,7 +855,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// When the item first debuted. For movies this could be premiere date, episodes would be first aired + /// Gets or sets the date that the item first debuted. For movies this could be premiere date, episodes would be first aired. /// /// The premiere date. [JsonIgnore] @@ -953,7 +952,7 @@ namespace MediaBrowser.Controller.Entities public int? ProductionYear { get; set; } /// - /// If the item is part of a series, this is it's number in the series. + /// Gets or sets the index number. If the item is part of a series, this is it's number in the series. /// This could be episode number, album track number, etc. /// /// The index number. @@ -961,7 +960,7 @@ namespace MediaBrowser.Controller.Entities public int? IndexNumber { get; set; } /// - /// For an episode this could be the season number, or for a song this could be the disc number. + /// Gets or sets the parent index number. For an episode this could be the season number, or for a song this could be the disc number. /// /// The parent index number. [JsonIgnore] @@ -1025,9 +1024,9 @@ namespace MediaBrowser.Controller.Entities } // if (!user.IsParentalScheduleAllowed()) - //{ + // { // return PlayAccess.None; - //} + // } return PlayAccess.Full; } @@ -2324,7 +2323,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => i.IsLocalFile) .Select(i => System.IO.Path.GetDirectoryName(i.Path)) .Distinct(StringComparer.OrdinalIgnoreCase) - .SelectMany(i => directoryService.GetFilePaths(i)) + .SelectMany(directoryService.GetFilePaths) .ToList(); var deletedImages = ImageInfos @@ -2653,7 +2652,9 @@ namespace MediaBrowser.Controller.Entities /// /// This is called before any metadata refresh and returns true if changes were made. /// - public virtual bool BeforeMetadataRefresh(bool replaceAllMetdata) + /// Whether to replace all metadata. + /// true if the item has change, else false. + public virtual bool BeforeMetadataRefresh(bool replaceAllMetadata) { _sortName = null; @@ -2777,11 +2778,11 @@ namespace MediaBrowser.Controller.Entities // var parentId = Id; // if (!video.IsOwnedItem || video.ParentId != parentId) - //{ + // { // video.IsOwnedItem = true; // video.ParentId = parentId; // newOptions.ForceSave = true; - //} + // } if (video == null) { diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs index 157ed83328..c39b18891f 100644 --- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs +++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #nullable enable #pragma warning disable CS1591 diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index ef5a5a734c..1bd25042f2 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Text.Json.Serialization; diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 55945283c9..d75beb06da 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -10,6 +12,11 @@ namespace MediaBrowser.Controller.Entities { public class Book : BaseItem, IHasLookupInfo, IHasSeries { + public Book() + { + this.RunTimeTicks = TimeSpan.TicksPerSecond; + } + [JsonIgnore] public override string MediaType => Model.Entities.MediaType.Book; @@ -26,11 +33,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public Guid SeriesId { get; set; } - public Book() - { - this.RunTimeTicks = TimeSpan.TicksPerSecond; - } - public string FindSeriesSortName() { return SeriesName; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 76b6d39a93..d0fb3997d5 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -27,30 +29,45 @@ namespace MediaBrowser.Controller.Entities public class CollectionFolder : Folder, ICollectionFolder { private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - public static IXmlSerializer XmlSerializer { get; set; } - - public static IServerApplicationHost ApplicationHost { get; set; } + private static readonly Dictionary _libraryOptions = new Dictionary(); + private bool _requiresRefresh; + /// + /// Initializes a new instance of the class. + /// public CollectionFolder() { PhysicalLocationsList = Array.Empty(); PhysicalFolderIds = Array.Empty(); } + public static IXmlSerializer XmlSerializer { get; set; } + + public static IServerApplicationHost ApplicationHost { get; set; } + [JsonIgnore] public override bool SupportsPlayedStatus => false; [JsonIgnore] public override bool SupportsInheritedParentImages => false; + public string CollectionType { get; set; } + + /// + /// Gets the item's children. + /// + /// + /// Our children are actually just references to the ones in the physical root... + /// + /// The actual children. + [JsonIgnore] + public override IEnumerable Children => GetActualChildren(); + public override bool CanDelete() { return false; } - public string CollectionType { get; set; } - - private static readonly Dictionary LibraryOptions = new Dictionary(); public LibraryOptions GetLibraryOptions() { return GetLibraryOptions(Path); @@ -61,7 +78,6 @@ namespace MediaBrowser.Controller.Entities try { var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) as LibraryOptions; - if (result == null) { return new LibraryOptions(); @@ -105,12 +121,12 @@ namespace MediaBrowser.Controller.Entities public static LibraryOptions GetLibraryOptions(string path) { - lock (LibraryOptions) + lock (_libraryOptions) { - if (!LibraryOptions.TryGetValue(path, out var options)) + if (!_libraryOptions.TryGetValue(path, out var options)) { options = LoadLibraryOptions(path); - LibraryOptions[path] = options; + _libraryOptions[path] = options; } return options; @@ -119,9 +135,9 @@ namespace MediaBrowser.Controller.Entities public static void SaveLibraryOptions(string path, LibraryOptions options) { - lock (LibraryOptions) + lock (_libraryOptions) { - LibraryOptions[path] = options; + _libraryOptions[path] = options; var clone = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions); foreach (var mediaPath in clone.PathInfos) @@ -138,15 +154,18 @@ namespace MediaBrowser.Controller.Entities public static void OnCollectionFolderChange() { - lock (LibraryOptions) + lock (_libraryOptions) { - LibraryOptions.Clear(); + _libraryOptions.Clear(); } } /// - /// Allow different display preferences for each collection folder. + /// Gets the display preferences id. /// + /// + /// Allow different display preferences for each collection folder. + /// /// The display prefs id. [JsonIgnore] public override Guid DisplayPreferencesId => Id; @@ -154,21 +173,20 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override string[] PhysicalLocations => PhysicalLocationsList; + public string[] PhysicalLocationsList { get; set; } + + public Guid[] PhysicalFolderIds { get; set; } + public override bool IsSaveLocalMetadataEnabled() { return true; } - public string[] PhysicalLocationsList { get; set; } - - public Guid[] PhysicalFolderIds { get; set; } - protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) { return CreateResolveArgs(directoryService, true).FileSystemChildren; } - private bool _requiresRefresh; public override bool RequiresRefresh() { var changed = base.RequiresRefresh() || _requiresRefresh; @@ -200,9 +218,9 @@ namespace MediaBrowser.Controller.Entities return changed; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var changed = base.BeforeMetadataRefresh(replaceAllMetdata) || _requiresRefresh; + var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh; _requiresRefresh = false; return changed; } @@ -271,7 +289,6 @@ namespace MediaBrowser.Controller.Entities var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) { FileInfo = FileSystem.GetDirectoryInfo(path), - Path = path, Parent = GetParent() as Folder, CollectionType = CollectionType }; @@ -312,13 +329,6 @@ namespace MediaBrowser.Controller.Entities return Task.CompletedTask; } - /// - /// Our children are actually just references to the ones in the physical root... - /// - /// The actual children. - [JsonIgnore] - public override IEnumerable Children => GetActualChildren(); - public IEnumerable GetActualChildren() { return GetPhysicalFolders(true).SelectMany(c => c.Children); @@ -355,9 +365,7 @@ namespace MediaBrowser.Controller.Entities if (result.Count == 0) { - var folder = LibraryManager.FindByPath(path, true) as Folder; - - if (folder != null) + if (LibraryManager.FindByPath(path, true) is Folder folder) { result.Add(folder); } diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs index 3a34c668cf..244cc00bea 100644 --- a/MediaBrowser.Controller/Entities/Extensions.cs +++ b/MediaBrowser.Controller/Entities/Extensions.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using MediaBrowser.Common.Extensions; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index d45f8758cb..29d837c143 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -35,6 +37,11 @@ namespace MediaBrowser.Controller.Entities /// public class Folder : BaseItem { + public Folder() + { + LinkedChildren = Array.Empty(); + } + public static IUserViewManager UserViewManager { get; set; } /// @@ -48,11 +55,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public DateTime? DateLastMediaAdded { get; set; } - public Folder() - { - LinkedChildren = Array.Empty(); - } - [JsonIgnore] public override bool SupportsThemeMedia => true; @@ -84,6 +86,85 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool SupportsDateLastMediaAdded => false; + [JsonIgnore] + public override string FileNameWithoutExtension + { + get + { + if (IsFileProtocol) + { + return System.IO.Path.GetFileName(Path); + } + + return null; + } + } + + /// + /// Gets the actual children. + /// + /// The actual children. + [JsonIgnore] + public virtual IEnumerable Children => LoadChildren(); + + /// + /// Gets thread-safe access to all recursive children of this folder - without regard to user. + /// + /// The recursive children. + [JsonIgnore] + public IEnumerable RecursiveChildren => GetRecursiveChildren(); + + [JsonIgnore] + protected virtual bool SupportsShortcutChildren => false; + + protected virtual bool FilterLinkedChildrenPerUser => false; + + [JsonIgnore] + protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren; + + [JsonIgnore] + public virtual bool SupportsUserDataFromChildren + { + get + { + // These are just far too slow. + if (this is ICollectionFolder) + { + return false; + } + + if (this is UserView) + { + return false; + } + + if (this is UserRootFolder) + { + return false; + } + + if (this is Channel) + { + return false; + } + + if (SourceType != SourceType.Library) + { + return false; + } + + if (this is IItemByName) + { + if (this is not IHasDualAccess hasDualAccess || hasDualAccess.IsAccessedByName) + { + return false; + } + } + + return true; + } + } + public override bool CanDelete() { if (IsRoot) @@ -106,20 +187,6 @@ namespace MediaBrowser.Controller.Entities return baseResult; } - [JsonIgnore] - public override string FileNameWithoutExtension - { - get - { - if (IsFileProtocol) - { - return System.IO.Path.GetFileName(Path); - } - - return null; - } - } - protected override bool IsAllowTagFilterEnforced() { if (this is ICollectionFolder) @@ -135,9 +202,6 @@ namespace MediaBrowser.Controller.Entities return true; } - [JsonIgnore] - protected virtual bool SupportsShortcutChildren => false; - /// /// Adds the child. /// @@ -167,20 +231,6 @@ namespace MediaBrowser.Controller.Entities LibraryManager.CreateItem(item, this); } - /// - /// Gets the actual children. - /// - /// The actual children. - [JsonIgnore] - public virtual IEnumerable Children => LoadChildren(); - - /// - /// thread-safe access to all recursive children of this folder - without regard to user. - /// - /// The recursive children. - [JsonIgnore] - public IEnumerable RecursiveChildren => GetRecursiveChildren(); - public override bool IsVisible(User user) { if (this is ICollectionFolder && !(this is BasePluginFolder)) @@ -1426,8 +1476,6 @@ namespace MediaBrowser.Controller.Entities return list; } - protected virtual bool FilterLinkedChildrenPerUser => false; - public bool ContainsLinkedChildByItemId(Guid itemId) { var linkedChildren = LinkedChildren; @@ -1528,9 +1576,6 @@ namespace MediaBrowser.Controller.Entities .Where(i => i.Item2 != null); } - [JsonIgnore] - protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren; - protected override async Task RefreshedOwnedItems(MetadataRefreshOptions options, List fileSystemChildren, CancellationToken cancellationToken) { var changesFound = false; @@ -1694,51 +1739,6 @@ namespace MediaBrowser.Controller.Entities return !IsPlayed(user); } - [JsonIgnore] - public virtual bool SupportsUserDataFromChildren - { - get - { - // These are just far too slow. - if (this is ICollectionFolder) - { - return false; - } - - if (this is UserView) - { - return false; - } - - if (this is UserRootFolder) - { - return false; - } - - if (this is Channel) - { - return false; - } - - if (SourceType != SourceType.Library) - { - return false; - } - - var iItemByName = this as IItemByName; - if (iItemByName != null) - { - var hasDualAccess = this as IHasDualAccess; - if (hasDualAccess == null || hasDualAccess.IsAccessedByName) - { - return false; - } - } - - return true; - } - } - public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields) { if (!SupportsUserDataFromChildren) @@ -1768,20 +1768,15 @@ namespace MediaBrowser.Controller.Entities { EnableImages = false } - }); + }).TotalRecordCount; - double unplayedCount = unplayedQueryResult.TotalRecordCount; + dto.UnplayedItemCount = unplayedQueryResult; - dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount; - - if (itemDto != null && itemDto.RecursiveItemCount.HasValue) + if (itemDto?.RecursiveItemCount > 0) { - if (itemDto.RecursiveItemCount.Value > 0) - { - var unplayedPercentage = (unplayedCount / itemDto.RecursiveItemCount.Value) * 100; - dto.PlayedPercentage = 100 - unplayedPercentage; - dto.Played = dto.PlayedPercentage.Value >= 100; - } + var unplayedPercentage = ((double)unplayedQueryResult / itemDto.RecursiveItemCount.Value) * 100; + dto.PlayedPercentage = 100 - unplayedPercentage; + dto.Played = dto.PlayedPercentage.Value >= 100; } else { diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 74a1702040..698643b446 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -14,6 +16,23 @@ namespace MediaBrowser.Controller.Entities /// public class Genre : BaseItem, IItemByName { + /// + /// Gets the folder containing the item. + /// If the item is a folder, it returns the folder itself. + /// + /// The containing folder path. + [JsonIgnore] + public override string ContainingFolderPath => Path; + + [JsonIgnore] + public override bool IsDisplayedAsFolder => true; + + [JsonIgnore] + public override bool SupportsAncestors => false; + + [JsonIgnore] + public override bool SupportsPeople => false; + public override List GetUserDataKeys() { var list = base.GetUserDataKeys(); @@ -32,20 +51,6 @@ namespace MediaBrowser.Controller.Entities return 1; } - /// - /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself. - /// - /// The containing folder path. - [JsonIgnore] - public override string ContainingFolderPath => Path; - - [JsonIgnore] - public override bool IsDisplayedAsFolder => true; - - [JsonIgnore] - public override bool SupportsAncestors => false; - public override bool IsSaveLocalMetadataEnabled() { return true; @@ -70,9 +75,6 @@ namespace MediaBrowser.Controller.Entities return LibraryManager.GetItemList(query); } - [JsonIgnore] - public override bool SupportsPeople => false; - public static string GetPath(string name) { return GetPath(name, true); @@ -105,12 +107,10 @@ namespace MediaBrowser.Controller.Entities return base.RequiresRefresh(); } - /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made. - /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + /// + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Entities/ICollectionFolder.cs b/MediaBrowser.Controller/Entities/ICollectionFolder.cs index b84a9fa6f1..2304570fd7 100644 --- a/MediaBrowser.Controller/Entities/ICollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/ICollectionFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/IHasAspectRatio.cs b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs index d7d0076681..3aeb7468f2 100644 --- a/MediaBrowser.Controller/Entities/IHasAspectRatio.cs +++ b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace MediaBrowser.Controller.Entities { /// diff --git a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs index 13226b2346..14459624e9 100644 --- a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs +++ b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace MediaBrowser.Controller.Entities { /// diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs index 0f612262a8..98c3b3edf6 100644 --- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs +++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs index f747b5149a..f80f7c304c 100644 --- a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs +++ b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.LiveTv; diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index 5444f1f523..5f774bbdee 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -7,7 +9,7 @@ namespace MediaBrowser.Controller.Entities public interface IHasSeries { /// - /// Gets the name of the series. + /// Gets or sets the name of the series. /// /// The name of the series. string SeriesName { get; set; } diff --git a/MediaBrowser.Controller/Entities/IHasShares.cs b/MediaBrowser.Controller/Entities/IHasShares.cs new file mode 100644 index 0000000000..bdde744a37 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasShares.cs @@ -0,0 +1,11 @@ +#nullable disable + +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasShares + { + Share[] Shares { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs b/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs index 6a350212b1..f317a02ff3 100644 --- a/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs +++ b/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs index d1f6f2b7e2..2bd9ded337 100644 --- a/MediaBrowser.Controller/Entities/IHasTrailers.cs +++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 270217356f..75fea365ba 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -18,9 +18,9 @@ namespace MediaBrowser.Controller.Entities public int? Limit { get; set; } - public User User { get; set; } + public User? User { get; set; } - public BaseItem SimilarTo { get; set; } + public BaseItem? SimilarTo { get; set; } public bool? IsFolder { get; set; } @@ -56,23 +56,23 @@ namespace MediaBrowser.Controller.Entities public bool? CollapseBoxSetItems { get; set; } - public string NameStartsWithOrGreater { get; set; } + public string? NameStartsWithOrGreater { get; set; } - public string NameStartsWith { get; set; } + public string? NameStartsWith { get; set; } - public string NameLessThan { get; set; } + public string? NameLessThan { get; set; } - public string NameContains { get; set; } + public string? NameContains { get; set; } - public string MinSortName { get; set; } + public string? MinSortName { get; set; } - public string PresentationUniqueKey { get; set; } + public string? PresentationUniqueKey { get; set; } - public string Path { get; set; } + public string? Path { get; set; } - public string Name { get; set; } + public string? Name { get; set; } - public string Person { get; set; } + public string? Person { get; set; } public Guid[] PersonIds { get; set; } @@ -80,7 +80,7 @@ namespace MediaBrowser.Controller.Entities public Guid[] ExcludeItemIds { get; set; } - public string AdjacentTo { get; set; } + public string? AdjacentTo { get; set; } public string[] PersonTypes { get; set; } @@ -180,13 +180,13 @@ namespace MediaBrowser.Controller.Entities public Guid ParentId { get; set; } - public string ParentType { get; set; } + public string? ParentType { get; set; } public Guid[] AncestorIds { get; set; } public Guid[] TopParentIds { get; set; } - public BaseItem Parent + public BaseItem? Parent { set { @@ -211,9 +211,9 @@ namespace MediaBrowser.Controller.Entities public SeriesStatus[] SeriesStatuses { get; set; } - public string ExternalSeriesId { get; set; } + public string? ExternalSeriesId { get; set; } - public string ExternalId { get; set; } + public string? ExternalId { get; set; } public Guid[] AlbumIds { get; set; } @@ -221,9 +221,9 @@ namespace MediaBrowser.Controller.Entities public Guid[] ExcludeArtistIds { get; set; } - public string AncestorWithPresentationUniqueKey { get; set; } + public string? AncestorWithPresentationUniqueKey { get; set; } - public string SeriesPresentationUniqueKey { get; set; } + public string? SeriesPresentationUniqueKey { get; set; } public bool GroupByPresentationUniqueKey { get; set; } @@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Entities public bool ForceDirect { get; set; } - public Dictionary ExcludeProviderIds { get; set; } + public Dictionary? ExcludeProviderIds { get; set; } public bool EnableGroupByMetadataKey { get; set; } @@ -251,13 +251,13 @@ namespace MediaBrowser.Controller.Entities public int MinSimilarityScore { get; set; } - public string HasNoAudioTrackWithLanguage { get; set; } + public string? HasNoAudioTrackWithLanguage { get; set; } - public string HasNoInternalSubtitleTrackWithLanguage { get; set; } + public string? HasNoInternalSubtitleTrackWithLanguage { get; set; } - public string HasNoExternalSubtitleTrackWithLanguage { get; set; } + public string? HasNoExternalSubtitleTrackWithLanguage { get; set; } - public string HasNoSubtitleTrackWithLanguage { get; set; } + public string? HasNoSubtitleTrackWithLanguage { get; set; } public bool? IsDeadArtist { get; set; } @@ -281,12 +281,10 @@ namespace MediaBrowser.Controller.Entities ExcludeInheritedTags = Array.Empty(); ExcludeItemIds = Array.Empty(); ExcludeItemTypes = Array.Empty(); - ExcludeProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); ExcludeTags = Array.Empty(); GenreIds = Array.Empty(); Genres = Array.Empty(); GroupByPresentationUniqueKey = true; - HasAnyProviderId = new Dictionary(StringComparer.OrdinalIgnoreCase); ImageTypes = Array.Empty(); IncludeItemTypes = Array.Empty(); ItemIds = Array.Empty(); @@ -307,32 +305,33 @@ namespace MediaBrowser.Controller.Entities Years = Array.Empty(); } - public InternalItemsQuery(User user) + public InternalItemsQuery(User? user) : this() { - SetUser(user); + if (user != null) + { + SetUser(user); + } } public void SetUser(User user) { - if (user != null) + MaxParentalRating = user.MaxParentalAgeRating; + + if (MaxParentalRating.HasValue) { - MaxParentalRating = user.MaxParentalAgeRating; - - if (MaxParentalRating.HasValue) - { - BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) - .Where(i => i != UnratedItem.Other.ToString()) - .Select(e => Enum.Parse(e, true)).ToArray(); - } - - ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags); - - User = user; + string other = UnratedItem.Other.ToString(); + BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) + .Where(i => i != other) + .Select(e => Enum.Parse(e, true)).ToArray(); } + + ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags); + + User = user; } - public Dictionary HasAnyProviderId { get; set; } + public Dictionary? HasAnyProviderId { get; set; } public Guid[] AlbumArtistIds { get; set; } @@ -354,8 +353,8 @@ namespace MediaBrowser.Controller.Entities public int? MinWidth { get; set; } - public string SearchTerm { get; set; } + public string? SearchTerm { get; set; } - public string SeriesTimerId { get; set; } + public string? SeriesTimerId { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs index 5b96a5af65..3e1d892748 100644 --- a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs @@ -1,12 +1,26 @@ +#nullable disable + #pragma warning disable CS1591 using System; +using System.Collections.Generic; using Jellyfin.Data.Entities; namespace MediaBrowser.Controller.Entities { public class InternalPeopleQuery { + public InternalPeopleQuery() + : this(Array.Empty(), Array.Empty()) + { + } + + public InternalPeopleQuery(IReadOnlyList personTypes, IReadOnlyList excludePersonTypes) + { + PersonTypes = personTypes; + ExcludePersonTypes = excludePersonTypes; + } + /// /// Gets or sets the maximum number of items the query should return. /// @@ -14,9 +28,9 @@ namespace MediaBrowser.Controller.Entities public Guid ItemId { get; set; } - public string[] PersonTypes { get; set; } + public IReadOnlyList PersonTypes { get; } - public string[] ExcludePersonTypes { get; set; } + public IReadOnlyList ExcludePersonTypes { get; } public int? MaxListOrder { get; set; } @@ -27,11 +41,5 @@ namespace MediaBrowser.Controller.Entities public User User { get; set; } public bool? IsFavorite { get; set; } - - public InternalPeopleQuery() - { - PersonTypes = Array.Empty(); - ExcludePersonTypes = Array.Empty(); - } } } diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index 570d8eec0c..ea8555dbfe 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index 8e0f721e77..fd5fef3dc5 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -1,15 +1,20 @@ +#nullable disable + #pragma warning disable CS1591 using System; -using System.Collections.Generic; using System.Globalization; using System.Text.Json.Serialization; -using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.Entities { public class LinkedChild { + public LinkedChild() + { + Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + } + public string Path { get; set; } public LinkedChildType Type { get; set; } @@ -20,7 +25,7 @@ namespace MediaBrowser.Controller.Entities public string Id { get; set; } /// - /// Serves as a cache. + /// Gets or sets the linked item id. /// public Guid? ItemId { get; set; } @@ -39,41 +44,5 @@ namespace MediaBrowser.Controller.Entities return child; } - - public LinkedChild() - { - Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - } - } - - public enum LinkedChildType - { - Manual = 0, - Shortcut = 1 - } - - public class LinkedChildComparer : IEqualityComparer - { - private readonly IFileSystem _fileSystem; - - public LinkedChildComparer(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - } - - public bool Equals(LinkedChild x, LinkedChild y) - { - if (x.Type == y.Type) - { - return _fileSystem.AreEqual(x.Path, y.Path); - } - - return false; - } - - public int GetHashCode(LinkedChild obj) - { - return ((obj.Path ?? string.Empty) + (obj.LibraryItemId ?? string.Empty) + obj.Type).GetHashCode(); - } } } diff --git a/MediaBrowser.Controller/Entities/LinkedChildComparer.cs b/MediaBrowser.Controller/Entities/LinkedChildComparer.cs new file mode 100644 index 0000000000..66fc44b8a9 --- /dev/null +++ b/MediaBrowser.Controller/Entities/LinkedChildComparer.cs @@ -0,0 +1,34 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.Controller.Entities +{ + public class LinkedChildComparer : IEqualityComparer + { + private readonly IFileSystem _fileSystem; + + public LinkedChildComparer(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + public bool Equals(LinkedChild x, LinkedChild y) + { + if (x.Type == y.Type) + { + return _fileSystem.AreEqual(x.Path, y.Path); + } + + return false; + } + + public int GetHashCode(LinkedChild obj) + { + return ((obj.Path ?? string.Empty) + (obj.LibraryItemId ?? string.Empty) + obj.Type).GetHashCode(); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/LinkedChildType.cs b/MediaBrowser.Controller/Entities/LinkedChildType.cs new file mode 100644 index 0000000000..9ddb7b6202 --- /dev/null +++ b/MediaBrowser.Controller/Entities/LinkedChildType.cs @@ -0,0 +1,18 @@ +namespace MediaBrowser.Controller.Entities +{ + /// + /// The linked child type. + /// + public enum LinkedChildType + { + /// + /// Manually linked child. + /// + Manual = 0, + + /// + /// Shortcut linked child. + /// + Shortcut = 1 + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 05e4229cad..74e84288d1 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -217,8 +219,7 @@ namespace MediaBrowser.Controller.Entities.Movies private IEnumerable FlattenItems(BaseItem item, List expandedFolders) { - var boxset = item as BoxSet; - if (boxset != null) + if (item is BoxSet boxset) { if (!expandedFolders.Contains(item.Id)) { diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 8b67aaccc6..b54bbf5eb9 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -142,9 +144,9 @@ namespace MediaBrowser.Controller.Entities.Movies } /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!ProductionYear.HasValue) { diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index b278a01423..237ad5198c 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -11,15 +13,15 @@ namespace MediaBrowser.Controller.Entities { public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasLookupInfo { - /// - [JsonIgnore] - public IReadOnlyList Artists { get; set; } - public MusicVideo() { Artists = Array.Empty(); } + /// + [JsonIgnore] + public IReadOnlyList Artists { get; set; } + public override UnratedItem GetBlockUnratedType() { return UnratedItem.Music; @@ -34,9 +36,9 @@ namespace MediaBrowser.Controller.Entities return info; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!ProductionYear.HasValue) { diff --git a/MediaBrowser.Controller/Entities/PeopleHelper.cs b/MediaBrowser.Controller/Entities/PeopleHelper.cs index 1f3758a73a..687ce1ec8d 100644 --- a/MediaBrowser.Controller/Entities/PeopleHelper.cs +++ b/MediaBrowser.Controller/Entities/PeopleHelper.cs @@ -100,23 +100,5 @@ namespace MediaBrowser.Controller.Entities existing.SetProviderId(id.Key, id.Value); } } - - public static bool ContainsPerson(List people, string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - foreach (var i in people) - { - if (string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } } } diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index c4fcb0267a..913f76d3bd 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -48,7 +50,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. @@ -65,6 +67,9 @@ namespace MediaBrowser.Controller.Entities return true; } + /// + /// Gets a value indicating whether to enable alpha numeric sorting. + /// [JsonIgnore] public override bool EnableAlphaNumericSorting => false; @@ -124,9 +129,9 @@ namespace MediaBrowser.Controller.Entities /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Entities/PersonInfo.cs b/MediaBrowser.Controller/Entities/PersonInfo.cs index 4ff9b0955c..fb79323f8f 100644 --- a/MediaBrowser.Controller/Entities/PersonInfo.cs +++ b/MediaBrowser.Controller/Entities/PersonInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index 2fc66176f6..3312a0e3e2 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Text.Json.Serialization; @@ -24,8 +26,7 @@ namespace MediaBrowser.Controller.Entities var parents = GetParents(); foreach (var parent in parents) { - var photoAlbum = parent as PhotoAlbum; - if (photoAlbum != null) + if (parent is PhotoAlbum photoAlbum) { return photoAlbum; } diff --git a/MediaBrowser.Controller/Entities/Share.cs b/MediaBrowser.Controller/Entities/Share.cs index 50f1655f39..64f446eef2 100644 --- a/MediaBrowser.Controller/Entities/Share.cs +++ b/MediaBrowser.Controller/Entities/Share.cs @@ -1,12 +1,9 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Entities { - public interface IHasShares - { - Share[] Shares { get; set; } - } - public class Share { public string UserId { get; set; } diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 9018ddb75e..6fd0a6c6c3 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -27,7 +29,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. @@ -103,9 +105,9 @@ namespace MediaBrowser.Controller.Entities /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 70663ef478..1b4cc7a78b 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -32,7 +34,7 @@ namespace MediaBrowser.Controller.Entities.TV public IReadOnlyList RemoteTrailerIds { get; set; } /// - /// Gets the season in which it aired. + /// Gets or sets the season in which it aired. /// /// The aired season. public int? AirsBeforeSeasonNumber { get; set; } @@ -42,7 +44,7 @@ namespace MediaBrowser.Controller.Entities.TV public int? AirsBeforeEpisodeNumber { get; set; } /// - /// This is the ending episode number for double episodes. + /// Gets or sets the ending episode number for double episodes. /// /// The index number. public int? IndexNumberEnd { get; set; } @@ -114,7 +116,7 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// This Episode's Series Instance. + /// Gets the Episode's Series Instance. /// /// The series. [JsonIgnore] @@ -259,6 +261,7 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] public Guid SeasonId { get; set; } + [JsonIgnore] public Guid SeriesId { get; set; } @@ -316,9 +319,9 @@ namespace MediaBrowser.Controller.Entities.TV return id; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!IsLocked) { @@ -326,7 +329,7 @@ namespace MediaBrowser.Controller.Entities.TV { try { - if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetdata)) + if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetadata)) { hasChanges = true; } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 5b8168d3d6..5e2053dcc7 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -79,7 +81,7 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// This Episode's Series Instance. + /// Gets this Episode's Series Instance. /// /// The series. [JsonIgnore] @@ -240,9 +242,9 @@ namespace MediaBrowser.Controller.Entities.TV /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// /// true if XXXX, false otherwise. - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path)) { diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 9f9a2ad50f..44d07b4a48 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -57,8 +59,11 @@ namespace MediaBrowser.Controller.Entities.TV public IReadOnlyList RemoteTrailerIds { get; set; } /// - /// airdate, dvd or absolute. + /// Gets or sets the display order. /// + /// + /// Valid options are airdate, dvd or absolute. + /// public string DisplayOrder { get; set; } /// @@ -316,20 +321,13 @@ namespace MediaBrowser.Controller.Entities.TV cancellationToken.ThrowIfCancellationRequested(); - var skipItem = false; - - var episode = item as Episode; - - if (episode != null + bool skipItem = item is Episode episode && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh && !refreshOptions.ReplaceAllMetadata && episode.IsMissingEpisode && episode.LocationType == LocationType.Virtual && episode.PremiereDate.HasValue - && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30) - { - skipItem = true; - } + && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30; if (!skipItem) { diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 9ae8ad7087..732b45521b 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -43,9 +45,9 @@ namespace MediaBrowser.Controller.Entities return info; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!ProductionYear.HasValue) { diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index db63c42e4c..6ab2116d73 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -94,7 +96,7 @@ namespace MediaBrowser.Controller.Entities public const double MinLikeValue = 6.5; /// - /// This is an interpreted property to indicate likes or dislikes + /// Gets or sets a value indicating whether the item is liked or not. /// This should never be serialized. /// /// null if [likes] contains no value, true if [likes]; otherwise, false. diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 30e0818450..120cb8d9b0 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -94,10 +96,10 @@ namespace MediaBrowser.Controller.Entities return list; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { ClearCache(); - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index b1da4d64cb..1e6c01bf8b 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -13,13 +15,19 @@ namespace MediaBrowser.Controller.Entities { public class UserView : Folder, IHasCollectionType { - /// + /// + /// Gets or sets the view type. + /// public string ViewType { get; set; } - /// + /// + /// Gets or sets the display parent id. + /// public new Guid DisplayParentId { get; set; } - /// + /// + /// Gets or sets the user id. + /// public Guid? UserId { get; set; } public static ITVSeriesManager TVSeriesManager; @@ -75,10 +83,7 @@ namespace MediaBrowser.Controller.Entities public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { - if (query == null) - { - query = new InternalItemsQuery(user); - } + query ??= new InternalItemsQuery(user); query.EnableTotalRecordCount = false; var result = GetItemList(query); @@ -111,10 +116,10 @@ namespace MediaBrowser.Controller.Entities return GetChildren(user, false); } - private static string[] UserSpecificViewTypes = new string[] - { - Model.Entities.CollectionType.Playlists - }; + private static readonly string[] UserSpecificViewTypes = new string[] + { + Model.Entities.CollectionType.Playlists + }; public static bool IsUserSpecific(Folder folder) { diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 78a64d8c95..15a4573c2e 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 6320b01b87..723027a883 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index b2e4d307a6..f268bc939e 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -22,7 +24,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. @@ -110,11 +112,13 @@ namespace MediaBrowser.Controller.Entities } /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made. + /// This is called before any metadata refresh and returns true if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + /// Whether to replace all metadata. + /// true if the item has change, else false. + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Events/IEventConsumer.cs b/MediaBrowser.Controller/Events/IEventConsumer.cs index 5c4ab5d8dd..93005134a7 100644 --- a/MediaBrowser.Controller/Events/IEventConsumer.cs +++ b/MediaBrowser.Controller/Events/IEventConsumer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace MediaBrowser.Controller.Events diff --git a/MediaBrowser.Controller/Events/IEventManager.cs b/MediaBrowser.Controller/Events/IEventManager.cs index a1f40b3a6d..074e3f1fe4 100644 --- a/MediaBrowser.Controller/Events/IEventManager.cs +++ b/MediaBrowser.Controller/Events/IEventManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace MediaBrowser.Controller.Events diff --git a/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs b/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs index 46d7e5a17a..3a331ad00f 100644 --- a/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs +++ b/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Session; namespace MediaBrowser.Controller.Events.Session diff --git a/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs b/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs index aab19cc46a..deeaaf55de 100644 --- a/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs +++ b/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Session; namespace MediaBrowser.Controller.Events.Session diff --git a/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs index b06046c05a..0dd8b0dbfd 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Updates; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs index dfadc9f61f..c1d503a7eb 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Updates; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs index 045a600272..7a9866834a 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Updates; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs index 661ca066a8..b078e06dc8 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Updates; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs index 182c8ef658..1853896eec 100644 --- a/MediaBrowser.Controller/Extensions/StringExtensions.cs +++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs @@ -1,7 +1,7 @@ -#nullable enable #pragma warning disable CS1591 using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Extensions { // will throw if input contains invalid unicode chars // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ - text = Regex.Replace(text, "([\ud800-\udbff](?![\udc00-\udfff]))|((? public interface IIntroProvider { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + /// /// Gets the intros. /// @@ -22,11 +30,5 @@ namespace MediaBrowser.Controller.Library /// /// IEnumerable{System.String}. IEnumerable GetAllIntroFiles(); - - /// - /// Gets the name. - /// - /// The name. - string Name { get; } } } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 80fcf71f3e..3fd4ff899a 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -41,6 +43,12 @@ namespace MediaBrowser.Controller.Library /// /// Resolves a set of files into a list of BaseItem. /// + /// The list of tiles. + /// Instance of the interface. + /// The parent folder. + /// The library options. + /// The collection type. + /// The items resolved from the paths. IEnumerable ResolvePaths( IEnumerable files, IDirectoryService directoryService, @@ -466,6 +474,15 @@ namespace MediaBrowser.Controller.Library /// The people. void UpdatePeople(BaseItem item, List people); + /// + /// Asynchronously updates the people. + /// + /// The item. + /// The people. + /// The cancellation token. + /// The async task. + Task UpdatePeopleAsync(BaseItem item, List people, CancellationToken cancellationToken); + /// /// Gets the item ids. /// diff --git a/MediaBrowser.Controller/Library/ILiveStream.cs b/MediaBrowser.Controller/Library/ILiveStream.cs index ff25be6577..85d866de5c 100644 --- a/MediaBrowser.Controller/Library/ILiveStream.cs +++ b/MediaBrowser.Controller/Library/ILiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Threading; diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 21c6ef2af1..d3d85a0563 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/IMetadataSaver.cs b/MediaBrowser.Controller/Library/IMetadataSaver.cs index 027cc5b40e..5fbfad8814 100644 --- a/MediaBrowser.Controller/Library/IMetadataSaver.cs +++ b/MediaBrowser.Controller/Library/IMetadataSaver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Library/IMusicManager.cs b/MediaBrowser.Controller/Library/IMusicManager.cs index d12f008e77..5329841bf5 100644 --- a/MediaBrowser.Controller/Library/IMusicManager.cs +++ b/MediaBrowser.Controller/Library/IMusicManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index c6a83e4dc4..58499e8531 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 6e267834b0..c95b0ea321 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/IUserViewManager.cs b/MediaBrowser.Controller/Library/IUserViewManager.cs index 8d541e8b68..46004e42f7 100644 --- a/MediaBrowser.Controller/Library/IUserViewManager.cs +++ b/MediaBrowser.Controller/Library/IUserViewManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/IntroInfo.cs b/MediaBrowser.Controller/Library/IntroInfo.cs index 283cc631ca..90786786b2 100644 --- a/MediaBrowser.Controller/Library/IntroInfo.cs +++ b/MediaBrowser.Controller/Library/IntroInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs b/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs index 1798a4fada..a37dc7af11 100644 --- a/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs +++ b/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index 12a311dc33..0e2d8fb021 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -14,14 +16,14 @@ namespace MediaBrowser.Controller.Library /// These are arguments relating to the file system that are collected once and then referred to /// whenever needed. Primarily for entity resolution. /// - public class ItemResolveArgs : EventArgs + public class ItemResolveArgs { /// /// The _app paths. /// private readonly IServerApplicationPaths _appPaths; - public IDirectoryService DirectoryService { get; private set; } + private LibraryOptions _libraryOptions; /// /// Initializes a new instance of the class. @@ -34,17 +36,18 @@ namespace MediaBrowser.Controller.Library DirectoryService = directoryService; } + public IDirectoryService DirectoryService { get; } + /// /// Gets the file system children. /// /// The file system children. public FileSystemMetadata[] FileSystemChildren { get; set; } - public LibraryOptions LibraryOptions { get; set; } - - public LibraryOptions GetLibraryOptions() + public LibraryOptions LibraryOptions { - return LibraryOptions ?? (LibraryOptions = Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent)); + get => _libraryOptions ??= Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent); + set => _libraryOptions = value; } /// @@ -60,10 +63,10 @@ namespace MediaBrowser.Controller.Library public FileSystemMetadata FileInfo { get; set; } /// - /// Gets or sets the path. + /// Gets the path. /// /// The path. - public string Path { get; set; } + public string Path => FileInfo.FullName; /// /// Gets a value indicating whether this instance is directory. @@ -139,7 +142,7 @@ namespace MediaBrowser.Controller.Library /// Adds the additional location. /// /// The path. - /// + /// is null or empty. public void AddAdditionalLocation(string path) { if (string.IsNullOrEmpty(path)) @@ -147,11 +150,7 @@ namespace MediaBrowser.Controller.Library throw new ArgumentException("The path was empty or null.", nameof(path)); } - if (AdditionalLocations == null) - { - AdditionalLocations = new List(); - } - + AdditionalLocations ??= new List(); AdditionalLocations.Add(path); } @@ -175,7 +174,7 @@ namespace MediaBrowser.Controller.Library /// /// The name. /// FileSystemInfo. - /// + /// is null or empty. public FileSystemMetadata GetFileSystemEntryByName(string name) { if (string.IsNullOrEmpty(name)) diff --git a/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs b/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs index 9581603f04..7bc8fa5abd 100644 --- a/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs +++ b/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs b/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs index 884f9e7735..41cfcae163 100644 --- a/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Common.Configuration; diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs index 6e79dc8ddf..29bfeca097 100644 --- a/MediaBrowser.Controller/Library/NameExtensions.cs +++ b/MediaBrowser.Controller/Library/NameExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index a2be3a42ab..609336ec4d 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs index ac372bceba..2138fef580 100644 --- a/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Controller.Library +namespace MediaBrowser.Controller.Library { /// /// An event that occurs when playback is started. diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs deleted file mode 100644 index 5efdc6a481..0000000000 --- a/MediaBrowser.Controller/Library/Profiler.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Controller.Library -{ - /// - /// Class Profiler. - /// - public class Profiler : IDisposable - { - /// - /// The name. - /// - readonly string _name; - - /// - /// The stopwatch. - /// - readonly Stopwatch _stopwatch; - - /// - /// The _logger. - /// - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The logger. - public Profiler(string name, ILogger logger) - { - this._name = name; - - _logger = logger; - - _stopwatch = new Stopwatch(); - _stopwatch.Start(); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - _stopwatch.Stop(); - string message; - if (_stopwatch.ElapsedMilliseconds > 300000) - { - message = string.Format( - CultureInfo.InvariantCulture, - "{0} took {1} minutes.", - _name, - ((float)_stopwatch.ElapsedMilliseconds / 60000).ToString("F", CultureInfo.InvariantCulture)); - } - else - { - message = string.Format( - CultureInfo.InvariantCulture, - "{0} took {1} seconds.", - _name, - ((float)_stopwatch.ElapsedMilliseconds / 1000).ToString("#0.000", CultureInfo.InvariantCulture)); - } - - _logger.LogInformation(message); - } - } - } -} diff --git a/MediaBrowser.Controller/Library/SearchHintInfo.cs b/MediaBrowser.Controller/Library/SearchHintInfo.cs index 897c2b7f49..de7806adc3 100644 --- a/MediaBrowser.Controller/Library/SearchHintInfo.cs +++ b/MediaBrowser.Controller/Library/SearchHintInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Library diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index a3aa6019ec..968338dc6a 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace MediaBrowser.Controller.Library { @@ -12,7 +13,8 @@ namespace MediaBrowser.Controller.Library /// /// The day. /// List{DayOfWeek}. - public static DayOfWeek[] GetAirDays(string day) + [return: NotNullIfNotNull("day")] + public static DayOfWeek[]? GetAirDays(string? day) { if (!string.IsNullOrEmpty(day)) { diff --git a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs index cd91097535..bfe433c971 100644 --- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs +++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs b/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs new file mode 100644 index 0000000000..463061e686 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs @@ -0,0 +1,19 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Threading; + +namespace MediaBrowser.Controller.LiveTv +{ + public class ActiveRecordingInfo + { + public string Id { get; set; } + + public string Path { get; set; } + + public TimerInfo Timer { get; set; } + + public CancellationTokenSource CancellationTokenSource { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index 166c4d77c0..699c15f934 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.LiveTv; @@ -22,7 +24,7 @@ namespace MediaBrowser.Controller.LiveTv public string Number { get; set; } /// - /// Get or sets the Id. + /// Gets or sets the Id. /// /// The id of the channel. public string Id { get; set; } @@ -52,13 +54,13 @@ namespace MediaBrowser.Controller.LiveTv public string ChannelGroup { get; set; } /// - /// Supply the image path if it can be accessed directly from the file system. + /// Gets or sets the the image path if it can be accessed directly from the file system. /// /// The image path. public string ImagePath { get; set; } /// - /// Supply the image url if it can be downloaded. + /// Gets or sets the image url if it can be downloaded. /// /// The image URL. public string ImageUrl { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs index 038ff2eaeb..2bd4b20e8c 100644 --- a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs +++ b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 54495c1c40..f4dc18e11c 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -266,16 +268,21 @@ namespace MediaBrowser.Controller.LiveTv void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user); Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); + Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); IListingsProvider[] ListingProviders { get; } List GetTunerHostTypes(); + Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); event EventHandler> SeriesTimerCancelled; + event EventHandler> TimerCancelled; + event EventHandler> TimerCreated; + event EventHandler> SeriesTimerCreated; string GetEmbyTvActiveRecordingPath(string id); @@ -286,15 +293,4 @@ namespace MediaBrowser.Controller.LiveTv List GetRecordingFolders(User user); } - - public class ActiveRecordingInfo - { - public string Id { get; set; } - - public string Path { get; set; } - - public TimerInfo Timer { get; set; } - - public CancellationTokenSource CancellationTokenSource { get; set; } - } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index 3ca1d165ef..897f263f3d 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index abca8f2390..7dced9f5ef 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index ec933caf34..1a893fc2d0 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -146,7 +148,7 @@ namespace MediaBrowser.Controller.LiveTv public bool IsNews { get; set; } /// - /// Gets or sets a value indicating whether this instance is kids. + /// Gets a value indicating whether this instance is kids. /// /// true if this instance is kids; otherwise, false. [JsonIgnore] diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 43af495dd6..e2adec0009 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -69,7 +71,7 @@ namespace MediaBrowser.Controller.LiveTv public override SourceType SourceType => SourceType.LiveTV; /// - /// The start date of the program, in UTC. + /// Gets or sets start date of the program, in UTC. /// [JsonIgnore] public DateTime StartDate { get; set; } @@ -99,7 +101,7 @@ namespace MediaBrowser.Controller.LiveTv public bool IsMovie { get; set; } /// - /// Gets or sets a value indicating whether this instance is sports. + /// Gets a value indicating whether this instance is sports. /// /// true if this instance is sports; otherwise, false. [JsonIgnore] @@ -113,35 +115,35 @@ namespace MediaBrowser.Controller.LiveTv public bool IsSeries { get; set; } /// - /// Gets or sets a value indicating whether this instance is live. + /// Gets a value indicating whether this instance is live. /// /// true if this instance is live; otherwise, false. [JsonIgnore] public bool IsLive => Tags.Contains("Live", StringComparer.OrdinalIgnoreCase); /// - /// Gets or sets a value indicating whether this instance is news. + /// Gets a value indicating whether this instance is news. /// /// true if this instance is news; otherwise, false. [JsonIgnore] public bool IsNews => Tags.Contains("News", StringComparer.OrdinalIgnoreCase); /// - /// Gets or sets a value indicating whether this instance is kids. + /// Gets a value indicating whether this instance is kids. /// /// true if this instance is kids; otherwise, false. [JsonIgnore] public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase); /// - /// Gets or sets a value indicating whether this instance is premiere. + /// Gets a value indicating whether this instance is premiere. /// /// true if this instance is premiere; otherwise, false. [JsonIgnore] public bool IsPremiere => Tags.Contains("Premiere", StringComparer.OrdinalIgnoreCase); /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. diff --git a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs index b629749049..eb3babc180 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs index 739978e7ce..aa5eb59d16 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index f9f559ee96..3c3ac2471f 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -8,8 +10,16 @@ namespace MediaBrowser.Controller.LiveTv { public class ProgramInfo { + public ProgramInfo() + { + Genres = new List(); + + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + /// - /// Id of the program. + /// Gets or sets the id of the program. /// public string Id { get; set; } @@ -20,7 +30,7 @@ namespace MediaBrowser.Controller.LiveTv public string ChannelId { get; set; } /// - /// Name of the program. + /// Gets or sets the name of the program. /// public string Name { get; set; } @@ -43,17 +53,17 @@ namespace MediaBrowser.Controller.LiveTv public string ShortOverview { get; set; } /// - /// The start date of the program, in UTC. + /// Gets or sets the start date of the program, in UTC. /// public DateTime StartDate { get; set; } /// - /// The end date of the program, in UTC. + /// Gets or sets the end date of the program, in UTC. /// public DateTime EndDate { get; set; } /// - /// Genre of the program. + /// Gets or sets the genre of the program. /// public List Genres { get; set; } @@ -69,6 +79,9 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is hd; otherwise, false. public bool? IsHD { get; set; } + /// + /// Gets or sets a value indicating whether this instance is 3d. + /// public bool? Is3D { get; set; } /// @@ -98,13 +111,13 @@ namespace MediaBrowser.Controller.LiveTv public string EpisodeTitle { get; set; } /// - /// Supply the image path if it can be accessed directly from the file system. + /// Gets or sets the image path if it can be accessed directly from the file system. /// /// The image path. public string ImagePath { get; set; } /// - /// Supply the image url if it can be downloaded. + /// Gets or sets the image url if it can be downloaded. /// /// The image URL. public string ImageUrl { get; set; } @@ -210,13 +223,5 @@ namespace MediaBrowser.Controller.LiveTv public Dictionary ProviderIds { get; set; } public Dictionary SeriesProviderIds { get; set; } - - public ProgramInfo() - { - Genres = new List(); - - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs index 69190694ff..1dcf7a58fe 100644 --- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs +++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -8,8 +10,13 @@ namespace MediaBrowser.Controller.LiveTv { public class RecordingInfo { + public RecordingInfo() + { + Genres = new List(); + } + /// - /// Id of the recording. + /// Gets or sets the id of the recording. /// public string Id { get; set; } @@ -26,7 +33,7 @@ namespace MediaBrowser.Controller.LiveTv public string TimerId { get; set; } /// - /// ChannelId of the recording. + /// Gets or sets the channelId of the recording. /// public string ChannelId { get; set; } @@ -37,7 +44,7 @@ namespace MediaBrowser.Controller.LiveTv public ChannelType ChannelType { get; set; } /// - /// Name of the recording. + /// Gets or sets the name of the recording. /// public string Name { get; set; } @@ -60,12 +67,12 @@ namespace MediaBrowser.Controller.LiveTv public string Overview { get; set; } /// - /// The start date of the recording, in UTC. + /// Gets or sets the start date of the recording, in UTC. /// public DateTime StartDate { get; set; } /// - /// The end date of the recording, in UTC. + /// Gets or sets the end date of the recording, in UTC. /// public DateTime EndDate { get; set; } @@ -82,7 +89,7 @@ namespace MediaBrowser.Controller.LiveTv public RecordingStatus Status { get; set; } /// - /// Genre of the program. + /// Gets or sets the genre of the program. /// public List Genres { get; set; } @@ -171,13 +178,13 @@ namespace MediaBrowser.Controller.LiveTv public float? CommunityRating { get; set; } /// - /// Supply the image path if it can be accessed directly from the file system. + /// Gets or sets the image path if it can be accessed directly from the file system. /// /// The image path. public string ImagePath { get; set; } /// - /// Supply the image url if it can be downloaded. + /// Gets or sets the image url if it can be downloaded. /// /// The image URL. public string ImageUrl { get; set; } @@ -199,10 +206,5 @@ namespace MediaBrowser.Controller.LiveTv /// /// The date last updated. public DateTime DateLastUpdated { get; set; } - - public RecordingInfo() - { - Genres = new List(); - } } } diff --git a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs b/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs index 847c0ea8c0..0b943c9396 100644 --- a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs +++ b/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index 1343ecd982..d6811fe14e 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -8,13 +10,20 @@ namespace MediaBrowser.Controller.LiveTv { public class SeriesTimerInfo { + public SeriesTimerInfo() + { + Days = new List(); + SkipEpisodesInLibrary = true; + KeepUntil = KeepUntil.UntilDeleted; + } + /// - /// Id of the recording. + /// Gets or sets the id of the recording. /// public string Id { get; set; } /// - /// ChannelId of the recording. + /// Gets or sets the channelId of the recording. /// public string ChannelId { get; set; } @@ -25,24 +34,27 @@ namespace MediaBrowser.Controller.LiveTv public string ProgramId { get; set; } /// - /// Name of the recording. + /// Gets or sets the name of the recording. /// public string Name { get; set; } + /// + /// Gets or sets the service name. + /// public string ServiceName { get; set; } /// - /// Description of the recording. + /// Gets or sets the description of the recording. /// public string Overview { get; set; } /// - /// The start date of the recording, in UTC. + /// Gets or sets the start date of the recording, in UTC. /// public DateTime StartDate { get; set; } /// - /// The end date of the recording, in UTC. + /// Gets or sets the end date of the recording, in UTC. /// public DateTime EndDate { get; set; } @@ -111,12 +123,5 @@ namespace MediaBrowser.Controller.LiveTv /// /// The series identifier. public string SeriesId { get; set; } - - public SeriesTimerInfo() - { - Days = new List(); - SkipEpisodesInLibrary = true; - KeepUntil = KeepUntil.UntilDeleted; - } } } diff --git a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs index 1b8f41db69..728387c56d 100644 --- a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #nullable enable #pragma warning disable CS1591 diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index aa5170617d..1a2e8acb31 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -26,18 +28,17 @@ namespace MediaBrowser.Controller.LiveTv public string[] Tags { get; set; } /// - /// Id of the recording. + /// Gets or sets the id of the recording. /// public string Id { get; set; } /// /// Gets or sets the series timer identifier. /// - /// The series timer identifier. public string SeriesTimerId { get; set; } /// - /// ChannelId of the recording. + /// Gets or sets the channelId of the recording. /// public string ChannelId { get; set; } @@ -50,24 +51,24 @@ namespace MediaBrowser.Controller.LiveTv public string ShowId { get; set; } /// - /// Name of the recording. + /// Gets or sets the name of the recording. /// public string Name { get; set; } /// - /// Description of the recording. + /// Gets or sets the description of the recording. /// public string Overview { get; set; } public string SeriesId { get; set; } /// - /// The start date of the recording, in UTC. + /// Gets or sets the start date of the recording, in UTC. /// public DateTime StartDate { get; set; } /// - /// The end date of the recording, in UTC. + /// Gets or sets the end date of the recording, in UTC. /// public DateTime EndDate { get; set; } @@ -131,7 +132,7 @@ namespace MediaBrowser.Controller.LiveTv public bool IsSeries { get; set; } /// - /// Gets or sets a value indicating whether this instance is live. + /// Gets a value indicating whether this instance is live. /// /// true if this instance is live; otherwise, false. [JsonIgnore] diff --git a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs index 2759b314f5..1c1a4417dc 100644 --- a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs +++ b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.LiveTv diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 8c68b47dd7..3f3a505eaf 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -16,7 +16,7 @@ - + @@ -34,6 +34,7 @@ false true true + enable AllEnabledByDefault ../jellyfin.ruleset true diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2b53647758..feb5883e58 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -594,7 +596,8 @@ namespace MediaBrowser.Controller.MediaEncoding && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecDecoder) { - arg.Append("-hwaccel_output_format cuda -autorotate 0 "); + // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562 + arg.Append("-hwaccel_output_format cuda -extra_hw_frames 3 -autorotate 0 "); } if (state.IsVideoRequest @@ -1068,7 +1071,6 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc) { - // following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead. switch (encodingOptions.EncoderPreset) { case "veryslow": @@ -1249,7 +1251,7 @@ namespace MediaBrowser.Controller.MediaEncoding } if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase)) + && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase)) { profile = "constrained_baseline"; } @@ -2931,6 +2933,7 @@ namespace MediaBrowser.Controller.MediaEncoding return threads; } + #nullable disable public void TryStreamCopy(EncodingJobInfo state) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index d47a689f47..bc34785eeb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -428,7 +430,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target video level. /// public double? TargetVideoLevel { @@ -451,7 +453,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target video bit depth. /// public int? TargetVideoBitDepth { @@ -486,7 +488,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target framerate. /// public float? TargetFramerate { @@ -518,7 +520,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target packet length. /// public int? TargetPacketLength { @@ -534,7 +536,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target video profile. /// public string TargetVideoProfile { @@ -698,25 +700,4 @@ namespace MediaBrowser.Controller.MediaEncoding Progress.Report(percentComplete.Value); } } - - /// - /// Enum TranscodingJobType. - /// - public enum TranscodingJobType - { - /// - /// The progressive. - /// - Progressive, - - /// - /// The HLS. - /// - Hls, - - /// - /// The dash. - /// - Dash - } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index 1f3abe8f43..61f3bc7711 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -1,60 +1,13 @@ +#nullable disable + #pragma warning disable CS1591 using System; using System.Collections.Generic; -using System.Linq; using MediaBrowser.Model.Dlna; namespace MediaBrowser.Controller.MediaEncoding { - public class EncodingJobOptions : BaseEncodingJobOptions - { - public string OutputDirectory { get; set; } - - public string ItemId { get; set; } - - public string TempDirectory { get; set; } - - public bool ReadInputAtNativeFramerate { get; set; } - - /// - /// Gets a value indicating whether this instance has fixed resolution. - /// - /// true if this instance has fixed resolution; otherwise, false. - public bool HasFixedResolution => Width.HasValue || Height.HasValue; - - public DeviceProfile DeviceProfile { get; set; } - - public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile) - { - Container = info.Container; - StartTimeTicks = info.StartPositionTicks; - MaxWidth = info.MaxWidth; - MaxHeight = info.MaxHeight; - MaxFramerate = info.MaxFramerate; - Id = info.ItemId; - MediaSourceId = info.MediaSourceId; - AudioCodec = info.TargetAudioCodec.FirstOrDefault(); - MaxAudioChannels = info.GlobalMaxAudioChannels; - AudioBitRate = info.AudioBitrate; - AudioSampleRate = info.TargetAudioSampleRate; - DeviceProfile = deviceProfile; - VideoCodec = info.TargetVideoCodec.FirstOrDefault(); - VideoBitRate = info.VideoBitrate; - AudioStreamIndex = info.AudioStreamIndex; - SubtitleMethod = info.SubtitleDeliveryMethod; - Context = info.Context; - TranscodingMaxAudioChannels = info.TranscodingMaxAudioChannels; - - if (info.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) - { - SubtitleStreamIndex = info.SubtitleStreamIndex; - } - - StreamOptions = info.StreamOptions; - } - } - // For now until api and media encoding layers are unified public class BaseEncodingJobOptions { diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs index fbc8275341..c38e7ec3b3 100644 --- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs +++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs index 15a2580afd..773547872c 100644 --- a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs +++ b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 05dd1a69b1..76a9fd7c74 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -18,7 +20,7 @@ namespace MediaBrowser.Controller.MediaEncoding public interface IMediaEncoder : ITranscoderSupport { /// - /// The location of the discovered FFmpeg tool. + /// Gets location of the discovered FFmpeg tool. /// FFmpegLocation EncoderLocation { get; } diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs index 6ebf7f159b..3fb2c47e13 100644 --- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs index e7b4c8c15c..044ba6d331 100644 --- a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.MediaEncoding diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index 227c5f2587..aa5e2c4038 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 89e01c08b6..841e7b2872 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 - namespace MediaBrowser.Controller.MediaEncoding { /// diff --git a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs index 2cb04bdc4f..1dd8bcf31b 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.Dlna; diff --git a/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs b/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs new file mode 100644 index 0000000000..66b6283719 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Controller.MediaEncoding +{ + /// + /// Enum TranscodingJobType. + /// + public enum TranscodingJobType + { + /// + /// The progressive. + /// + Progressive, + + /// + /// The HLS. + /// + Hls, + + /// + /// The dash. + /// + Dash + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs index 93573e08e2..2452b25ab1 100644 --- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Jellyfin.Data.Entities; diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 163a9c8f86..855467e8e3 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index 04b2e13e8c..d15c6d3183 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs index a60dc2ea19..6b896b41f4 100644 --- a/MediaBrowser.Controller/Net/ISessionContext.cs +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -10,10 +10,10 @@ namespace MediaBrowser.Controller.Net { SessionInfo GetSession(object requestContext); - User GetUser(object requestContext); + User? GetUser(object requestContext); SessionInfo GetSession(HttpContext requestContext); - User GetUser(HttpContext requestContext); + User? GetUser(HttpContext requestContext); } } diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index e87f3bca68..e50cd97819 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 #nullable enable @@ -32,7 +34,7 @@ namespace MediaBrowser.Controller.Net DateTime LastKeepAliveDate { get; set; } /// - /// Gets or sets the query string. + /// Gets the query string. /// /// The query string. IQueryCollection QueryString { get; } diff --git a/MediaBrowser.Controller/Net/SecurityException.cs b/MediaBrowser.Controller/Net/SecurityException.cs index c6347133a8..f0d0b45a0a 100644 --- a/MediaBrowser.Controller/Net/SecurityException.cs +++ b/MediaBrowser.Controller/Net/SecurityException.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; namespace MediaBrowser.Controller.Net diff --git a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs index be0b3ddc3f..6f7ebf1565 100644 --- a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs +++ b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Model.Net; namespace MediaBrowser.Controller.Net diff --git a/MediaBrowser.Controller/Notifications/INotificationManager.cs b/MediaBrowser.Controller/Notifications/INotificationManager.cs index 08d9bc12a2..7caba1097a 100644 --- a/MediaBrowser.Controller/Notifications/INotificationManager.cs +++ b/MediaBrowser.Controller/Notifications/INotificationManager.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Notifications /// Task. Task SendNotification(NotificationRequest request, CancellationToken cancellationToken); - Task SendNotification(NotificationRequest request, BaseItem relatedItem, CancellationToken cancellationToken); + Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken); /// /// Adds the parts. diff --git a/MediaBrowser.Controller/Notifications/INotificationService.cs b/MediaBrowser.Controller/Notifications/INotificationService.cs index fa947220ad..535c08795b 100644 --- a/MediaBrowser.Controller/Notifications/INotificationService.cs +++ b/MediaBrowser.Controller/Notifications/INotificationService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Threading; diff --git a/MediaBrowser.Controller/Notifications/UserNotification.cs b/MediaBrowser.Controller/Notifications/UserNotification.cs index d768abfe73..4be0e09ae7 100644 --- a/MediaBrowser.Controller/Notifications/UserNotification.cs +++ b/MediaBrowser.Controller/Notifications/UserNotification.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 45c6805f0e..56fb36af2a 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -153,8 +155,7 @@ namespace MediaBrowser.Controller.Persistence /// /// Updates the inherited values. /// - /// The cancellation token. - void UpdateInheritedValues(CancellationToken cancellationToken); + void UpdateInheritedValues(); int GetCount(InternalItemsQuery query); diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index 81ba513cef..6f5f02123e 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Threading; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index c9c168c4c6..bb9e5da1e3 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -20,14 +22,14 @@ namespace MediaBrowser.Controller.Playlists { public class Playlist : Folder, IHasShares { - public static string[] SupportedExtensions = - { - ".m3u", - ".m3u8", - ".pls", - ".wpl", - ".zpl" - }; + public static readonly IReadOnlyList SupportedExtensions = new[] + { + ".m3u", + ".m3u8", + ".pls", + ".wpl", + ".zpl" + }; public Guid OwnerUserId { get; set; } @@ -126,10 +128,7 @@ namespace MediaBrowser.Controller.Playlists private List GetPlayableItems(User user, InternalItemsQuery query) { - if (query == null) - { - query = new InternalItemsQuery(user); - } + query ??= new InternalItemsQuery(user); query.IsFolder = false; diff --git a/MediaBrowser.Controller/Plugins/ILocalizablePlugin.cs b/MediaBrowser.Controller/Plugins/ILocalizablePlugin.cs deleted file mode 100644 index bf15fe0407..0000000000 --- a/MediaBrowser.Controller/Plugins/ILocalizablePlugin.cs +++ /dev/null @@ -1,22 +0,0 @@ -#pragma warning disable CS1591 - -using System.IO; -using System.Reflection; - -namespace MediaBrowser.Controller.Plugins -{ - public interface ILocalizablePlugin - { - Stream GetDictionary(string culture); - } - - public static class LocalizablePluginHelper - { - public static Stream GetDictionary(Assembly assembly, string manifestPrefix, string culture) - { - // Find all dictionaries using GetManifestResourceNames, start start with the prefix - // Return the one for the culture if exists, otherwise return the default - return null; - } - } -} diff --git a/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs b/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs new file mode 100644 index 0000000000..ea966c2827 --- /dev/null +++ b/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs @@ -0,0 +1,9 @@ +namespace MediaBrowser.Controller.Plugins +{ + /// + /// Indicates that a should be invoked as a pre-startup task. + /// + public interface IRunBeforeStartup + { + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs index b44e2531e1..6024661e15 100644 --- a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs +++ b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs @@ -14,13 +14,7 @@ namespace MediaBrowser.Controller.Plugins /// /// Run the initialization for this module. This method is invoked at application start. /// + /// A representing the asynchronous operation. Task RunAsync(); } - - /// - /// Indicates that a should be invoked as a pre-startup task. - /// - public interface IRunBeforeStartup - { - } } diff --git a/MediaBrowser.Controller/Providers/AlbumInfo.cs b/MediaBrowser.Controller/Providers/AlbumInfo.cs index 276bcf1252..c7fad5974a 100644 --- a/MediaBrowser.Controller/Providers/AlbumInfo.cs +++ b/MediaBrowser.Controller/Providers/AlbumInfo.cs @@ -7,6 +7,13 @@ namespace MediaBrowser.Controller.Providers { public class AlbumInfo : ItemLookupInfo { + public AlbumInfo() + { + ArtistProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + SongInfos = new List(); + AlbumArtists = Array.Empty(); + } + /// /// Gets or sets the album artist. /// @@ -20,12 +27,5 @@ namespace MediaBrowser.Controller.Providers public Dictionary ArtistProviderIds { get; set; } public List SongInfos { get; set; } - - public AlbumInfo() - { - ArtistProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - SongInfos = new List(); - AlbumArtists = Array.Empty(); - } } } diff --git a/MediaBrowser.Controller/Providers/ArtistInfo.cs b/MediaBrowser.Controller/Providers/ArtistInfo.cs index adf885baa6..e9181f4765 100644 --- a/MediaBrowser.Controller/Providers/ArtistInfo.cs +++ b/MediaBrowser.Controller/Providers/ArtistInfo.cs @@ -6,11 +6,11 @@ namespace MediaBrowser.Controller.Providers { public class ArtistInfo : ItemLookupInfo { - public List SongInfos { get; set; } - public ArtistInfo() { SongInfos = new List(); } + + public List SongInfos { get; set; } } } diff --git a/MediaBrowser.Controller/Providers/BookInfo.cs b/MediaBrowser.Controller/Providers/BookInfo.cs index cce0a25fcb..3055c5d871 100644 --- a/MediaBrowser.Controller/Providers/BookInfo.cs +++ b/MediaBrowser.Controller/Providers/BookInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Providers diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 5c92069b44..291a268830 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -43,18 +43,17 @@ namespace MediaBrowser.Controller.Providers return list; } - public FileSystemMetadata GetFile(string path) + public FileSystemMetadata? GetFile(string path) { - var result = _fileCache.GetOrAdd(path, p => + if (!_fileCache.TryGetValue(path, out var result)) { - var file = _fileSystem.GetFileInfo(p); - return file != null && file.Exists ? file : null; - }); - - if (result == null) - { - // lets not store null results in the cache - _fileCache.TryRemove(path, out _); + var file = _fileSystem.GetFileInfo(path); + var res = file != null && file.Exists ? file : null; + if (res != null) + { + result = res; + _fileCache.TryAdd(path, result); + } } return result; diff --git a/MediaBrowser.Controller/Providers/DynamicImageResponse.cs b/MediaBrowser.Controller/Providers/DynamicImageResponse.cs index 006174be8c..66fddb0e38 100644 --- a/MediaBrowser.Controller/Providers/DynamicImageResponse.cs +++ b/MediaBrowser.Controller/Providers/DynamicImageResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/EpisodeInfo.cs b/MediaBrowser.Controller/Providers/EpisodeInfo.cs index a4c8dab7ea..0c932fa877 100644 --- a/MediaBrowser.Controller/Providers/EpisodeInfo.cs +++ b/MediaBrowser.Controller/Providers/EpisodeInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.Providers { public class EpisodeInfo : ItemLookupInfo { + public EpisodeInfo() + { + SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + public Dictionary SeriesProviderIds { get; set; } public int? IndexNumberEnd { get; set; } @@ -14,10 +21,5 @@ namespace MediaBrowser.Controller.Providers public bool IsMissingEpisode { get; set; } public string SeriesDisplayOrder { get; set; } - - public EpisodeInfo() - { - SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.Controller/Providers/IDirectoryService.cs b/MediaBrowser.Controller/Providers/IDirectoryService.cs index f06481c7a9..9cee06a4c6 100644 --- a/MediaBrowser.Controller/Providers/IDirectoryService.cs +++ b/MediaBrowser.Controller/Providers/IDirectoryService.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Providers List GetFiles(string path); - FileSystemMetadata GetFile(string path); + FileSystemMetadata? GetFile(string path); IReadOnlyList GetFilePaths(string path); diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index 5e38446bc9..e2dbef2bc1 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; diff --git a/MediaBrowser.Controller/Providers/IMetadataService.cs b/MediaBrowser.Controller/Providers/IMetadataService.cs index 5f3d4274ef..05fbb18ee4 100644 --- a/MediaBrowser.Controller/Providers/IMetadataService.cs +++ b/MediaBrowser.Controller/Providers/IMetadataService.cs @@ -10,6 +10,12 @@ namespace MediaBrowser.Controller.Providers { public interface IMetadataService { + /// + /// Gets the order. + /// + /// The order. + int Order { get; } + /// /// Determines whether this instance can refresh the specified item. /// @@ -27,11 +33,5 @@ namespace MediaBrowser.Controller.Providers /// The cancellation token. /// Task. Task RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken); - - /// - /// Gets the order. - /// - /// The order. - int Order { get; } } } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 7bc56c82ad..684bd9e681 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -189,11 +191,4 @@ namespace MediaBrowser.Controller.Providers double? GetRefreshProgress(Guid id); } - - public enum RefreshPriority - { - High = 0, - Normal = 1, - Low = 2 - } } diff --git a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs index 9fc379f045..81a22affb0 100644 --- a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/ItemInfo.cs b/MediaBrowser.Controller/Providers/ItemInfo.cs index b50def043f..b8dd416a2d 100644 --- a/MediaBrowser.Controller/Providers/ItemInfo.cs +++ b/MediaBrowser.Controller/Providers/ItemInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -14,8 +16,7 @@ namespace MediaBrowser.Controller.Providers ContainingFolderPath = item.ContainingFolderPath; IsInMixedFolder = item.IsInMixedFolder; - var video = item as Video; - if (video != null) + if (item is Video video) { VideoType = video.VideoType; IsPlaceHolder = video.IsPlaceHolder; diff --git a/MediaBrowser.Controller/Providers/ItemLookupInfo.cs b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs index b777cc1d31..e6f49c26ae 100644 --- a/MediaBrowser.Controller/Providers/ItemLookupInfo.cs +++ b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -8,6 +10,12 @@ namespace MediaBrowser.Controller.Providers { public class ItemLookupInfo : IHasProviderIds { + public ItemLookupInfo() + { + IsAutomated = true; + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + /// /// Gets or sets the name. /// @@ -51,11 +59,5 @@ namespace MediaBrowser.Controller.Providers public DateTime? PremiereDate { get; set; } public bool IsAutomated { get; set; } - - public ItemLookupInfo() - { - IsAutomated = true; - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.Controller/Providers/LocalImageInfo.cs b/MediaBrowser.Controller/Providers/LocalImageInfo.cs index 41801862fd..a8e70e6d08 100644 --- a/MediaBrowser.Controller/Providers/LocalImageInfo.cs +++ b/MediaBrowser.Controller/Providers/LocalImageInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index b92b837012..2cf5367793 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -9,21 +11,6 @@ namespace MediaBrowser.Controller.Providers { public class MetadataRefreshOptions : ImageRefreshOptions { - /// - /// When paired with MetadataRefreshMode=FullRefresh, all existing data will be overwritten with new data from the providers. - /// - public bool ReplaceAllMetadata { get; set; } - - public MetadataRefreshMode MetadataRefreshMode { get; set; } - - public RemoteSearchResult SearchResult { get; set; } - - public string[] RefreshPaths { get; set; } - - public bool ForceSave { get; set; } - - public bool EnableRemoteContentProbe { get; set; } - public MetadataRefreshOptions(IDirectoryService directoryService) : base(directoryService) { @@ -45,15 +32,28 @@ namespace MediaBrowser.Controller.Providers if (copy.RefreshPaths != null && copy.RefreshPaths.Length > 0) { - if (RefreshPaths == null) - { - RefreshPaths = Array.Empty(); - } + RefreshPaths ??= Array.Empty(); RefreshPaths = copy.RefreshPaths.ToArray(); } } + /// + /// Gets or sets a value indicating whether all existing data should be overwritten with new data from providers + /// when paired with MetadataRefreshMode=FullRefresh. + /// + public bool ReplaceAllMetadata { get; set; } + + public MetadataRefreshMode MetadataRefreshMode { get; set; } + + public RemoteSearchResult SearchResult { get; set; } + + public string[] RefreshPaths { get; set; } + + public bool ForceSave { get; set; } + + public bool EnableRemoteContentProbe { get; set; } + public bool RefreshItem(BaseItem item) { if (RefreshPaths != null && RefreshPaths.Length > 0) diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index 864cb30506..8b0967a6e6 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -37,10 +39,7 @@ namespace MediaBrowser.Controller.Providers public void AddPerson(PersonInfo p) { - if (People == null) - { - People = new List(); - } + People ??= new List(); PeopleHelper.AddPerson(People, p); } @@ -54,16 +53,15 @@ namespace MediaBrowser.Controller.Providers { People = new List(); } - - People.Clear(); + else + { + People.Clear(); + } } public UserItemData GetOrAddUserData(string userId) { - if (UserDataList == null) - { - UserDataList = new List(); - } + UserDataList ??= new List(); UserItemData userData = null; diff --git a/MediaBrowser.Controller/Providers/MusicVideoInfo.cs b/MediaBrowser.Controller/Providers/MusicVideoInfo.cs index 0b927f6eb0..322320abdf 100644 --- a/MediaBrowser.Controller/Providers/MusicVideoInfo.cs +++ b/MediaBrowser.Controller/Providers/MusicVideoInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Providers/RefreshPriority.cs b/MediaBrowser.Controller/Providers/RefreshPriority.cs new file mode 100644 index 0000000000..3619f679d6 --- /dev/null +++ b/MediaBrowser.Controller/Providers/RefreshPriority.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Controller.Providers +{ + /// + /// Provider refresh priority. + /// + public enum RefreshPriority + { + /// + /// High priority. + /// + High = 0, + + /// + /// Normal priority. + /// + Normal = 1, + + /// + /// Low priority. + /// + Low = 2 + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs b/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs index 9653bc1c4a..d4df5fa0db 100644 --- a/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs +++ b/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -12,7 +14,7 @@ namespace MediaBrowser.Controller.Providers public Guid ItemId { get; set; } /// - /// Will only search within the given provider when set. + /// Gets or sets the provider name to search within if set. /// public string SearchProviderName { get; set; } diff --git a/MediaBrowser.Controller/Providers/SeasonInfo.cs b/MediaBrowser.Controller/Providers/SeasonInfo.cs index 2a4c1f03c7..7e39bc37a6 100644 --- a/MediaBrowser.Controller/Providers/SeasonInfo.cs +++ b/MediaBrowser.Controller/Providers/SeasonInfo.cs @@ -7,11 +7,11 @@ namespace MediaBrowser.Controller.Providers { public class SeasonInfo : ItemLookupInfo { - public Dictionary SeriesProviderIds { get; set; } - public SeasonInfo() { SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); } + + public Dictionary SeriesProviderIds { get; set; } } } diff --git a/MediaBrowser.Controller/Providers/SongInfo.cs b/MediaBrowser.Controller/Providers/SongInfo.cs index 58f76dca91..c90717a2e1 100644 --- a/MediaBrowser.Controller/Providers/SongInfo.cs +++ b/MediaBrowser.Controller/Providers/SongInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs index 959a2d7712..c4e709c245 100644 --- a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Model.QuickConnect; diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs index 25128a5cd0..e77593a036 100644 --- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -10,6 +12,12 @@ namespace MediaBrowser.Controller.Resolvers public abstract class ItemResolver : IItemResolver where T : BaseItem, new() { + /// + /// Gets the priority. + /// + /// The priority. + public virtual ResolverPriority Priority => ResolverPriority.First; + /// /// Resolves the specified args. /// @@ -20,12 +28,6 @@ namespace MediaBrowser.Controller.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/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs b/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs index bb80e60256..a07b3e8988 100644 --- a/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs +++ b/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.Resolvers { /// - /// Provides a base "rule" that anyone can use to have paths ignored by the resolver + /// Provides a base "rule" that anyone can use to have paths ignored by the resolver. /// public interface IResolverIgnoreRule { diff --git a/MediaBrowser.Controller/Security/AuthenticationInfo.cs b/MediaBrowser.Controller/Security/AuthenticationInfo.cs index efac9273ec..b4b242f1b2 100644 --- a/MediaBrowser.Controller/Security/AuthenticationInfo.cs +++ b/MediaBrowser.Controller/Security/AuthenticationInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs index c5f3da0b1b..3af6a525c7 100644 --- a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs +++ b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs index 883b74165c..1dd69ccd85 100644 --- a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs +++ b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.Devices; diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs index 8c3ac58f2b..647c75e66e 100644 --- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index bc4ccd44ca..6bc39d6f4e 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 6c06dcad58..4c3cf5ffe1 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -344,21 +346,19 @@ namespace MediaBrowser.Controller.Session /// Logouts the specified access token. /// /// The access token. - /// Task. void Logout(string accessToken); + void Logout(AuthenticationInfo accessToken); /// /// Revokes the user tokens. /// - /// Task. void RevokeUserTokens(Guid userId, string currentAccessToken); /// /// Revokes the token. /// /// The identifier. - /// Task. void RevokeToken(string id); void CloseIfNeeded(SessionInfo session); diff --git a/MediaBrowser.Controller/Session/SessionEventArgs.cs b/MediaBrowser.Controller/Session/SessionEventArgs.cs index 097e32eaec..269fe7dc4d 100644 --- a/MediaBrowser.Controller/Session/SessionEventArgs.cs +++ b/MediaBrowser.Controller/Session/SessionEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index d09852870e..6134c0cf33 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -52,7 +54,7 @@ namespace MediaBrowser.Controller.Session public string RemoteEndPoint { get; set; } /// - /// Gets or sets the playable media types. + /// Gets the playable media types. /// /// The playable media types. public IReadOnlyList PlayableMediaTypes @@ -228,7 +230,7 @@ namespace MediaBrowser.Controller.Session public string UserPrimaryImageTag { get; set; } /// - /// Gets or sets the supported commands. + /// Gets the supported commands. /// /// The supported commands. public IReadOnlyList SupportedCommands diff --git a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs index 70cb9eebe0..4d9b98889f 100644 --- a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs +++ b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs @@ -1,7 +1,5 @@ #pragma warning disable CS1591 -#nullable enable - using System; using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs index 727cbe639c..07fe1ea8a9 100644 --- a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs @@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Sorting /// /// Interface IBaseItemComparer. /// - public interface IBaseItemComparer : IComparer + public interface IBaseItemComparer : IComparer { /// /// Gets the name. diff --git a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs index 6d03d97ae3..bd47db39a6 100644 --- a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.Sorting diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs index 88467814cd..aa6ec513f3 100644 --- a/MediaBrowser.Controller/Sorting/SortExtensions.cs +++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 6d63286ef5..9e661cbe42 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs b/MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs index a633262de9..326348d583 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs b/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs index ce8141219a..c782f57961 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs index a86b057783..85b3e6fbd7 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs index 7d3c20e8f1..0f7c47e767 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs b/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs deleted file mode 100644 index e7395b136d..0000000000 --- a/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs +++ /dev/null @@ -1,20 +0,0 @@ -#pragma warning disable CS1591 - -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Sync; - -namespace MediaBrowser.Controller.Sync -{ - public interface IHasDynamicAccess - { - /// - /// Gets the synced file information. - /// - /// The identifier. - /// The target. - /// The cancellation token. - /// Task<SyncedFileInfo>. - Task GetSyncedFileInfo(string id, SyncTarget target, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs b/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs deleted file mode 100644 index b2c53365c4..0000000000 --- a/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Controller.Sync -{ - /// - /// A marker interface. - /// - public interface IRemoteSyncProvider - { - } -} diff --git a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs deleted file mode 100644 index c97fd70442..0000000000 --- a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Sync; - -namespace MediaBrowser.Controller.Sync -{ - public interface IServerSyncProvider : ISyncProvider - { - /// - /// Transfers the file. - /// - Task SendFile(SyncJob syncJob, string originalMediaPath, Stream inputStream, bool isMedia, string[] outputPathParts, SyncTarget target, IProgress progress, CancellationToken cancellationToken); - - Task> GetFiles(string[] directoryPathParts, SyncTarget target, CancellationToken cancellationToken); - } - - public interface ISupportsDirectCopy - { - /// - /// Sends the file. - /// - Task SendFile(SyncJob syncJob, string originalMediaPath, string inputPath, bool isMedia, string[] outputPathParts, SyncTarget target, IProgress progress, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Sync/ISyncProvider.cs b/MediaBrowser.Controller/Sync/ISyncProvider.cs deleted file mode 100644 index 950cc73e85..0000000000 --- a/MediaBrowser.Controller/Sync/ISyncProvider.cs +++ /dev/null @@ -1,29 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Model.Sync; - -namespace MediaBrowser.Controller.Sync -{ - public interface ISyncProvider - { - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - - /// - /// Gets the synchronize targets. - /// - /// The user identifier. - /// IEnumerable<SyncTarget>. - List GetSyncTargets(string userId); - - /// - /// Gets all synchronize targets. - /// - /// IEnumerable<SyncTarget>. - List GetAllSyncTargets(); - } -} diff --git a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs deleted file mode 100644 index a626738fb2..0000000000 --- a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs +++ /dev/null @@ -1,41 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.Controller.Sync -{ - public class SyncedFileInfo - { - public SyncedFileInfo() - { - RequiredHttpHeaders = new Dictionary(); - } - - /// - /// Gets or sets the path. - /// - /// The path. - public string Path { get; set; } - - public string[] PathParts { get; set; } - - /// - /// Gets or sets the protocol. - /// - /// The protocol. - public MediaProtocol Protocol { get; set; } - - /// - /// Gets or sets the required HTTP headers. - /// - /// The required HTTP headers. - public Dictionary RequiredHttpHeaders { get; set; } - - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - } -} diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs index 5fb982e85a..7e7e759a51 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupMember.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Session; namespace MediaBrowser.Controller.SyncPlay diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs index e3de22db38..91a13fb28e 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay.PlaybackRequests; diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs index 12ce6c8f82..6b5a7cdf9a 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay.PlaybackRequests; diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs index fba8ba9e2e..b9786ddb08 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs index 9797b247ce..cb1cadf0bc 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs index 507573653f..a0c38b3097 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs b/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs index 201f29952f..9045063eed 100644 --- a/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/IGroupState.cs b/MediaBrowser.Controller/SyncPlay/IGroupState.cs index 95ee09985f..0666a62a85 100644 --- a/MediaBrowser.Controller/SyncPlay/IGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/IGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay.PlaybackRequests; diff --git a/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs index aa263638aa..de26c7d9ef 100644 --- a/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs +++ b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index 1c954828c3..a6999a12c9 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/AbstractPlaybackRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/AbstractPlaybackRequest.cs index 4090f65b9c..ef496c1038 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/AbstractPlaybackRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/AbstractPlaybackRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs index 11cc99fcda..d188114c34 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs index 64ef791ed7..464c81dfd2 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs index 9cd8da5668..be314e807b 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextItemGroupRequest.cs index e0ae0deb76..679076239e 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextItemGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextItemGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs index 2869b35f77..7ee18a3666 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs index 8ef3b20303..beab655c59 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs index 16f9b40874..05ff262c1e 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousItemGroupRequest.cs index 166ee08007..3e34b6ce46 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousItemGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousItemGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs index d4af63b6d4..0f91476de1 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs index 74f01cbeaf..b1f0bd3602 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs index 47c06c2227..6891452934 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs index ecaa689ae3..1961133749 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs index c3451703ed..44df127a6f 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs index 51011672ea..d250eab56e 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs index d7b2504b4b..5034e992eb 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs index fdec29417e..b8ae9f3ff6 100644 --- a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs +++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/MediaBrowser.Controller/TV/ITVSeriesManager.cs b/MediaBrowser.Controller/TV/ITVSeriesManager.cs index 291dea04ef..e066c03fdc 100644 --- a/MediaBrowser.Controller/TV/ITVSeriesManager.cs +++ b/MediaBrowser.Controller/TV/ITVSeriesManager.cs @@ -6,16 +6,26 @@ using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.TV { + /// + /// The TV Series manager. + /// public interface ITVSeriesManager { /// /// Gets the next up. /// + /// The next up query. + /// The dto options. + /// The next up items. QueryResult GetNextUp(NextUpQuery query, DtoOptions options); /// /// Gets the next up. /// + /// The next up request. + /// The list of parent folders. + /// The dto options. + /// The next up items. QueryResult GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options); } } diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index b036a2cc84..9f8e921b4b 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -91,7 +91,7 @@ namespace MediaBrowser.LocalMetadata /// Item inf. /// Instance of the interface. /// The file system metadata. - protected abstract FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService); + protected abstract FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService); /// public bool HasChanged(BaseItem item, IDirectoryService directoryService) diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs index 2d3b2d889c..bc62ca4d53 100644 --- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs @@ -42,6 +42,10 @@ namespace MediaBrowser.LocalMetadata.Images public IEnumerable GetImages(BaseItem item, IDirectoryService directoryService) { var parentPath = Path.GetDirectoryName(item.Path); + if (parentPath == null) + { + return Enumerable.Empty(); + } var parentPathFiles = directoryService.GetFiles(parentPath); diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 7ad8c24e89..31475e22f4 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -466,7 +466,7 @@ namespace MediaBrowser.LocalMetadata.Images return added; } - private bool AddImage(IEnumerable files, List images, string name, ImageType type) + private bool AddImage(List files, List images, string name, ImageType type) { var image = GetImage(files, name); @@ -484,9 +484,20 @@ namespace MediaBrowser.LocalMetadata.Images return false; } - private FileSystemMetadata? GetImage(IEnumerable files, string name) + private static FileSystemMetadata? GetImage(IReadOnlyList files, string name) { - return files.FirstOrDefault(i => !i.IsDirectory && string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase) && i.Length > 0); + for (var i = 0; i < files.Count; i++) + { + var file = files[i]; + if (!file.IsDirectory + && file.Length > 0 + && Path.GetFileNameWithoutExtension(file.FullName.AsSpan()).Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return file; + } + } + + return null; } } } diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 5f620634fb..32e5ac7615 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -468,8 +468,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { var val = reader.ReadElementContentAsString(); - var hasDisplayOrder = item as IHasDisplayOrder; - if (hasDisplayOrder != null) + if (item is IHasDisplayOrder hasDisplayOrder) { if (!string.IsNullOrWhiteSpace(val)) { diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs index cc705a9dfa..ea123bbf22 100644 --- a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.LocalMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "collection.xml")); } diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs index 36f3048ad5..8990356521 100644 --- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.LocalMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(PlaylistXmlSaver.GetSavePath(info.Path)); } diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index dfbce5f49a..98ed3dcf7d 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -296,8 +296,7 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteEndElement(); } - var hasDisplayOrder = item as IHasDisplayOrder; - if (hasDisplayOrder != null && !string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder)) + if (item is IHasDisplayOrder hasDisplayOrder && !string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder)) { writer.WriteElementString("DisplayOrder", hasDisplayOrder.DisplayOrder); } @@ -312,8 +311,7 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(_usCulture)); } - var hasAspectRatio = item as IHasAspectRatio; - if (hasAspectRatio != null) + if (item is IHasAspectRatio hasAspectRatio) { if (!string.IsNullOrEmpty(hasAspectRatio.AspectRatio)) { diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index e8aeabf9d0..a0ec3bd90d 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs index ef9943722d..e86e518be6 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs @@ -9,9 +9,9 @@ namespace MediaBrowser.MediaEncoding.BdInfo { public class BdInfoDirectoryInfo : IDirectoryInfo { - private readonly IFileSystem _fileSystem = null; + private readonly IFileSystem _fileSystem; - private readonly FileSystemMetadata _impl = null; + private readonly FileSystemMetadata _impl; public BdInfoDirectoryInfo(IFileSystem fileSystem, string path) { @@ -29,7 +29,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo public string FullName => _impl.FullName; - public IDirectoryInfo Parent + public IDirectoryInfo? Parent { get { diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs index 9108d96497..6ebaa4fff0 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs @@ -61,33 +61,25 @@ namespace MediaBrowser.MediaEncoding.BdInfo foreach (var stream in playlist.SortedStreams) { - var videoStream = stream as TSVideoStream; - - if (videoStream != null) + if (stream is TSVideoStream videoStream) { AddVideoStream(mediaStreams, videoStream); continue; } - var audioStream = stream as TSAudioStream; - - if (audioStream != null) + if (stream is TSAudioStream audioStream) { AddAudioStream(mediaStreams, audioStream); continue; } - var textStream = stream as TSTextStream; - - if (textStream != null) + if (stream is TSTextStream textStream) { AddSubtitleStream(mediaStreams, textStream); continue; } - var graphicsStream = stream as TSGraphicsStream; - - if (graphicsStream != null) + if (stream is TSGraphicsStream graphicsStream) { AddSubtitleStream(mediaStreams, graphicsStream); } diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs index 0a8af8e9c8..41143c2593 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs @@ -7,7 +7,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo { public class BdInfoFileInfo : BDInfo.IO.IFileInfo { - private FileSystemMetadata _impl = null; + private FileSystemMetadata _impl; public BdInfoFileInfo(FileSystemMetadata impl) { diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 9e24176033..f782e65bd1 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -121,11 +121,11 @@ namespace MediaBrowser.MediaEncoding.Encoder // When changing this, also change the minimum library versions in _ffmpegMinimumLibraryVersions public static Version MinVersion { get; } = new Version(4, 0); - public static Version MaxVersion { get; } = null; + public static Version? MaxVersion { get; } = null; public bool ValidateVersion() { - string output = null; + string output; try { output = GetProcessOutput(_encoderPath, "-version"); @@ -133,6 +133,7 @@ namespace MediaBrowser.MediaEncoding.Encoder catch (Exception ex) { _logger.LogError(ex, "Error validating encoder"); + return false; } if (string.IsNullOrWhiteSpace(output)) @@ -207,7 +208,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// The output from "ffmpeg -version". /// The FFmpeg version. - internal Version GetFFmpegVersion(string output) + internal Version? GetFFmpegVersion(string output) { // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)"); @@ -275,7 +276,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private IEnumerable GetHwaccelTypes() { - string output = null; + string? output = null; try { output = GetProcessOutput(_encoderPath, "-hwaccels"); @@ -303,7 +304,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } - string output = null; + string output; try { output = GetProcessOutput(_encoderPath, "-h filter=" + filter); @@ -311,6 +312,7 @@ namespace MediaBrowser.MediaEncoding.Encoder catch (Exception ex) { _logger.LogError(ex, "Error detecting the given filter"); + return false; } if (output.Contains("Filter " + filter, StringComparison.Ordinal)) @@ -331,7 +333,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private IEnumerable GetCodecs(Codec codec) { string codecstr = codec == Codec.Encoder ? "encoders" : "decoders"; - string output = null; + string output; try { output = GetProcessOutput(_encoderPath, "-" + codecstr); @@ -339,6 +341,7 @@ namespace MediaBrowser.MediaEncoding.Encoder catch (Exception ex) { _logger.LogError(ex, "Error detecting available {Codec}", codecstr); + return Enumerable.Empty(); } if (string.IsNullOrWhiteSpace(output)) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 62c0c0bb1f..cdb778bf2d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 39fb0b47c1..7733e715f2 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -10,6 +10,7 @@ false true true + enable AllEnabledByDefault ../jellyfin.ruleset diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index da37687e8f..1fa90bb213 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Globalization; diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs index 0e319c1a8b..d4d153b083 100644 --- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs +++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs index de062d06b0..a1cef7a9f2 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs index 8af122ef9b..d50da37b89 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs index 7b7744163c..c9c8c34c2e 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index a87104cd67..2ec9dc3467 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -29,7 +30,7 @@ namespace MediaBrowser.MediaEncoding.Probing private readonly ILogger _logger; private readonly ILocalizationManager _localization; - private List _splitWhiteList = null; + private string[] _splitWhiteList; public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization) { @@ -37,6 +38,8 @@ namespace MediaBrowser.MediaEncoding.Probing _localization = localization; } + private IReadOnlyList SplitWhitelist => _splitWhiteList ??= new string[] { "AC/DC" }; + public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol) { var info = new MediaInfo @@ -57,7 +60,7 @@ namespace MediaBrowser.MediaEncoding.Probing .Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec)) .ToList(); - info.MediaAttachments = internalStreams.Select(s => GetMediaAttachment(s)) + info.MediaAttachments = internalStreams.Select(GetMediaAttachment) .Where(i => i != null) .ToList(); @@ -1187,43 +1190,28 @@ namespace MediaBrowser.MediaEncoding.Probing FetchStudios(audio, tags, "label"); // These support mulitple values, but for now we only store the first. - var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); - } + var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); audio.SetProviderId(MetadataProvider.MusicBrainzArtist, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); audio.SetProviderId(MetadataProvider.MusicBrainzTrack, mb); } @@ -1269,7 +1257,7 @@ namespace MediaBrowser.MediaEncoding.Probing var artistsFound = new List(); - foreach (var whitelistArtist in GetSplitWhitelist()) + foreach (var whitelistArtist in SplitWhitelist) { var originalVal = val; val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase); @@ -1288,19 +1276,6 @@ namespace MediaBrowser.MediaEncoding.Probing return artistsFound; } - private IEnumerable GetSplitWhitelist() - { - if (_splitWhiteList == null) - { - _splitWhiteList = new List - { - "AC/DC" - }; - } - - return _splitWhiteList; - } - /// /// Gets the studios from the tags collection. /// diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs index 8219aa7b47..08ee5c72e5 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.Extensions.Logging; using Nikse.SubtitleEdit.Core.SubtitleFormats; diff --git a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs index dca5c1e8ac..cec1aaf080 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 namespace MediaBrowser.MediaEncoding.Subtitles diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs index 19fb951dc5..78d54ca51f 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.Extensions.Logging; using Nikse.SubtitleEdit.Core.SubtitleFormats; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs index 36dc2e01fc..17c2ae40e0 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.Extensions.Logging; using Nikse.SubtitleEdit.Core.SubtitleFormats; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs index 82ec6ca217..639a34d993 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Globalization; using System.IO; using System.Linq; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 39bec8da1c..608ebf4436 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -71,8 +72,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles try { - var reader = GetReader(inputFormat, true); - + var reader = GetReader(inputFormat); var trackInfo = reader.Parse(stream, cancellationToken); FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps); @@ -139,10 +139,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles .ConfigureAwait(false); var inputFormat = subtitle.format; - var writer = TryGetWriter(outputFormat); // Return the original if we don't have any way of converting it - if (writer == null) + if (!TryGetWriter(outputFormat, out var writer)) { return subtitle.stream; } @@ -239,7 +238,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec) .TrimStart('.'); - if (GetReader(currentFormat, false) == null) + if (TryGetReader(currentFormat, out _)) { // Convert var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt"); @@ -257,37 +256,41 @@ namespace MediaBrowser.MediaEncoding.Subtitles return new SubtitleInfo(subtitleStream.Path, mediaSource.Protocol, currentFormat, true); } - private ISubtitleParser GetReader(string format, bool throwIfMissing) + private bool TryGetReader(string format, [NotNullWhen(true)] out ISubtitleParser? value) { - if (string.IsNullOrEmpty(format)) - { - throw new ArgumentNullException(nameof(format)); - } - if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) { - return new SrtParser(_logger); + value = new SrtParser(_logger); + return true; } if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase)) { - return new SsaParser(_logger); + value = new SsaParser(_logger); + return true; } if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)) { - return new AssParser(_logger); + value = new AssParser(_logger); + return true; } - if (throwIfMissing) - { - throw new ArgumentException("Unsupported format: " + format); - } - - return null; + value = null; + return false; } - private ISubtitleWriter TryGetWriter(string format) + private ISubtitleParser GetReader(string format) + { + if (TryGetReader(format, out var reader)) + { + return reader; + } + + throw new ArgumentException("Unsupported format: " + format); + } + + private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value) { if (string.IsNullOrEmpty(format)) { @@ -296,32 +299,35 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase)) { - return new JsonWriter(); + value = new JsonWriter(); + return true; } if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) { - return new SrtWriter(); + value = new SrtWriter(); + return true; } if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase)) { - return new VttWriter(); + value = new VttWriter(); + return true; } if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase)) { - return new TtmlWriter(); + value = new TtmlWriter(); + return true; } - return null; + value = null; + return false; } private ISubtitleWriter GetWriter(string format) { - var writer = TryGetWriter(format); - - if (writer != null) + if (TryGetWriter(format, out var writer)) { return writer; } @@ -391,7 +397,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException(nameof(outputPath)); } - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath))); var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, mediaSource.Protocol, cancellationToken).ConfigureAwait(false); @@ -549,7 +555,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException(nameof(outputPath)); } - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath))); var processArgs = string.Format( CultureInfo.InvariantCulture, @@ -715,7 +721,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false)) { - var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName; + var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName ?? string.Empty; // UTF16 is automatically converted to UTF8 by FFmpeg, do not specify a character encoding if ((path.EndsWith(".ass", StringComparison.Ordinal) || path.EndsWith(".ssa", StringComparison.Ordinal) || path.EndsWith(".srt", StringComparison.Ordinal)) @@ -725,7 +731,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles charset = string.Empty; } - _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path); + _logger.LogDebug("charset {0} detected for {Path}", charset, path); return charset; } diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index d83c8f2f3c..7409660881 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -9,25 +8,15 @@ namespace MediaBrowser.Model.Dlna { public class ContainerProfile { - public ContainerProfile() - { - Conditions = Array.Empty(); - } - [XmlAttribute("type")] public DlnaProfileType Type { get; set; } - public ProfileCondition[] Conditions { get; set; } + public ProfileCondition[]? Conditions { get; set; } = Array.Empty(); [XmlAttribute("container")] - public string Container { get; set; } + public string Container { get; set; } = string.Empty; - public string[] GetContainers() - { - return SplitValue(Container); - } - - public static string[] SplitValue(string value) + public static string[] SplitValue(string? value) { if (string.IsNullOrEmpty(value)) { @@ -37,14 +26,14 @@ namespace MediaBrowser.Model.Dlna return value.Split(',', StringSplitOptions.RemoveEmptyEntries); } - public bool ContainsContainer(string container) + public bool ContainsContainer(string? container) { - var containers = GetContainers(); + var containers = SplitValue(Container); return ContainsContainer(containers, container); } - public static bool ContainsContainer(string profileContainers, string inputContainer) + public static bool ContainsContainer(string? profileContainers, string? inputContainer) { var isNegativeList = false; if (profileContainers != null && profileContainers.StartsWith('-')) @@ -56,46 +45,30 @@ namespace MediaBrowser.Model.Dlna return ContainsContainer(SplitValue(profileContainers), isNegativeList, inputContainer); } - public static bool ContainsContainer(string[] profileContainers, string inputContainer) + public static bool ContainsContainer(string[]? profileContainers, string? inputContainer) { return ContainsContainer(profileContainers, false, inputContainer); } - public static bool ContainsContainer(string[] profileContainers, bool isNegativeList, string inputContainer) + public static bool ContainsContainer(string[]? profileContainers, bool isNegativeList, string? inputContainer) { - if (profileContainers.Length == 0) + if (profileContainers == null || profileContainers.Length == 0) { + // Empty profiles always support all containers/codecs return true; } - if (isNegativeList) + var allInputContainers = SplitValue(inputContainer); + + foreach (var container in allInputContainers) { - var allInputContainers = SplitValue(inputContainer); - - foreach (var container in allInputContainers) + if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) { - if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) - { - return false; - } + return !isNegativeList; } - - return true; } - else - { - var allInputContainers = SplitValue(inputContainer); - foreach (var container in allInputContainers) - { - if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } + return isNegativeList; } } } diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index ff51866587..feb3d880ec 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,6 +1,6 @@ -#nullable disable #pragma warning disable CA1819 // Properties should not return arrays using System; +using System.ComponentModel; using System.Linq; using System.Xml.Serialization; using MediaBrowser.Model.MediaInfo; @@ -8,226 +8,219 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna { /// - /// Defines the . + /// A represents a set of metadata which determines which content a certain device is able to play. + ///
+ /// Specifically, it defines the supported containers and + /// codecs (video and/or audio, including codec profiles and levels) + /// the device is able to direct play (without transcoding or remuxing), + /// as well as which containers/codecs to transcode to in case it isn't. ///
[XmlRoot("Profile")] public class DeviceProfile { /// - /// Initializes a new instance of the class. + /// Gets or sets the name of this device profile. /// - public DeviceProfile() - { - DirectPlayProfiles = Array.Empty(); - TranscodingProfiles = Array.Empty(); - ResponseProfiles = Array.Empty(); - CodecProfiles = Array.Empty(); - ContainerProfiles = Array.Empty(); - SubtitleProfiles = Array.Empty(); - - XmlRootAttributes = Array.Empty(); - - SupportedMediaTypes = "Audio,Photo,Video"; - MaxStreamingBitrate = 8000000; - MaxStaticBitrate = 8000000; - MusicStreamingTranscodingBitrate = 128000; - } - - /// - /// Gets or sets the Name. - /// - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the Id. /// [XmlIgnore] - public string Id { get; set; } + public string? Id { get; set; } /// /// Gets or sets the Identification. /// - public DeviceIdentification Identification { get; set; } + public DeviceIdentification? Identification { get; set; } /// - /// Gets or sets the FriendlyName. + /// Gets or sets the friendly name of the device profile, which can be shown to users. /// - public string FriendlyName { get; set; } + public string? FriendlyName { get; set; } /// - /// Gets or sets the Manufacturer. + /// Gets or sets the manufacturer of the device which this profile represents. /// - public string Manufacturer { get; set; } + public string? Manufacturer { get; set; } /// - /// Gets or sets the ManufacturerUrl. + /// Gets or sets an url for the manufacturer of the device which this profile represents. /// - public string ManufacturerUrl { get; set; } + public string? ManufacturerUrl { get; set; } /// - /// Gets or sets the ModelName. + /// Gets or sets the model name of the device which this profile represents. /// - public string ModelName { get; set; } + public string? ModelName { get; set; } /// - /// Gets or sets the ModelDescription. + /// Gets or sets the model description of the device which this profile represents. /// - public string ModelDescription { get; set; } + public string? ModelDescription { get; set; } /// - /// Gets or sets the ModelNumber. + /// Gets or sets the model number of the device which this profile represents. /// - public string ModelNumber { get; set; } + public string? ModelNumber { get; set; } /// /// Gets or sets the ModelUrl. /// - public string ModelUrl { get; set; } + public string? ModelUrl { get; set; } /// - /// Gets or sets the SerialNumber. + /// Gets or sets the serial number of the device which this profile represents. /// - public string SerialNumber { get; set; } + public string? SerialNumber { get; set; } /// /// Gets or sets a value indicating whether EnableAlbumArtInDidl. /// + [DefaultValue(false)] public bool EnableAlbumArtInDidl { get; set; } /// /// Gets or sets a value indicating whether EnableSingleAlbumArtLimit. /// + [DefaultValue(false)] public bool EnableSingleAlbumArtLimit { get; set; } /// /// Gets or sets a value indicating whether EnableSingleSubtitleLimit. /// + [DefaultValue(false)] public bool EnableSingleSubtitleLimit { get; set; } /// /// Gets or sets the SupportedMediaTypes. /// - public string SupportedMediaTypes { get; set; } + public string SupportedMediaTypes { get; set; } = "Audio,Photo,Video"; /// /// Gets or sets the UserId. /// - public string UserId { get; set; } + public string? UserId { get; set; } /// /// Gets or sets the AlbumArtPn. /// - public string AlbumArtPn { get; set; } + public string? AlbumArtPn { get; set; } /// /// Gets or sets the MaxAlbumArtWidth. /// - public int MaxAlbumArtWidth { get; set; } + public int? MaxAlbumArtWidth { get; set; } /// /// Gets or sets the MaxAlbumArtHeight. /// - public int MaxAlbumArtHeight { get; set; } + public int? MaxAlbumArtHeight { get; set; } /// - /// Gets or sets the MaxIconWidth. + /// Gets or sets the maximum allowed width of embedded icons. /// public int? MaxIconWidth { get; set; } /// - /// Gets or sets the MaxIconHeight. + /// Gets or sets the maximum allowed height of embedded icons. /// public int? MaxIconHeight { get; set; } /// - /// Gets or sets the MaxStreamingBitrate. + /// Gets or sets the maximum allowed bitrate for all streamed content. /// - public int? MaxStreamingBitrate { get; set; } + public int? MaxStreamingBitrate { get; set; } = 8000000; /// - /// Gets or sets the MaxStaticBitrate. + /// Gets or sets the maximum allowed bitrate for statically streamed content (= direct played files). /// - public int? MaxStaticBitrate { get; set; } + public int? MaxStaticBitrate { get; set; } = 8000000; /// - /// Gets or sets the MusicStreamingTranscodingBitrate. + /// Gets or sets the maximum allowed bitrate for transcoded music streams. /// - public int? MusicStreamingTranscodingBitrate { get; set; } + public int? MusicStreamingTranscodingBitrate { get; set; } = 128000; /// - /// Gets or sets the MaxStaticMusicBitrate. + /// Gets or sets the maximum allowed bitrate for statically streamed (= direct played) music files. /// - public int? MaxStaticMusicBitrate { get; set; } + public int? MaxStaticMusicBitrate { get; set; } = 8000000; /// /// Gets or sets the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace. /// - public string SonyAggregationFlags { get; set; } + public string? SonyAggregationFlags { get; set; } /// /// Gets or sets the ProtocolInfo. /// - public string ProtocolInfo { get; set; } + public string? ProtocolInfo { get; set; } /// /// Gets or sets the TimelineOffsetSeconds. /// + [DefaultValue(0)] public int TimelineOffsetSeconds { get; set; } /// /// Gets or sets a value indicating whether RequiresPlainVideoItems. /// + [DefaultValue(false)] public bool RequiresPlainVideoItems { get; set; } /// /// Gets or sets a value indicating whether RequiresPlainFolders. /// + [DefaultValue(false)] public bool RequiresPlainFolders { get; set; } /// /// Gets or sets a value indicating whether EnableMSMediaReceiverRegistrar. /// + [DefaultValue(false)] public bool EnableMSMediaReceiverRegistrar { get; set; } /// /// Gets or sets a value indicating whether IgnoreTranscodeByteRangeRequests. /// + [DefaultValue(false)] public bool IgnoreTranscodeByteRangeRequests { get; set; } /// /// Gets or sets the XmlRootAttributes. /// - public XmlAttribute[] XmlRootAttributes { get; set; } + public XmlAttribute[] XmlRootAttributes { get; set; } = Array.Empty(); /// /// Gets or sets the direct play profiles. /// - public DirectPlayProfile[] DirectPlayProfiles { get; set; } + public DirectPlayProfile[] DirectPlayProfiles { get; set; } = Array.Empty(); /// /// Gets or sets the transcoding profiles. /// - public TranscodingProfile[] TranscodingProfiles { get; set; } + public TranscodingProfile[] TranscodingProfiles { get; set; } = Array.Empty(); /// - /// Gets or sets the ContainerProfiles. + /// Gets or sets the container profiles. /// - public ContainerProfile[] ContainerProfiles { get; set; } + public ContainerProfile[] ContainerProfiles { get; set; } = Array.Empty(); /// - /// Gets or sets the CodecProfiles. + /// Gets or sets the codec profiles. /// - public CodecProfile[] CodecProfiles { get; set; } + public CodecProfile[] CodecProfiles { get; set; } = Array.Empty(); /// /// Gets or sets the ResponseProfiles. /// - public ResponseProfile[] ResponseProfiles { get; set; } + public ResponseProfile[] ResponseProfiles { get; set; } = Array.Empty(); /// - /// Gets or sets the SubtitleProfiles. + /// Gets or sets the subtitle profiles. /// - public SubtitleProfile[] SubtitleProfiles { get; set; } + public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty(); /// /// The GetSupportedMediaTypes. @@ -244,13 +237,13 @@ namespace MediaBrowser.Model.Dlna /// The container. /// The audio Codec. /// A . - public TranscodingProfile GetAudioTranscodingProfile(string container, string audioCodec) + public TranscodingProfile? GetAudioTranscodingProfile(string? container, string? audioCodec) { container = (container ?? string.Empty).TrimStart('.'); foreach (var i in TranscodingProfiles) { - if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Audio) + if (i.Type != DlnaProfileType.Audio) { continue; } @@ -278,13 +271,13 @@ namespace MediaBrowser.Model.Dlna /// The audio Codec. /// The video Codec. /// The . - public TranscodingProfile GetVideoTranscodingProfile(string container, string audioCodec, string videoCodec) + public TranscodingProfile? GetVideoTranscodingProfile(string? container, string? audioCodec, string? videoCodec) { container = (container ?? string.Empty).TrimStart('.'); foreach (var i in TranscodingProfiles) { - if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Video) + if (i.Type != DlnaProfileType.Video) { continue; } @@ -299,7 +292,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!string.Equals(videoCodec, i.VideoCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(videoCodec, i.VideoCodec, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -320,7 +313,7 @@ namespace MediaBrowser.Model.Dlna /// The audio sample rate. /// The audio bit depth. /// The . - public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth) + public ResponseProfile? GetAudioMediaProfile(string container, string? audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth) { foreach (var i in ResponseProfiles) { @@ -384,7 +377,7 @@ namespace MediaBrowser.Model.Dlna /// The width. /// The height. /// The . - public ResponseProfile GetImageMediaProfile(string container, int? width, int? height) + public ResponseProfile? GetImageMediaProfile(string container, int? width, int? height) { foreach (var i in ResponseProfiles) { @@ -442,10 +435,10 @@ namespace MediaBrowser.Model.Dlna /// The video Codec tag. /// True if Avc. /// The . - public ResponseProfile GetVideoMediaProfile( + public ResponseProfile? GetVideoMediaProfile( string container, - string audioCodec, - string videoCodec, + string? audioCodec, + string? videoCodec, int? width, int? height, int? bitDepth, diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index 88cb839918..fa3ad098f0 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -1,6 +1,6 @@ -#nullable disable #pragma warning disable CS1591 +using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna @@ -8,13 +8,13 @@ namespace MediaBrowser.Model.Dlna public class DirectPlayProfile { [XmlAttribute("container")] - public string Container { get; set; } + public string? Container { get; set; } [XmlAttribute("audioCodec")] - public string AudioCodec { get; set; } + public string? AudioCodec { get; set; } [XmlAttribute("videoCodec")] - public string VideoCodec { get; set; } + public string? VideoCodec { get; set; } [XmlAttribute("type")] public DlnaProfileType Type { get; set; } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 398d47d5f5..f4c69fe8f5 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -297,7 +297,7 @@ namespace MediaBrowser.Model.Dlna int? inputAudioSampleRate = audioStream?.SampleRate; int? inputAudioBitDepth = audioStream?.BitDepth; - if (directPlayMethods.Count() > 0) + if (directPlayMethods.Any()) { string audioCodec = audioStream?.Codec; diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index f05e31047c..214578a85e 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -1,6 +1,7 @@ -#nullable disable #pragma warning disable CS1591 +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna @@ -8,47 +9,56 @@ namespace MediaBrowser.Model.Dlna public class TranscodingProfile { [XmlAttribute("container")] - public string Container { get; set; } + public string Container { get; set; } = string.Empty; [XmlAttribute("type")] public DlnaProfileType Type { get; set; } [XmlAttribute("videoCodec")] - public string VideoCodec { get; set; } + public string VideoCodec { get; set; } = string.Empty; [XmlAttribute("audioCodec")] - public string AudioCodec { get; set; } + public string AudioCodec { get; set; } = string.Empty; [XmlAttribute("protocol")] - public string Protocol { get; set; } + public string Protocol { get; set; } = string.Empty; + [DefaultValue(false)] [XmlAttribute("estimateContentLength")] public bool EstimateContentLength { get; set; } + [DefaultValue(false)] [XmlAttribute("enableMpegtsM2TsMode")] public bool EnableMpegtsM2TsMode { get; set; } + [DefaultValue(TranscodeSeekInfo.Auto)] [XmlAttribute("transcodeSeekInfo")] public TranscodeSeekInfo TranscodeSeekInfo { get; set; } + [DefaultValue(false)] [XmlAttribute("copyTimestamps")] public bool CopyTimestamps { get; set; } + [DefaultValue(EncodingContext.Streaming)] [XmlAttribute("context")] public EncodingContext Context { get; set; } + [DefaultValue(false)] [XmlAttribute("enableSubtitlesInManifest")] public bool EnableSubtitlesInManifest { get; set; } [XmlAttribute("maxAudioChannels")] - public string MaxAudioChannels { get; set; } + public string? MaxAudioChannels { get; set; } + [DefaultValue(0)] [XmlAttribute("minSegments")] public int MinSegments { get; set; } + [DefaultValue(0)] [XmlAttribute("segmentLength")] public int SegmentLength { get; set; } + [DefaultValue(false)] [XmlAttribute("breakOnNonKeyFrames")] public bool BreakOnNonKeyFrames { get; set; } diff --git a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs index d71013f019..987a3a908f 100644 --- a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs +++ b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs @@ -16,5 +16,7 @@ namespace MediaBrowser.Model.Dlna public IPAddress LocalIpAddress { get; set; } public int LocalPort { get; set; } + + public IPAddress RemoteIpAddress { get; set; } } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index ade9d7e8dd..e644c9ba72 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -163,7 +163,7 @@ namespace MediaBrowser.Model.Entities foreach (var tag in attributes) { // Keep Tags that are not already in Title. - if (Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) + if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) { result.Append(" - ").Append(tag); } @@ -202,7 +202,7 @@ namespace MediaBrowser.Model.Entities foreach (var tag in attributes) { // Keep Tags that are not already in Title. - if (Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) + if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) { result.Append(" - ").Append(tag); } @@ -522,9 +522,9 @@ namespace MediaBrowser.Model.Entities // sub = external .sub file - return codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) == -1 && - codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) == -1 && - codec.IndexOf("dvbsub", StringComparison.OrdinalIgnoreCase) == -1 && + return !codec.Contains("pgs", StringComparison.OrdinalIgnoreCase) && + !codec.Contains("dvd", StringComparison.OrdinalIgnoreCase) && + !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase) && !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase); } diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 09d14dc6a4..ce4b0ec92e 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -123,10 +123,7 @@ namespace MediaBrowser.Model.Entities else { // Ensure it exists - if (instance.ProviderIds == null) - { - instance.ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + instance.ProviderIds ??= new Dictionary(StringComparer.OrdinalIgnoreCase); instance.ProviderIds[name] = value; } diff --git a/MediaBrowser.Model/IO/FileSystemMetadata.cs b/MediaBrowser.Model/IO/FileSystemMetadata.cs index 118c78e801..fb74886bfa 100644 --- a/MediaBrowser.Model/IO/FileSystemMetadata.cs +++ b/MediaBrowser.Model/IO/FileSystemMetadata.cs @@ -37,12 +37,6 @@ namespace MediaBrowser.Model.IO /// The length. public long Length { get; set; } - /// - /// Gets or sets the name of the directory. - /// - /// The name of the directory. - public string DirectoryName { get; set; } - /// /// Gets or sets the last write time UTC. /// diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index e5c26430a8..be4f1e16b5 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -25,7 +24,7 @@ namespace MediaBrowser.Model.IO /// /// The filename. /// System.String. - string ResolveShortcut(string filename); + string? ResolveShortcut(string filename); /// /// Creates the shortcut. @@ -160,7 +159,7 @@ namespace MediaBrowser.Model.IO /// All found files. IEnumerable GetFiles(string path, bool recursive = false); - IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable GetFiles(string path, IReadOnlyList? extensions, bool enableCaseSensitiveExtensions, bool recursive); /// /// Gets the file system entries. @@ -186,7 +185,7 @@ namespace MediaBrowser.Model.IO /// IEnumerable<System.String>. IEnumerable GetFilePaths(string path, bool recursive = false); - IEnumerable GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive); /// /// Gets the file system entry paths. diff --git a/MediaBrowser.Model/IO/IShortcutHandler.cs b/MediaBrowser.Model/IO/IShortcutHandler.cs index 14d5c4b62f..2c364a962f 100644 --- a/MediaBrowser.Model/IO/IShortcutHandler.cs +++ b/MediaBrowser.Model/IO/IShortcutHandler.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.IO /// /// The shortcut path. /// System.String. - string Resolve(string shortcutPath); + string? Resolve(string shortcutPath); /// /// Creates the specified shortcut path. diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs index 0e09db16e8..f900da5567 100644 --- a/MediaBrowser.Model/IO/IStreamHelper.cs +++ b/MediaBrowser.Model/IO/IStreamHelper.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Model.IO { public interface IStreamHelper { - Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken); + Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken); Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Querying/EpisodeQuery.cs b/MediaBrowser.Model/Querying/EpisodeQuery.cs deleted file mode 100644 index 56a7f33201..0000000000 --- a/MediaBrowser.Model/Querying/EpisodeQuery.cs +++ /dev/null @@ -1,75 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Querying -{ - public class EpisodeQuery - { - public EpisodeQuery() - { - Fields = Array.Empty(); - } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public string UserId { get; set; } - - /// - /// Gets or sets the season identifier. - /// - /// The season identifier. - public string SeasonId { get; set; } - - /// - /// Gets or sets the series identifier. - /// - /// The series identifier. - public string SeriesId { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is missing. - /// - /// null if [is missing] contains no value, true if [is missing]; otherwise, false. - public bool? IsMissing { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is virtual unaired. - /// - /// null if [is virtual unaired] contains no value, true if [is virtual unaired]; otherwise, false. - public bool? IsVirtualUnaired { get; set; } - - /// - /// Gets or sets the season number. - /// - /// The season number. - public int? SeasonNumber { get; set; } - - /// - /// Gets or sets the fields. - /// - /// The fields. - public ItemFields[] Fields { get; set; } - - /// - /// Gets or sets the start index. - /// - /// The start index. - public int? StartIndex { get; set; } - - /// - /// Gets or sets the limit. - /// - /// The limit. - public int? Limit { get; set; } - - /// - /// Gets or sets the start item identifier. - /// - /// The start item identifier. - public string StartItemId { get; set; } - } -} diff --git a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs deleted file mode 100644 index b800f5de5f..0000000000 --- a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs +++ /dev/null @@ -1,47 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Querying -{ - public class MovieRecommendationQuery - { - public MovieRecommendationQuery() - { - ItemLimit = 10; - CategoryLimit = 6; - Fields = Array.Empty(); - } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public string UserId { get; set; } - - /// - /// Gets or sets the parent identifier. - /// - /// The parent identifier. - public string ParentId { get; set; } - - /// - /// Gets or sets the item limit. - /// - /// The item limit. - public int ItemLimit { get; set; } - - /// - /// Gets or sets the category limit. - /// - /// The category limit. - public int CategoryLimit { get; set; } - - /// - /// Gets or sets the fields. - /// - /// The fields. - public ItemFields[] Fields { get; set; } - } -} diff --git a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs deleted file mode 100644 index 2cf0f0d5f8..0000000000 --- a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs +++ /dev/null @@ -1,64 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Model.Querying -{ - public class UpcomingEpisodesQuery - { - public UpcomingEpisodesQuery() - { - EnableImageTypes = Array.Empty(); - } - - /// - /// Gets or sets the user id. - /// - /// The user id. - public string UserId { get; set; } - - /// - /// Gets or sets the parent identifier. - /// - /// The parent identifier. - public string ParentId { get; set; } - - /// - /// Gets or sets the start index. Use for paging. - /// - /// The start index. - public int? StartIndex { get; set; } - - /// - /// Gets or sets the maximum number of items to return. - /// - /// The limit. - public int? Limit { get; set; } - - /// - /// Gets or sets the fields to return within the items, in addition to basic information. - /// - /// The fields. - public ItemFields[] Fields { get; set; } - - /// - /// Gets or sets a value indicating whether [enable images]. - /// - /// null if [enable images] contains no value, true if [enable images]; otherwise, false. - public bool? EnableImages { get; set; } - - /// - /// Gets or sets the image type limit. - /// - /// The image type limit. - public int? ImageTypeLimit { get; set; } - - /// - /// Gets or sets the enable image types. - /// - /// The enable image types. - public ImageType[] EnableImageTypes { get; set; } - } -} diff --git a/MediaBrowser.Model/Sync/SyncCategory.cs b/MediaBrowser.Model/Sync/SyncCategory.cs deleted file mode 100644 index 1248c2f739..0000000000 --- a/MediaBrowser.Model/Sync/SyncCategory.cs +++ /dev/null @@ -1,22 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Sync -{ - public enum SyncCategory - { - /// - /// The latest. - /// - Latest = 0, - - /// - /// The next up. - /// - NextUp = 1, - - /// - /// The resume. - /// - Resume = 2 - } -} diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs deleted file mode 100644 index 3e396e5d13..0000000000 --- a/MediaBrowser.Model/Sync/SyncJob.cs +++ /dev/null @@ -1,135 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Sync -{ - public class SyncJob - { - public SyncJob() - { - RequestedItemIds = Array.Empty(); - } - - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - - /// - /// Gets or sets the device identifier. - /// - /// The device identifier. - public string TargetId { get; set; } - - /// - /// Gets or sets the name of the target. - /// - /// The name of the target. - public string TargetName { get; set; } - - /// - /// Gets or sets the quality. - /// - /// The quality. - public string Quality { get; set; } - - /// - /// Gets or sets the bitrate. - /// - /// The bitrate. - public int? Bitrate { get; set; } - - /// - /// Gets or sets the profile. - /// - /// The profile. - public string Profile { get; set; } - - /// - /// Gets or sets the category. - /// - /// The category. - public SyncCategory? Category { get; set; } - - /// - /// Gets or sets the parent identifier. - /// - /// The parent identifier. - public string ParentId { get; set; } - - /// - /// Gets or sets the current progress. - /// - /// The current progress. - public double? Progress { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the status. - /// - /// The status. - public SyncJobStatus Status { get; set; } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public string UserId { get; set; } - - /// - /// Gets or sets a value indicating whether [unwatched only]. - /// - /// true if [unwatched only]; otherwise, false. - public bool UnwatchedOnly { get; set; } - - /// - /// Gets or sets a value indicating whether [synchronize new content]. - /// - /// true if [synchronize new content]; otherwise, false. - public bool SyncNewContent { get; set; } - - /// - /// Gets or sets the item limit. - /// - /// The item limit. - public int? ItemLimit { get; set; } - - /// - /// Gets or sets the requested item ids. - /// - /// The requested item ids. - public Guid[] RequestedItemIds { get; set; } - - /// - /// Gets or sets the date created. - /// - /// The date created. - public DateTime DateCreated { get; set; } - - /// - /// Gets or sets the date last modified. - /// - /// The date last modified. - public DateTime DateLastModified { get; set; } - - /// - /// Gets or sets the item count. - /// - /// The item count. - public int ItemCount { get; set; } - - public string ParentName { get; set; } - - public string PrimaryImageItemId { get; set; } - - public string PrimaryImageTag { get; set; } - } -} diff --git a/MediaBrowser.Model/Sync/SyncJobStatus.cs b/MediaBrowser.Model/Sync/SyncJobStatus.cs deleted file mode 100644 index 226a47d4c0..0000000000 --- a/MediaBrowser.Model/Sync/SyncJobStatus.cs +++ /dev/null @@ -1,15 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Sync -{ - public enum SyncJobStatus - { - Queued = 0, - Converting = 1, - ReadyToTransfer = 2, - Transferring = 3, - Completed = 4, - CompletedWithError = 5, - Failed = 6 - } -} diff --git a/MediaBrowser.Model/Sync/SyncTarget.cs b/MediaBrowser.Model/Sync/SyncTarget.cs deleted file mode 100644 index 9e6bbbc009..0000000000 --- a/MediaBrowser.Model/Sync/SyncTarget.cs +++ /dev/null @@ -1,20 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Sync -{ - public class SyncTarget - { - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - } -} diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs index cbd60cca18..db9fba6964 100644 --- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs +++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Tasks /// /// Fires when the trigger condition is satisfied and the task should run. /// - event EventHandler Triggered; + event EventHandler? Triggered; /// /// Gets or sets the options of this task. diff --git a/MediaBrowser.Model/Users/UserAction.cs b/MediaBrowser.Model/Users/UserAction.cs deleted file mode 100644 index 7646db4a82..0000000000 --- a/MediaBrowser.Model/Users/UserAction.cs +++ /dev/null @@ -1,24 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Users -{ - public class UserAction - { - public string Id { get; set; } - - public string ServerId { get; set; } - - public Guid UserId { get; set; } - - public Guid ItemId { get; set; } - - public UserActionType Type { get; set; } - - public DateTime Date { get; set; } - - public long? PositionTicks { get; set; } - } -} diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index f12586665f..401c7e99f2 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Manager } // Save to database - await SaveItemAsync(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false); + await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false); } await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false); @@ -216,71 +216,18 @@ namespace MediaBrowser.Providers.Manager lookupInfo.Year = result.ProductionYear; } - protected async Task SaveItemAsync(MetadataResult result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken) + protected async Task SaveItemAsync(MetadataResult result, ItemUpdateType reason, CancellationToken cancellationToken) { if (result.Item.SupportsPeople && result.People != null) { var baseItem = result.Item; - LibraryManager.UpdatePeople(baseItem, result.People); - await SavePeopleMetadataAsync(result.People, cancellationToken).ConfigureAwait(false); + await LibraryManager.UpdatePeopleAsync(baseItem, result.People, cancellationToken).ConfigureAwait(false); } await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false); } - 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 = LibraryManager.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 LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); - } - } - - LibraryManager.CreateItems(personsToSave, null, CancellationToken.None); - } - protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { item.AfterMetadataRefresh(); @@ -334,8 +281,7 @@ namespace MediaBrowser.Providers.Manager return true; } - var folder = item as Folder; - if (folder != null) + if (item is Folder folder) { return folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks; } @@ -389,8 +335,7 @@ namespace MediaBrowser.Providers.Manager private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IList children) { - var folder = item as Folder; - if (folder != null && folder.SupportsCumulativeRunTimeTicks) + if (item is Folder folder && folder.SupportsCumulativeRunTimeTicks) { long ticks = 0; diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 08c92e15a0..dd497845d1 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -167,7 +167,7 @@ namespace MediaBrowser.Providers.Manager throw new HttpRequestException("Invalid image received.", null, response.StatusCode); } - var contentType = response.Content.Headers.ContentType.MediaType; + var contentType = response.Content.Headers.ContentType?.MediaType; // Workaround for tvheadend channel icons // TODO: Isolate this hack into the tvh plugin diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 64ad1bddfe..03e45fb869 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -137,9 +137,7 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - var audio = item as Audio; - - return audio != null; + return item is Audio; } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 74849a5221..f049cc81ff 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -111,10 +111,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (streamFileNames == null) - { - streamFileNames = Array.Empty(); - } + streamFileNames ??= Array.Empty(); mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index 912aedb0db..44ab5aa5b9 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -172,9 +172,7 @@ namespace MediaBrowser.Providers.MediaInfo SubtitleFetcherOrder = subtitleFetcherOrder }; - var episode = video as Episode; - - if (episode != null) + if (video is Episode episode) { request.IndexNumberEnd = episode.IndexNumberEnd; request.SeriesName = episode.SeriesName; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index e9f999c6d0..b086ef07bb 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -15,17 +15,6 @@ namespace MediaBrowser.Providers.MediaInfo { private readonly ILocalizationManager _localization; - private static readonly HashSet SubtitleExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) - { - ".srt", - ".ssa", - ".ass", - ".sub", - ".smi", - ".sami", - ".vtt" - }; - public SubtitleResolver(ILocalizationManager localization) { _localization = localization; @@ -88,6 +77,115 @@ namespace MediaBrowser.Providers.MediaInfo return list; } + public void AddExternalSubtitleStreams( + List streams, + string videoPath, + int startIndex, + string[] files) + { + var videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoPath); + + foreach (var fullName in files) + { + var extension = Path.GetExtension(fullName.AsSpan()); + if (!IsSubtitleExtension(extension)) + { + continue; + } + + var fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fullName); + + MediaStream mediaStream; + + // The subtitle filename must either be equal to the video filename or start with the video filename followed by a dot + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + mediaStream = new MediaStream + { + Index = startIndex++, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = fullName + }; + } + else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + var isForced = fullName.Contains(".forced.", StringComparison.OrdinalIgnoreCase) + || fullName.Contains(".foreign.", StringComparison.OrdinalIgnoreCase); + + var isDefault = fullName.Contains(".default.", StringComparison.OrdinalIgnoreCase); + + // Support xbmc naming conventions - 300.spanish.srt + var languageSpan = fileNameWithoutExtension; + while (languageSpan.Length > 0) + { + var lastDot = languageSpan.LastIndexOf('.'); + var currentSlice = languageSpan[lastDot..]; + if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase) + || currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase) + || currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase)) + { + languageSpan = languageSpan[..lastDot]; + continue; + } + + languageSpan = languageSpan[(lastDot + 1)..]; + break; + } + + var language = languageSpan.ToString(); + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var culture = _localization.FindLanguageInfo(language); + + if (culture != null) + { + language = culture.ThreeLetterISOLanguageName; + } + + mediaStream = new MediaStream + { + Index = startIndex++, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = fullName, + Language = language, + IsForced = isForced, + IsDefault = isDefault + }; + } + else + { + continue; + } + + mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); + + streams.Add(mediaStream); + } + } + + private static bool IsSubtitleExtension(ReadOnlySpan extension) + { + return extension.Equals(".srt", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".ssa", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".ass", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".sub", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".vtt", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".smi", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".sami", StringComparison.OrdinalIgnoreCase); + } + + private static ReadOnlySpan NormalizeFilenameForSubtitleComparison(string filename) + { + // Try to account for sloppy file naming + filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); + filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); + return Path.GetFileNameWithoutExtension(filename.AsSpan()); + } + private void AddExternalSubtitleStreams( List streams, string folder, @@ -100,104 +198,5 @@ namespace MediaBrowser.Providers.MediaInfo AddExternalSubtitleStreams(streams, videoPath, startIndex, files); } - - public void AddExternalSubtitleStreams( - List streams, - string videoPath, - int startIndex, - string[] files) - { - var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoPath); - videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoFileNameWithoutExtension); - - foreach (var fullName in files) - { - var extension = Path.GetExtension(fullName); - - if (!SubtitleExtensions.Contains(extension)) - { - continue; - } - - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); - fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fileNameWithoutExtension); - - if (!string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) && - !fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var codec = Path.GetExtension(fullName).ToLowerInvariant().TrimStart('.'); - - if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase)) - { - codec = "srt"; - } - - // If the subtitle file matches the video file name - if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - streams.Add(new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName, - Codec = codec - }); - } - else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) - { - var isForced = fullName.IndexOf(".forced.", StringComparison.OrdinalIgnoreCase) != -1 || - fullName.IndexOf(".foreign.", StringComparison.OrdinalIgnoreCase) != -1; - - var isDefault = fullName.IndexOf(".default.", StringComparison.OrdinalIgnoreCase) != -1; - - // Support xbmc naming conventions - 300.spanish.srt - var language = fileNameWithoutExtension - .Replace(".forced", string.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(".foreign", string.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(".default", string.Empty, StringComparison.OrdinalIgnoreCase) - .Split('.') - .LastOrDefault(); - - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var culture = _localization.FindLanguageInfo(language); - - if (culture != null) - { - language = culture.ThreeLetterISOLanguageName; - } - - streams.Add(new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName, - Codec = codec, - Language = language, - IsForced = isForced, - IsDefault = isDefault - }); - } - } - } - - private string NormalizeFilenameForSubtitleComparison(string filename) - { - // Try to account for sloppy file naming - filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); - filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); - - // can't normalize this due to languages such as pt-br - // filename = filename.Replace("-", string.Empty); - - // filename = filename.Replace(".", string.Empty); - - return filename; - } } } diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index c36c3af6a7..30af6710ab 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -154,9 +154,7 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - var video = item as Video; - - if (video != null && !video.IsPlaceHolder && video.IsCompleteMedia) + if (item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia) { return true; } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs index ce93924026..2eab95294a 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs @@ -69,58 +69,52 @@ namespace MediaBrowser.Providers.Music private IEnumerable GetResultsFromResponse(Stream stream) { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) + using var oReader = new StreamReader(stream, Encoding.UTF8); + var settings = new XmlReaderSettings() { - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; - using (var reader = XmlReader.Create(oReader, settings)) - { - reader.MoveToContent(); - reader.Read(); + using var reader = XmlReader.Create(oReader, settings); + reader.MoveToContent(); + reader.Read(); - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) { - if (reader.NodeType == XmlNodeType.Element) + case "artist-list": { - switch (reader.Name) + if (reader.IsEmptyElement) { - case "artist-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistList(subReader).ToList(); - } - } - - default: - { - reader.Skip(); - break; - } + reader.Read(); + continue; } + + using var subReader = reader.ReadSubtree(); + return ParseArtistList(subReader).ToList(); } - else + + default: { - reader.Read(); + reader.Skip(); + break; } } - - return Enumerable.Empty(); + } + else + { + reader.Read(); } } + + return Enumerable.Empty(); } private IEnumerable ParseArtistList(XmlReader reader) @@ -145,13 +139,11 @@ namespace MediaBrowser.Providers.Music var mbzId = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) + using var subReader = reader.ReadSubtree(); + var artist = ParseArtist(subReader, mbzId); + if (artist != null) { - var artist = ParseArtist(subReader, mbzId); - if (artist != null) - { - yield return artist; - } + yield return artist; } break; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index e5ad0f3e09..0023d59594 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -128,53 +128,49 @@ namespace MediaBrowser.Providers.Music private IEnumerable GetResultsFromResponse(Stream stream) { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) + using var oReader = new StreamReader(stream, Encoding.UTF8); + var settings = new XmlReaderSettings() { - var settings = new XmlReaderSettings() + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using var reader = XmlReader.Create(oReader, settings); + var results = ReleaseResult.Parse(reader); + + return results.Select(i => + { + var result = new RemoteSearchResult { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true + Name = i.Title, + ProductionYear = i.Year }; - using (var reader = XmlReader.Create(oReader, settings)) + if (i.Artists.Count > 0) { - var results = ReleaseResult.Parse(reader); - - return results.Select(i => + result.AlbumArtist = new RemoteSearchResult { - var result = new RemoteSearchResult - { - Name = i.Title, - ProductionYear = i.Year - }; + SearchProviderName = Name, + Name = i.Artists[0].Item1 + }; - if (i.Artists.Count > 0) - { - result.AlbumArtist = new RemoteSearchResult - { - SearchProviderName = Name, - Name = i.Artists[0].Item1 - }; - - result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2); - } - - if (!string.IsNullOrWhiteSpace(i.ReleaseId)) - { - result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId); - } - - if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) - { - result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId); - } - - return result; - }); + result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2); } - } + + if (!string.IsNullOrWhiteSpace(i.ReleaseId)) + { + result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId); + } + + if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) + { + result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId); + } + + return result; + }); } /// @@ -339,10 +335,8 @@ namespace MediaBrowser.Providers.Music continue; } - using (var subReader = reader.ReadSubtree()) - { - return ParseReleaseList(subReader).ToList(); - } + using var subReader = reader.ReadSubtree(); + return ParseReleaseList(subReader).ToList(); } default: @@ -383,13 +377,11 @@ namespace MediaBrowser.Providers.Music var releaseId = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) + using var subReader = reader.ReadSubtree(); + var release = ParseRelease(subReader, releaseId); + if (release != null) { - var release = ParseRelease(subReader, releaseId); - if (release != null) - { - yield return release; - } + yield return release; } break; @@ -460,14 +452,12 @@ namespace MediaBrowser.Providers.Music case "artist-credit": { - using (var subReader = reader.ReadSubtree()) - { - var artist = ParseArtistCredit(subReader); + using var subReader = reader.ReadSubtree(); + var artist = ParseArtistCredit(subReader); - if (!string.IsNullOrEmpty(artist.Item1)) - { - result.Artists.Add(artist); - } + if (!string.IsNullOrEmpty(artist.Item1)) + { + result.Artists.Add(artist); } break; @@ -505,12 +495,10 @@ namespace MediaBrowser.Providers.Music switch (reader.Name) { case "name-credit": - { - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistNameCredit(subReader); - } - } + { + using var subReader = reader.ReadSubtree(); + return ParseArtistNameCredit(subReader); + } default: { @@ -545,10 +533,8 @@ namespace MediaBrowser.Providers.Music case "artist": { var id = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistArtistCredit(subReader, id); - } + using var subReader = reader.ReadSubtree(); + return ParseArtistArtistCredit(subReader, id); } default: @@ -647,47 +633,43 @@ namespace MediaBrowser.Providers.Music IgnoreComments = true }; - using (var reader = XmlReader.Create(oReader, settings)) + using var reader = XmlReader.Create(oReader, settings); + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + if (reader.NodeType == XmlNodeType.Element) { - if (reader.NodeType == XmlNodeType.Element) + switch (reader.Name) { - switch (reader.Name) + case "release-group-list": { - case "release-group-list": + if (reader.IsEmptyElement) { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) - { - return GetFirstReleaseGroupId(subReader); - } + reader.Read(); + continue; } - default: - { - reader.Skip(); - break; - } + using var subReader = reader.ReadSubtree(); + return GetFirstReleaseGroupId(subReader); + } + + default: + { + reader.Skip(); + break; } } - else - { - reader.Read(); - } } - - return null; + else + { + reader.Read(); + } } + + return null; } private string GetFirstReleaseGroupId(XmlReader reader) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index d22c1b50aa..4a0884c079 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -206,12 +206,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (ourRelease != null) { - var ratingPrefix = string.Equals(info.MetadataCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? string.Empty : info.MetadataCountryCode + "-"; - var newRating = ratingPrefix + ourRelease.Certification; - - newRating = newRating.Replace("de-", "FSK-", StringComparison.OrdinalIgnoreCase); - - movie.OfficialRating = newRating; + movie.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Certification); } else if (usRelease != null) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index bf42ceadef..e4c908a62c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 -using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -55,14 +54,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People return Enumerable.Empty(); } - var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false); + var language = item.GetPreferredMetadataLanguage(); + var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), language, cancellationToken).ConfigureAwait(false); if (personResult?.Images?.Profiles == null) { return Enumerable.Empty(); } var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count]; - var language = item.GetPreferredMetadataLanguage(); for (var i = 0; i < personResult.Images.Profiles.Count; i++) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 1757c82678..6db550b1d0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -32,7 +31,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People { if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId)) { - var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false); + var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); if (personResult != null) { @@ -96,7 +95,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People if (personTmdbId > 0) { - var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false); + var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false); result.HasMetadata = true; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 496e1ae256..da76345b5b 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -300,7 +300,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (ourRelease != null) { - series.OfficialRating = ourRelease.Rating; + series.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Rating); } else if (usRelease != null) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 05e5d3ced7..79ec6139d1 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -276,11 +276,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// Gets a person eg. cast or crew member from the TMDb API based on its TMDb id. /// /// The person's TMDb id. + /// The episode's language. /// The cancellation token. /// The TMDb person information or null if not found. - public async Task GetPersonAsync(int personTmdbId, CancellationToken cancellationToken) + public async Task GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken) { - var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}"; + var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; if (_memoryCache.TryGetValue(key, out Person person)) { return person; @@ -290,6 +291,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb person = await _tmDbClient.GetPersonAsync( personTmdbId, + TmdbUtils.NormalizeLanguage(language), PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 15a44c7ed3..b713736a0f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -63,19 +63,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The Jellyfin person type. public static string MapCrewToPersonType(Crew crew) { - if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) - && crew.Job.Contains("director", StringComparison.InvariantCultureIgnoreCase)) + if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) + && crew.Job.Contains("director", StringComparison.OrdinalIgnoreCase)) { return PersonType.Director; } - if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) - && crew.Job.Contains("producer", StringComparison.InvariantCultureIgnoreCase)) + if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) + && crew.Job.Contains("producer", StringComparison.OrdinalIgnoreCase)) { return PersonType.Producer; } - if (crew.Department.Equals("writing", StringComparison.InvariantCultureIgnoreCase)) + if (crew.Department.Equals("writing", StringComparison.OrdinalIgnoreCase)) { return PersonType.Writer; } @@ -148,6 +148,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb if (parts.Length == 2) { + // TMDB doesn't support Switzerland (de-CH, it-CH or fr-CH) so use the language (de, it or fr) without country code + if (string.Equals(parts[1], "CH", StringComparison.OrdinalIgnoreCase)) + { + return parts[0]; + } + language = parts[0] + "-" + parts[1].ToUpperInvariant(); } @@ -173,5 +179,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return imageLanguage; } + + /// + /// Combines the metadata country code and the parental rating from the Api into the value we store in our database. + /// + /// The Iso 3166-1 country code of the rating country. + /// The rating value returned by the Tmdb Api. + /// The combined parental rating of country code+rating value. + public static string BuildParentalRating(string countryCode, string ratingValue) + { + // exclude US because we store us values as TV-14 without the country code. + var ratingPrefix = string.Equals(countryCode, "US", StringComparison.OrdinalIgnoreCase) ? string.Empty : countryCode + "-"; + var newRating = ratingPrefix + ratingValue; + + return newRating.Replace("DE-", "FSK-", StringComparison.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 5fcf6d9aa1..f6153dd532 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -171,25 +172,19 @@ namespace MediaBrowser.Providers.Studios public IEnumerable GetAvailableImages(string file) { - using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) + using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); + using var reader = new StreamReader(fileStream); + var lines = new List(); + + foreach (var line in reader.ReadAllLines()) { - using (var reader = new StreamReader(fileStream)) + if (!string.IsNullOrWhiteSpace(line)) { - var lines = new List(); - - while (!reader.EndOfStream) - { - var text = reader.ReadLine(); - - if (!string.IsNullOrWhiteSpace(text)) - { - lines.Add(text); - } - } - - return lines; + lines.Add(line); } } + + return lines; } } } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 1f3d9acff2..6aacaa15de 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -187,54 +187,52 @@ namespace MediaBrowser.Providers.Subtitles { var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia; - using (var stream = response.Stream) - using (var memoryStream = new MemoryStream()) + using var stream = response.Stream; + using var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream).ConfigureAwait(false); + memoryStream.Position = 0; + + var savePaths = new List(); + var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); + + if (response.IsForced) { - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; + saveFileName += ".forced"; + } - var savePaths = new List(); - var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); + saveFileName += "." + response.Format.ToLowerInvariant(); - if (response.IsForced) + if (saveInMediaFolder) + { + var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName)); + // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path."); + if (mediaFolderPath.StartsWith(video.ContainingFolderPath, StringComparison.Ordinal)) { - saveFileName += ".forced"; + savePaths.Add(mediaFolderPath); } + } - saveFileName += "." + response.Format.ToLowerInvariant(); + var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); - if (saveInMediaFolder) - { - var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName)); - // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path."); - if (mediaFolderPath.StartsWith(video.ContainingFolderPath)) - { - savePaths.Add(mediaFolderPath); - } - } + // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path."); + if (internalPath.StartsWith(video.GetInternalMetadataPath(), StringComparison.Ordinal)) + { + savePaths.Add(internalPath); + } - var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); - - // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path."); - if (internalPath.StartsWith(video.GetInternalMetadataPath())) - { - savePaths.Add(internalPath); - } - - if (savePaths.Count > 0) - { - await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); - } - else - { - _logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid."); - } + if (savePaths.Count > 0) + { + await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); + } + else + { + _logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid."); } } private async Task TrySaveToFiles(Stream stream, List savePaths) { - Exception exceptionToThrow = null; + List exs = null; foreach (var savePath in savePaths) { @@ -247,19 +245,14 @@ namespace MediaBrowser.Providers.Subtitles Directory.CreateDirectory(Path.GetDirectoryName(savePath)); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true)) - { - await stream.CopyToAsync(fs).ConfigureAwait(false); - } + using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true); + await stream.CopyToAsync(fs).ConfigureAwait(false); return; } catch (Exception ex) { - if (exceptionToThrow == null) - { - exceptionToThrow = ex; - } + (exs ??= new List()).Add(ex); } finally { @@ -269,9 +262,9 @@ namespace MediaBrowser.Providers.Subtitles stream.Position = 0; } - if (exceptionToThrow != null) + if (exs != null) { - throw exceptionToThrow; + throw new AggregateException(exs); } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index ff9f11eabd..302c93f0bc 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -662,6 +662,21 @@ namespace MediaBrowser.XbmcMetadata.Parsers break; } + case "ratings": + { + if (!reader.IsEmptyElement) + { + using var subtree = reader.ReadSubtree(); + FetchFromRatingsNode(subtree, item); + } + else + { + reader.Read(); + } + + break; + } + case "aired": case "formed": case "premiered": @@ -1080,6 +1095,92 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } + private void FetchFromRatingsNode(XmlReader reader, T item) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "rating": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + + var ratingName = reader.GetAttribute("name"); + + using var subtree = reader.ReadSubtree(); + FetchFromRatingNode(subtree, item, ratingName); + + break; + } + + default: + reader.Skip(); + break; + } + } + else + { + reader.Read(); + } + } + } + + private void FetchFromRatingNode(XmlReader reader, T item, string? ratingName) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "value": + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + if (float.TryParse(val, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var ratingValue)) + { + // if ratingName contains tomato --> assume critic rating + if (ratingName != null && + ratingName.Contains("tomato", StringComparison.OrdinalIgnoreCase) && + !ratingName.Contains("audience", StringComparison.OrdinalIgnoreCase)) + { + item.CriticRating = ratingValue; + } + else + { + item.CommunityRating = ratingValue; + } + } + } + + break; + default: + reader.Skip(); + break; + } + } + else + { + reader.Read(); + } + } + } + /// /// Gets the persons from XML node. /// @@ -1232,6 +1333,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers "discart" => ImageType.Disc, "landscape" => ImageType.Thumb, "clearart" => ImageType.Art, + "fanart" => ImageType.Backdrop, // unknown type (including "poster") --> primary _ => ImageType.Primary, }; diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs index bd557d783c..f7a85a07e6 100644 --- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) => directoryService.GetFile(Path.Combine(info.Path, "album.nfo")); } } diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs index 54bb83114b..2b563b7fa6 100644 --- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) => directoryService.GetFile(Path.Combine(info.Path, "artist.nfo")); } } diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs index 64b208345e..f6c5877b40 100644 --- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) { var path = Path.ChangeExtension(info.Path, ".nfo"); diff --git a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs index 97220cf7ea..d01abadf67 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) => directoryService.GetFile(Path.Combine(info.Path, "season.nfo")); } } diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs index 9a9b941236..002b944631 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) => directoryService.GetFile(Path.Combine(info.Path, "tvshow.nfo")); } } diff --git a/RSSDP/DeviceAvailableEventArgs.cs b/RSSDP/DeviceAvailableEventArgs.cs index b7d22a7df5..04b14c4dca 100644 --- a/RSSDP/DeviceAvailableEventArgs.cs +++ b/RSSDP/DeviceAvailableEventArgs.cs @@ -8,7 +8,7 @@ namespace Rssdp /// public sealed class DeviceAvailableEventArgs : EventArgs { - public IPAddress LocalIpAddress { get; set; } + public IPAddress RemoteIpAddress { get; set; } private readonly DiscoveredSsdpDevice _DiscoveredDevice; diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 8f1f0fa613..f448ad38bb 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -415,10 +415,7 @@ namespace Rssdp.Infrastructure { lock (_SendSocketSynchroniser) { - if (_sendSockets == null) - { - _sendSockets = CreateSocketAndListenForResponsesAsync(); - } + _sendSockets ??= CreateSocketAndListenForResponsesAsync(); } } } diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 0cdc5ce3db..188e298e2a 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -211,7 +211,7 @@ namespace Rssdp.Infrastructure /// Raises the event. /// /// - protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress) + protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IpAddress) { if (this.IsDisposed) { @@ -223,7 +223,7 @@ namespace Rssdp.Infrastructure { handlers(this, new DeviceAvailableEventArgs(device, isNewDevice) { - LocalIpAddress = localIpAddress + RemoteIpAddress = IpAddress }); } } @@ -289,7 +289,7 @@ namespace Rssdp.Infrastructure } } - private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress localIpAddress) + private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress IpAddress) { bool isNewDevice = false; lock (_Devices) @@ -307,17 +307,17 @@ namespace Rssdp.Infrastructure } } - DeviceFound(device, isNewDevice, localIpAddress); + DeviceFound(device, isNewDevice, IpAddress); } - private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress) + private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IpAddress) { if (!NotificationTypeMatchesFilter(device)) { return; } - OnDeviceAvailable(device, isNewDevice, localIpAddress); + OnDeviceAvailable(device, isNewDevice, IpAddress); } private bool NotificationTypeMatchesFilter(DiscoveredSsdpDevice device) @@ -350,7 +350,7 @@ namespace Rssdp.Infrastructure return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken); } - private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress) + private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress IpAddress) { if (!message.IsSuccessStatusCode) { @@ -370,11 +370,11 @@ namespace Rssdp.Infrastructure ResponseHeaders = message.Headers }; - AddOrUpdateDiscoveredDevice(device, localIpAddress); + AddOrUpdateDiscoveredDevice(device, IpAddress); } } - private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress localIpAddress) + private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress IpAddress) { if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0) { @@ -384,7 +384,7 @@ namespace Rssdp.Infrastructure var notificationType = GetFirstHeaderStringValue("NTS", message); if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0) { - ProcessAliveNotification(message, localIpAddress); + ProcessAliveNotification(message, IpAddress); } else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0) { @@ -392,7 +392,7 @@ namespace Rssdp.Infrastructure } } - private void ProcessAliveNotification(HttpRequestMessage message, IPAddress localIpAddress) + private void ProcessAliveNotification(HttpRequestMessage message, IPAddress IpAddress) { var location = GetFirstHeaderUriValue("Location", message); if (location != null) @@ -407,7 +407,7 @@ namespace Rssdp.Infrastructure ResponseHeaders = message.Headers }; - AddOrUpdateDiscoveredDevice(device, localIpAddress); + AddOrUpdateDiscoveredDevice(device, IpAddress); } } @@ -628,7 +628,7 @@ namespace Rssdp.Infrastructure private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e) { - ProcessNotificationMessage(e.Message, e.LocalIpAddress); + ProcessNotificationMessage(e.Message, e.ReceivedFrom.Address); } } } diff --git a/apiclient/.openapi-generator-ignore b/apiclient/.openapi-generator-ignore deleted file mode 100644 index f3802cf541..0000000000 --- a/apiclient/.openapi-generator-ignore +++ /dev/null @@ -1,2 +0,0 @@ -# Prevent generator from creating these files: -git_push.sh diff --git a/apiclient/templates/typescript/axios/generate.sh b/apiclient/templates/typescript/axios/generate.sh deleted file mode 100644 index 9599f85dbd..0000000000 --- a/apiclient/templates/typescript/axios/generate.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -artifactsDirectory="${1}" - -java -jar openapi-generator-cli.jar generate \ - --input-spec ${artifactsDirectory}/openapispec/openapi.json \ - --generator-name typescript-axios \ - --output ./apiclient/generated/typescript/axios \ - --template-dir ./apiclient/templates/typescript/axios \ - --ignore-file-override ./apiclient/.openapi-generator-ignore \ - --additional-properties=useSingleRequestParameter="true",withSeparateModelsAndApi="true",modelPackage="models",apiPackage="api",npmName="axios" diff --git a/apiclient/templates/typescript/axios/package.mustache b/apiclient/templates/typescript/axios/package.mustache deleted file mode 100644 index 7bfab08cbb..0000000000 --- a/apiclient/templates/typescript/axios/package.mustache +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@jellyfin/client-axios", - "version": "10.7.0{{snapshotVersion}}", - "description": "Jellyfin api client using axios", - "author": "Jellyfin Contributors", - "keywords": [ - "axios", - "typescript", - "jellyfin" - ], - "license": "GPL-3.0-only", - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", - "scripts": { - "build": "tsc --outDir dist/", - "prepublishOnly": "npm run build" - }, - "dependencies": { - "axios": "^0.19.2" - }, - "devDependencies": { - "@types/node": "^12.11.5", - "typescript": "^3.6.4" - }{{#npmRepository}},{{/npmRepository}} -{{#npmRepository}} - "publishConfig": { - "registry": "{{npmRepository}}" - } -{{/npmRepository}} -} diff --git a/debian/postinst b/debian/postinst index 860222e051..2f9c4cffbf 100644 --- a/debian/postinst +++ b/debian/postinst @@ -9,6 +9,8 @@ if [[ -f $DEFAULT_FILE ]]; then . $DEFAULT_FILE fi +JELLYFIN_USER=${JELLYFIN_USER:-jellyfin} + # Data directories for program data (cache, db), configs, and logs PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} @@ -18,12 +20,12 @@ CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} case "$1" in configure) # create jellyfin group if it does not exist - if [[ -z "$(getent group jellyfin)" ]]; then - addgroup --quiet --system jellyfin > /dev/null 2>&1 + if [[ -z "$(getent group ${JELLYFIN_USER})" ]]; then + addgroup --quiet --system ${JELLYFIN_USER} > /dev/null 2>&1 fi # create jellyfin user if it does not exist - if [[ -z "$(getent passwd jellyfin)" ]]; then - adduser --system --ingroup jellyfin --shell /bin/false jellyfin --no-create-home --home ${PROGRAMDATA} \ + if [[ -z "$(getent passwd ${JELLYFIN_USER})" ]]; then + adduser --system --ingroup ${JELLYFIN_USER} --shell /bin/false ${JELLYFIN_USER} --no-create-home --home ${PROGRAMDATA} \ --gecos "Jellyfin default user" > /dev/null 2>&1 fi # ensure $PROGRAMDATA exists @@ -43,7 +45,7 @@ case "$1" in mkdir $CACHEDATA fi # Ensure permissions are correct on all config directories - chown -R jellyfin $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA + chown -R ${JELLYFIN_USER} $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA chgrp adm $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA chmod 0750 $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64 index ec0321f477..0923645007 100644 --- a/deployment/Dockerfile.debian.amd64 +++ b/deployment/Dockerfile.debian.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64 index 8fd5ddb932..eef272d5be 100644 --- a/deployment/Dockerfile.debian.arm64 +++ b/deployment/Dockerfile.debian.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf index 14615d19fb..154481d64a 100644 --- a/deployment/Dockerfile.debian.armhf +++ b/deployment/Dockerfile.debian.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64 index 1f6ca15586..171ebe3728 100644 --- a/deployment/Dockerfile.linux.amd64 +++ b/deployment/Dockerfile.linux.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl index 6af5d8baf5..a0a5f69235 100644 --- a/deployment/Dockerfile.linux.amd64-musl +++ b/deployment/Dockerfile.linux.amd64-musl @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64 index 15b59e29d1..ae59802cd4 100644 --- a/deployment/Dockerfile.linux.arm64 +++ b/deployment/Dockerfile.linux.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf index 71a0fda215..236b35ce5e 100644 --- a/deployment/Dockerfile.linux.armhf +++ b/deployment/Dockerfile.linux.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos index 9291bcbb94..2e80d4a6ec 100644 --- a/deployment/Dockerfile.macos +++ b/deployment/Dockerfile.macos @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index e98ba74f87..852a6b553c 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index d1fd8818e6..bffeb7307e 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index 8e79d417cf..e90da4636b 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index 627caa95a8..aae262ee61 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64 index 5723abcae6..c847b1621d 100644 --- a/deployment/Dockerfile.windows.amd64 +++ b/deployment/Dockerfile.windows.amd64 @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 19c0a08b23..1a9f2bf96c 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -1,13 +1,6 @@ - - - - - - - @@ -22,6 +15,10 @@ + + + + @@ -43,6 +40,8 @@ or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token --> + + diff --git a/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs new file mode 100644 index 0000000000..1170838156 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using AutoFixture; +using AutoFixture.AutoMoq; +using Jellyfin.Api.Controllers; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using Moq; +using Xunit; + +namespace Jellyfin.Api.Tests.Controllers +{ + public class DynamicHlsControllerTests + { + [Theory] + [MemberData(nameof(GetSegmentLengths_Success_TestData))] + public void GetSegmentLengths_Success(long runtimeTicks, int segmentlength, double[] expected) + { + var res = DynamicHlsController.GetSegmentLengthsInternal(runtimeTicks, segmentlength); + Assert.Equal(expected.Length, res.Length); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], res[i]); + } + } + + public static IEnumerable GetSegmentLengths_Success_TestData() + { + yield return new object[] { 0, 6, Array.Empty() }; + yield return new object[] + { + TimeSpan.FromSeconds(3).Ticks, + 6, + new double[] { 3 } + }; + yield return new object[] + { + TimeSpan.FromSeconds(6).Ticks, + 6, + new double[] { 6 } + }; + yield return new object[] + { + TimeSpan.FromSeconds(3.3333333).Ticks, + 6, + new double[] { 3.3333333 } + }; + yield return new object[] + { + TimeSpan.FromSeconds(9.3333333).Ticks, + 6, + new double[] { 6, 3.3333333 } + }; + } + } +} diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 050d4c040c..839cfb2807 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,10 +15,10 @@ - - - - + + + + diff --git a/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs new file mode 100644 index 0000000000..668bd8f878 --- /dev/null +++ b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs @@ -0,0 +1,131 @@ +using Emby.Dlna; +using Emby.Dlna.PlayTo; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Jellyfin.Dlna.Tests +{ + public class DlnaManagerTests + { + private DlnaManager GetManager() + { + var xmlSerializer = new Mock(); + var fileSystem = new Mock(); + var appPaths = new Mock(); + var loggerFactory = new Mock(); + var appHost = new Mock(); + + return new DlnaManager(xmlSerializer.Object, fileSystem.Object, appPaths.Object, loggerFactory.Object, appHost.Object); + } + + [Fact] + public void IsMatch_GivenMatchingName_ReturnsTrue() + { + var device = new DeviceInfo() + { + Name = "My Device", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", + }; + + var profile = new DeviceProfile() + { + Name = "Test Profile", + FriendlyName = "My Device", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", + Identification = new () + { + FriendlyName = "My Device", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", + } + }; + + var profile2 = new DeviceProfile() + { + Name = "Test Profile", + FriendlyName = "My Device", + Identification = new DeviceIdentification() + { + FriendlyName = "My Device", + } + }; + + var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification); + var deviceMatch2 = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification); + + Assert.True(deviceMatch); + Assert.True(deviceMatch2); + } + + [Fact] + public void IsMatch_GivenNamesAndManufacturersDoNotMatch_ReturnsFalse() + { + var device = new DeviceInfo() + { + Name = "My Device", + Manufacturer = "JVC" + }; + + var profile = new DeviceProfile() + { + Name = "Test Profile", + FriendlyName = "My Device", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", + Identification = new () + { + FriendlyName = "My Device", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", + } + }; + + var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification); + + Assert.False(deviceMatch); + } + + [Fact] + public void IsMatch_GivenNamesAndRegExMatch_ReturnsTrue() + { + var device = new DeviceInfo() + { + Name = "My Device" + }; + + var profile = new DeviceProfile() + { + Name = "Test Profile", + FriendlyName = "My .*", + Identification = new () + }; + + var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification); + + Assert.True(deviceMatch); + } + } +} diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index c2c0dca1bb..f7c21f0721 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -10,7 +10,8 @@ - + + diff --git a/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs b/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs new file mode 100644 index 0000000000..cca056c280 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs @@ -0,0 +1,19 @@ +using MediaBrowser.Model.Dlna; +using Xunit; + +namespace Jellyfin.Model.Tests.Dlna +{ + public class ContainerProfileTests + { + private readonly ContainerProfile _emptyContainerProfile = new ContainerProfile(); + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("mp4")] + public void ContainsContainer_EmptyContainerProfile_True(string? containers) + { + Assert.True(_emptyContainerProfile.ContainsContainer(containers)); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs index 9bbbe29709..c56046f039 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -148,7 +148,7 @@ namespace Jellyfin.Naming.Tests.Video yield return new object[] { new VideoFileInfo( - path: @"/server/Movies/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - Ozlem/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - Ozlem.mp4", + path: @"/server/Movies/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - JEFF/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - JEFF.mp4", container: "mp4", name: "Rain Man", year: 1988) @@ -200,6 +200,10 @@ namespace Jellyfin.Naming.Tests.Video Assert.NotNull(results[0]); Assert.NotNull(results[1]); Assert.Null(results[2]); + foreach (var result in results) + { + Assert.Null(result?.Container); + } } } } diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 9b0da2b3c4..671b8598de 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -384,6 +384,9 @@ namespace Jellyfin.Networking.Tests [InlineData("jellyfin.org", "eth16", false, "eth16")] // User on external network, no binding - so result is the 1st external. [InlineData("jellyfin.org", "", false, "eth11")] + // Dns failure - should skip the test. + // https://en.wikipedia.org/wiki/.test + [InlineData("invalid.domain.test", "", false, "eth11")] // User assumed to be internal, no binding - so result is the 1st internal. [InlineData("", "", false, "eth16")] public void TestBindInterfaces(string source, string bindAddresses, bool ipv6enabled, string result) @@ -416,10 +419,13 @@ namespace Jellyfin.Networking.Tests _ = nm.TryParseInterface(result, out Collection? resultObj); - if (resultObj != null) + // Check to see if dns resolution is working. If not, skip test. + _ = IPHost.TryParse(source, out var host); + + if (resultObj != null && host?.HasAddress == true) { result = ((IPNetAddress)resultObj[0]).ToString(true); - var intf = nm.GetBindInterface(source, out int? _); + var intf = nm.GetBindInterface(source, out _); Assert.Equal(intf, result); } diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj new file mode 100644 index 0000000000..b37515e78e --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -0,0 +1,37 @@ + + + + net5.0 + false + true + enable + AllEnabledByDefault + ../jellyfin-tests.ruleset + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs new file mode 100644 index 0000000000..b160e676e4 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs @@ -0,0 +1,96 @@ +#pragma warning disable CA1002 // Do not expose generic lists + +using System.Collections.Generic; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Providers.MediaInfo; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo +{ + public class SubtitleResolverTests + { + public static IEnumerable AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData() + { + var index = 0; + yield return new object[] + { + new List(), + "/video/My.Video.mkv", + index, + new[] + { + "/video/My.Video.mp3", + "/video/My.Video.png", + "/video/My.Video.srt", + "/video/My.Video.txt", + "/video/My.Video.vtt", + "/video/My.Video.ass", + "/video/My.Video.sub", + "/video/My.Video.ssa", + "/video/My.Video.smi", + "/video/My.Video.sami", + "/video/My.Video.en.srt", + "/video/My.Video.default.en.srt", + "/video/My.Video.default.forced.en.srt", + "/video/My.Video.en.default.forced.srt", + "/video/My.Video.With.Additional.Garbage.en.srt", + "/video/My.Video With Additional Garbage.srt" + }, + new[] + { + CreateMediaStream("/video/My.Video.srt", "srt", null, index++), + CreateMediaStream("/video/My.Video.vtt", "vtt", null, index++), + CreateMediaStream("/video/My.Video.ass", "ass", null, index++), + CreateMediaStream("/video/My.Video.sub", "sub", null, index++), + CreateMediaStream("/video/My.Video.ssa", "ssa", null, index++), + CreateMediaStream("/video/My.Video.smi", "smi", null, index++), + CreateMediaStream("/video/My.Video.sami", "sami", null, index++), + CreateMediaStream("/video/My.Video.en.srt", "srt", "en", index++), + CreateMediaStream("/video/My.Video.default.en.srt", "srt", "en", index++, isDefault: true), + CreateMediaStream("/video/My.Video.default.forced.en.srt", "srt", "en", index++, isForced: true, isDefault: true), + CreateMediaStream("/video/My.Video.en.default.forced.srt", "srt", "en", index++, isForced: true, isDefault: true), + CreateMediaStream("/video/My.Video.With.Additional.Garbage.en.srt", "srt", "en", index), + } + }; + } + + [Theory] + [MemberData(nameof(AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData))] + public void AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles(List streams, string videoPath, int startIndex, string[] files, MediaStream[] expectedResult) + { + new SubtitleResolver(Mock.Of()).AddExternalSubtitleStreams(streams, videoPath, startIndex, files); + + Assert.Equal(expectedResult.Length, streams.Count); + for (var i = 0; i < expectedResult.Length; i++) + { + var expected = expectedResult[i]; + var actual = streams[i]; + + Assert.Equal(expected.Index, actual.Index); + Assert.Equal(expected.Type, actual.Type); + Assert.Equal(expected.IsExternal, actual.IsExternal); + Assert.Equal(expected.Path, actual.Path); + Assert.Equal(expected.IsDefault, actual.IsDefault); + Assert.Equal(expected.IsForced, actual.IsForced); + Assert.Equal(expected.Language, actual.Language); + } + } + + private static MediaStream CreateMediaStream(string path, string codec, string? language, int index, bool isForced = false, bool isDefault = false) + { + return new () + { + Index = index, + Codec = codec, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = path, + IsDefault = isDefault, + IsForced = isForced, + Language = language + }; + } + } +} diff --git a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs new file mode 100644 index 0000000000..f6a7c676f4 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs @@ -0,0 +1,27 @@ +using MediaBrowser.Providers.Plugins.Tmdb; +using Xunit; + +namespace Jellyfin.Providers.Tests.Tmdb +{ + public static class TmdbUtilsTests + { + [Theory] + [InlineData("de", "de")] + [InlineData("En", "En")] + [InlineData("de-de", "de-DE")] + [InlineData("en-US", "en-US")] + [InlineData("de-CH", "de")] + public static void NormalizeLanguage_Valid_Success(string input, string expected) + { + Assert.Equal(expected, TmdbUtils.NormalizeLanguage(input)); + } + + [Theory] + [InlineData(null, null)] + [InlineData("", "")] + public static void NormalizeLanguage_Invalid_Equal(string? input, string? expected) + { + Assert.Equal(expected, TmdbUtils.NormalizeLanguage(input!)); + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs new file mode 100644 index 0000000000..71f8c51818 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using AutoFixture; +using AutoFixture.AutoMoq; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Data +{ + public class SqliteItemRepositoryTests + { + public const string VirtualMetaDataPath = "%MetadataPath%"; + public const string MetaDataPath = "/meta/data/path"; + + private readonly IFixture _fixture; + private readonly SqliteItemRepository _sqliteItemRepository; + + public SqliteItemRepositoryTests() + { + var appHost = new Mock(); + appHost.Setup(x => x.ExpandVirtualPath(It.IsAny())) + .Returns((string x) => x.Replace(VirtualMetaDataPath, MetaDataPath, StringComparison.Ordinal)); + appHost.Setup(x => x.ReverseVirtualPath(It.IsAny())) + .Returns((string x) => x.Replace(MetaDataPath, VirtualMetaDataPath, StringComparison.Ordinal)); + + _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true }); + _fixture.Inject(appHost); + _sqliteItemRepository = _fixture.Create(); + } + + public static IEnumerable ItemImageInfoFromValueString_Valid_TestData() + { + yield return new object[] + { + "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN", + new ItemImageInfo + { + Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg", + Type = ImageType.Primary, + DateModified = new DateTime(637452096478512963, DateTimeKind.Utc), + Width = 1920, + Height = 1080, + BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN" + } + }; + + yield return new object[] + { + "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*0*0", + new ItemImageInfo + { + Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg", + Type = ImageType.Primary, + } + }; + + yield return new object[] + { + "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary", + new ItemImageInfo + { + Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg", + Type = ImageType.Primary, + } + }; + + yield return new object[] + { + "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*600", + new ItemImageInfo + { + Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg", + Type = ImageType.Primary, + } + }; + + yield return new object[] + { + "%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336", + new ItemImageInfo + { + Path = "/meta/data/path/library/68/68578562b96c80a7ebd530848801f645/poster.jpg", + Type = ImageType.Primary, + DateModified = new DateTime(637264380567586027, DateTimeKind.Utc), + Width = 600, + Height = 336 + } + }; + } + + [Theory] + [MemberData(nameof(ItemImageInfoFromValueString_Valid_TestData))] + public void ItemImageInfoFromValueString_Valid_Success(string value, ItemImageInfo expected) + { + var result = _sqliteItemRepository.ItemImageInfoFromValueString(value); + Assert.Equal(expected.Path, result.Path); + Assert.Equal(expected.Type, result.Type); + Assert.Equal(expected.DateModified, result.DateModified); + Assert.Equal(expected.Width, result.Width); + Assert.Equal(expected.Height, result.Height); + Assert.Equal(expected.BlurHash, result.BlurHash); + } + + [Theory] + [InlineData("")] + [InlineData("*")] + [InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")] + public void ItemImageInfoFromValueString_Invalid_Null(string value) + { + Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value)); + } + + public static IEnumerable DeserializeImages_Valid_TestData() + { + yield return new object[] + { + "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN", + new ItemImageInfo[] + { + new ItemImageInfo() + { + Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg", + Type = ImageType.Primary, + DateModified = new DateTime(637452096478512963, DateTimeKind.Utc), + Width = 1920, + Height = 1080, + BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN" + } + } + }; + + yield return new object[] + { + "%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg*637261226720645297*Primary*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png*637261226720805297*Logo*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg*637261226721285297*Thumb*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg*637261226721685297*Backdrop*0*0", + new ItemImageInfo[] + { + new ItemImageInfo() + { + Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg", + Type = ImageType.Primary, + DateModified = new DateTime(637261226720645297, DateTimeKind.Utc), + }, + new ItemImageInfo() + { + Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png", + Type = ImageType.Logo, + DateModified = new DateTime(637261226720805297, DateTimeKind.Utc), + }, + new ItemImageInfo() + { + Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg", + Type = ImageType.Thumb, + DateModified = new DateTime(637261226721285297, DateTimeKind.Utc), + }, + new ItemImageInfo() + { + Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg", + Type = ImageType.Backdrop, + DateModified = new DateTime(637261226721685297, DateTimeKind.Utc), + } + } + }; + } + + [Theory] + [MemberData(nameof(DeserializeImages_Valid_TestData))] + public void DeserializeImages_Valid_Success(string value, ItemImageInfo[] expected) + { + var result = _sqliteItemRepository.DeserializeImages(value); + Assert.Equal(expected.Length, result.Length); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i].Path, result[i].Path); + Assert.Equal(expected[i].Type, result[i].Type); + Assert.Equal(expected[i].DateModified, result[i].DateModified); + Assert.Equal(expected[i].Width, result[i].Width); + Assert.Equal(expected[i].Height, result[i].Height); + Assert.Equal(expected[i].BlurHash, result[i].BlurHash); + } + } + + [Theory] + [MemberData(nameof(DeserializeImages_Valid_TestData))] + public void SerializeImages_Valid_Success(string expected, ItemImageInfo[] value) + { + Assert.Equal(expected, _sqliteItemRepository.SerializeImages(value)); + } + + public static IEnumerable DeserializeProviderIds_Valid_TestData() + { + yield return new object[] + { + "Imdb=tt0119567", + new Dictionary() + { + { "Imdb", "tt0119567" }, + } + }; + + yield return new object[] + { + "Imdb=tt0119567|Tmdb=330|TmdbCollection=328", + new Dictionary() + { + { "Imdb", "tt0119567" }, + { "Tmdb", "330" }, + { "TmdbCollection", "328" }, + } + }; + + yield return new object[] + { + "MusicBrainzAlbum=9d363e43-f24f-4b39-bc5a-7ef305c677c7|MusicBrainzReleaseGroup=63eba062-847c-3b73-8b0f-6baf27bba6fa|AudioDbArtist=111352|AudioDbAlbum=2116560|MusicBrainzAlbumArtist=20244d07-534f-4eff-b4d4-930878889970", + new Dictionary() + { + { "MusicBrainzAlbum", "9d363e43-f24f-4b39-bc5a-7ef305c677c7" }, + { "MusicBrainzReleaseGroup", "63eba062-847c-3b73-8b0f-6baf27bba6fa" }, + { "AudioDbArtist", "111352" }, + { "AudioDbAlbum", "2116560" }, + { "MusicBrainzAlbumArtist", "20244d07-534f-4eff-b4d4-930878889970" }, + } + }; + } + + [Theory] + [MemberData(nameof(DeserializeProviderIds_Valid_TestData))] + public void DeserializeProviderIds_Valid_Success(string value, Dictionary expected) + { + var result = new ProviderIdsExtensionsTestsObject(); + SqliteItemRepository.DeserializeProviderIds(value, result); + Assert.Equal(expected, result.ProviderIds); + } + + [Theory] + [MemberData(nameof(DeserializeProviderIds_Valid_TestData))] + public void SerializeProviderIds_Valid_Success(string expected, Dictionary values) + { + Assert.Equal(expected, SqliteItemRepository.SerializeProviderIds(values)); + } + + private class ProviderIdsExtensionsTestsObject : IHasProviderIds + { + public Dictionary ProviderIds { get; set; } = new Dictionary(); + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs index 614a689759..30e6542f94 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs @@ -42,6 +42,16 @@ namespace Jellyfin.Server.Implementations.Tests.IO } } + [Theory] + [InlineData("ValidFileName", "ValidFileName")] + [InlineData("AC/DC", "AC DC")] + [InlineData("Invalid\0", "Invalid ")] + [InlineData("AC/DC\0KD/A", "AC DC KD A")] + public void GetValidFilename_ReturnsValidFilename(string filename, string expectedFileName) + { + Assert.Equal(expectedFileName, _sut.GetValidFilename(filename)); + } + [SkippableFact] public void GetFileInfo_DanglingSymlink_ExistsFalse() { diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 486899f4f0..27713d58a3 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -22,8 +22,8 @@ - - + + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs index 8765192150..c393742eb3 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; using Moq; using Xunit; @@ -28,7 +29,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library { Parent = parent, CollectionType = CollectionType.TvShows, - Path = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv" + FileInfo = new FileSystemMetadata() + { + FullName = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv" + } }; Assert.Null(episodeResolver.Resolve(itemResolveArgs)); @@ -48,7 +52,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library { Parent = series, CollectionType = CollectionType.TvShows, - Path = "Extras/Extras S01E01.mkv" + FileInfo = new FileSystemMetadata() + { + FullName = "Extras/Extras S01E01.mkv" + } }; Assert.NotNull(episodeResolver.Resolve(itemResolveArgs)); } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index e5508243f0..c5cc056f5d 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -33,6 +33,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff", "/home/jeff/", "/home/jeff/myfile.mkv")] [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/home/jeff/", "/home/jeff/myfile.mkv")] [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/", "/myfile.mkv")] + [InlineData("/o", "/o", "/s", "/s")] // regression test for #5977 public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, string? expectedResult) { Assert.True(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result)); diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs index 8847239d90..c859d11c69 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Net.Http; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using AutoFixture; @@ -15,8 +16,6 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv { public class HdHomerunHostTests { - private const string TestIp = "http://192.168.1.182"; - private readonly Fixture _fixture; private readonly HdHomerunHost _hdHomerunHost; @@ -30,7 +29,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv { return Task.FromResult(new HttpResponseMessage() { - Content = new StreamContent(File.OpenRead("Test Data/LiveTv/" + m.RequestUri?.Segments[^1])) + Content = new StreamContent(File.OpenRead(Path.Combine("Test Data/LiveTv", m.RequestUri!.Host, m.RequestUri.Segments[^1]))) }); }); @@ -50,7 +49,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv { var host = new TunerHostInfo() { - Url = TestIp + Url = "192.168.1.182" }; var modelInfo = await _hdHomerunHost.GetModelInfo(host, true, CancellationToken.None).ConfigureAwait(false); @@ -65,6 +64,26 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Assert.Equal("http://192.168.1.182:80/lineup.json", modelInfo.LineupURL); } + [Fact] + public async Task GetModelInfo_Legacy_Success() + { + var host = new TunerHostInfo() + { + Url = "10.10.10.100" + }; + + var modelInfo = await _hdHomerunHost.GetModelInfo(host, true, CancellationToken.None).ConfigureAwait(false); + Assert.Equal("HDHomeRun DUAL", modelInfo.FriendlyName); + Assert.Equal("HDHR3-US", modelInfo.ModelNumber); + Assert.Equal("hdhomerun3_atsc", modelInfo.FirmwareName); + Assert.Equal("20200225", modelInfo.FirmwareVersion); + Assert.Equal("10xxxxx5", modelInfo.DeviceID); + Assert.Null(modelInfo.DeviceAuth); + Assert.Equal(2, modelInfo.TunerCount); + Assert.Equal("http://10.10.10.100:80", modelInfo.BaseURL); + Assert.Null(modelInfo.LineupURL); + } + [Fact] public async Task GetModelInfo_EmptyUrl_ArgumentException() { @@ -81,7 +100,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv { var host = new TunerHostInfo() { - Url = TestIp + Url = "192.168.1.182" }; var channels = await _hdHomerunHost.GetLineup(host, CancellationToken.None).ConfigureAwait(false); @@ -93,12 +112,24 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Assert.Equal("http://192.168.1.111:5004/auto/v4.1", channels[0].URL); } + [Fact] + public async Task GetLineup_Legacy_Success() + { + var host = new TunerHostInfo() + { + Url = "10.10.10.100" + }; + + // Placeholder json is invalid, just need to make sure we can reach it + await Assert.ThrowsAsync(() => _hdHomerunHost.GetLineup(host, CancellationToken.None)); + } + [Fact] public async Task GetLineup_ImportFavoritesOnly_Success() { var host = new TunerHostInfo() { - Url = TestIp, + Url = "192.168.1.182", ImportFavoritesOnly = true }; @@ -114,9 +145,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv [Fact] public async Task TryGetTunerHostInfo_Valid_Success() { - var host = await _hdHomerunHost.TryGetTunerHostInfo(TestIp, CancellationToken.None).ConfigureAwait(false); + var host = await _hdHomerunHost.TryGetTunerHostInfo("192.168.1.182", CancellationToken.None).ConfigureAwait(false); Assert.Equal(_hdHomerunHost.Type, host.Type); - Assert.Equal(TestIp, host.Url); + Assert.Equal("192.168.1.182", host.Url); Assert.Equal("HDHomeRun PRIME", host.FriendlyName); Assert.Equal("FFFFFFFF", host.DeviceId); Assert.Equal(3, host.TunerCount); diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs new file mode 100644 index 0000000000..e8b93b437b --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using Emby.Server.Implementations.LiveTv.EmbyTV; +using MediaBrowser.Controller.LiveTv; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.LiveTv +{ + public static class RecordingHelperTests + { + public static IEnumerable GetRecordingName_Success_TestData() + { + yield return new object[] + { + "The Incredibles 2020_04_20_21_06_00", + new TimerInfo + { + Name = "The Incredibles", + StartDate = new DateTime(2020, 4, 20, 21, 6, 0, DateTimeKind.Local), + IsMovie = true + } + }; + + yield return new object[] + { + "The Incredibles (2004)", + new TimerInfo + { + Name = "The Incredibles", + IsMovie = true, + ProductionYear = 2004 + } + }; + + yield return new object[] + { + "The Big Bang Theory 2020_04_20_21_06_00", + new TimerInfo + { + Name = "The Big Bang Theory", + StartDate = new DateTime(2020, 4, 20, 21, 6, 0, DateTimeKind.Local), + IsProgramSeries = true, + } + }; + + yield return new object[] + { + "The Big Bang Theory S12E10", + new TimerInfo + { + Name = "The Big Bang Theory", + IsProgramSeries = true, + SeasonNumber = 12, + EpisodeNumber = 10 + } + }; + + yield return new object[] + { + "The Big Bang Theory S12E10 The VCR Illumination", + new TimerInfo + { + Name = "The Big Bang Theory", + IsProgramSeries = true, + SeasonNumber = 12, + EpisodeNumber = 10, + EpisodeTitle = "The VCR Illumination" + } + }; + + yield return new object[] + { + "The Big Bang Theory 2018-12-06", + new TimerInfo + { + Name = "The Big Bang Theory", + IsProgramSeries = true, + OriginalAirDate = new DateTime(2018, 12, 6) + } + }; + + yield return new object[] + { + "The Big Bang Theory 2018-12-06 - The VCR Illumination", + new TimerInfo + { + Name = "The Big Bang Theory", + IsProgramSeries = true, + OriginalAirDate = new DateTime(2018, 12, 6), + EpisodeTitle = "The VCR Illumination" + } + }; + + yield return new object[] + { + "The Big Bang Theory 2018_12_06_21_06_00 - The VCR Illumination", + new TimerInfo + { + Name = "The Big Bang Theory", + StartDate = new DateTime(2018, 12, 6, 21, 6, 0, DateTimeKind.Local), + IsProgramSeries = true, + OriginalAirDate = new DateTime(2018, 12, 6), + EpisodeTitle = "The VCR Illumination" + } + }; + } + + [Theory] + [MemberData(nameof(GetRecordingName_Success_TestData))] + public static void GetRecordingName_Success(string expected, TimerInfo timerInfo) + { + Assert.Equal(expected, RecordingHelper.GetRecordingName(timerInfo)); + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/discover.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/discover.json new file mode 100644 index 0000000000..a4ad4ed444 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/discover.json @@ -0,0 +1 @@ +{"FriendlyName":"HDHomeRun DUAL","ModelNumber":"HDHR3-US","Legacy":1,"FirmwareName":"hdhomerun3_atsc","FirmwareVersion":"20200225","DeviceID":"10xxxxx5","TunerCount":2,"BaseURL":"http://10.10.10.100:80"} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/lineup.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/lineup.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/lineup.json @@ -0,0 +1 @@ +{} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/192.168.1.182/discover.json similarity index 100% rename from tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json rename to tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/192.168.1.182/discover.json diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/192.168.1.182/lineup.json similarity index 100% rename from tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json rename to tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/192.168.1.182/lineup.json diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 0de92249a9..4bf6faef73 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,10 +9,10 @@ - - - - + + + + @@ -22,6 +22,13 @@ + + + + PreserveNewest + + + diff --git a/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json b/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json new file mode 100644 index 0000000000..809e880c71 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "parallelizeAssembly": false, + "parallelizeTestCollections": false +} diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 9e60dbcd9f..260b99df99 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,10 +10,10 @@ - - - - + + + + diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index b58151b3b4..30a48857a4 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -156,7 +156,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers Assert.Equal("Justice League Collection", item.CollectionName); // Images - Assert.Equal(6, result.RemoteImages.Count); + Assert.Equal(7, result.RemoteImages.Count); var posters = result.RemoteImages.Where(x => x.type == ImageType.Primary).ToList(); Assert.Single(posters); @@ -182,6 +182,10 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers Assert.Single(discArt); Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].url); + var backdrop = result.RemoteImages.Where(x => x.type == ImageType.Backdrop).ToList(); + Assert.Single(backdrop); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg", backdrop[0].url); + // Local Image - contains only one item depending on operating system Assert.Single(result.Images); Assert.Equal(_localImageFileMetadata.Name, result.Images[0].FileInfo.Name); diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo index b0c5e3c572..4e8c79dcaa 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo +++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo @@ -82,8 +82,8 @@ https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a0b913c233be.png https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-59dc595362ef1.png + https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg - https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg