diff --git a/.ci/azure-pipelines-compat.yml b/.ci/azure-pipelines-compat.yml index 762bbdcb2e..de60d2ccb1 100644 --- a/.ci/azure-pipelines-compat.yml +++ b/.ci/azure-pipelines-compat.yml @@ -23,7 +23,7 @@ jobs: NugetPackageName: ${{ Package.value.NugetPackageName }} AssemblyFileName: ${{ Package.value.AssemblyFileName }} maxParallel: 2 - dependsOn: MainBuild + dependsOn: Build steps: - checkout: none diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml index 09901b2a6a..d155624abb 100644 --- a/.ci/azure-pipelines-main.yml +++ b/.ci/azure-pipelines-main.yml @@ -4,15 +4,14 @@ parameters: DotNetSdkVersion: 3.1.100 jobs: - - job: MainBuild - displayName: Main Build + - job: Build + displayName: Build strategy: matrix: Release: BuildConfiguration: Release Debug: BuildConfiguration: Debug - maxParallel: 2 pool: vmImage: "${{ parameters.LinuxImage }}" steps: @@ -22,13 +21,13 @@ jobs: persistCredentials: true - task: CmdLine@2 - displayName: "Clone Web Client (Master, Release, or Tag)" + displayName: "Clone Web Branch" condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web" - task: CmdLine@2 - displayName: "Clone Web Client (PR)" + displayName: "Clone Web Target" condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest')) inputs: script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web" @@ -37,7 +36,7 @@ jobs: displayName: "Install Node" condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: - versionSpec: "10.x" + versionSpec: "12.x" - task: CmdLine@2 displayName: "Build Web Client" @@ -69,33 +68,33 @@ jobs: command: publish publishWebProjects: false projects: "${{ parameters.RestoreBuildProjects }}" - arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)" + arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)" zipAfterPublish: false - task: PublishPipelineArtifact@0 displayName: "Publish Artifact Naming" condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) inputs: - targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll" + targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll" artifactName: "Jellyfin.Naming" - task: PublishPipelineArtifact@0 displayName: "Publish Artifact Controller" condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) inputs: - targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll" + targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll" artifactName: "Jellyfin.Controller" - task: PublishPipelineArtifact@0 displayName: "Publish Artifact Model" condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) inputs: - targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll" + targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll" artifactName: "Jellyfin.Model" - task: PublishPipelineArtifact@0 displayName: "Publish Artifact Common" condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) inputs: - targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll" + targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll" artifactName: "Jellyfin.Common" diff --git a/.ci/azure-pipelines-windows.yml b/.ci/azure-pipelines-windows.yml deleted file mode 100644 index 32d1d1382d..0000000000 --- a/.ci/azure-pipelines-windows.yml +++ /dev/null @@ -1,82 +0,0 @@ -parameters: - WindowsImage: "windows-latest" - TestProjects: "tests/**/*Tests.csproj" - DotNetSdkVersion: 3.1.100 - -jobs: - - job: PublishWindows - displayName: Publish Windows - pool: - vmImage: ${{ parameters.WindowsImage }} - steps: - - checkout: self - clean: true - submodules: true - persistCredentials: true - - - task: CmdLine@2 - displayName: "Clone Web Client (Master, Release, or Tag)" - condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')) - inputs: - script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web" - - - task: CmdLine@2 - displayName: "Clone Web Client (PR)" - condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest')) - inputs: - script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web" - - - task: NodeTool@0 - displayName: "Install Node" - condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) - inputs: - versionSpec: "10.x" - - - task: CmdLine@2 - displayName: "Build Web Client" - condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) - inputs: - script: yarn install - workingDirectory: $(Agent.TempDirectory)/jellyfin-web - - - task: CopyFiles@2 - displayName: "Copy Web Client" - condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) - inputs: - sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist - contents: "**" - targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web - cleanTargetFolder: true - overWrite: true - flattenFolders: false - - - task: CmdLine@2 - displayName: "Clone UX Repository" - inputs: - script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux - - - task: PowerShell@2 - displayName: "Build NSIS Installer" - inputs: - targetType: "filePath" - filePath: ./deployment/windows/build-jellyfin.ps1 - arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory) - errorActionPreference: "stop" - workingDirectory: $(Build.SourcesDirectory) - - - task: CopyFiles@2 - displayName: "Copy NSIS Installer" - inputs: - sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ - contents: "jellyfin*.exe" - targetFolder: $(System.ArtifactsDirectory)/setup - cleanTargetFolder: true - overWrite: true - flattenFolders: true - - - task: PublishPipelineArtifact@0 - displayName: "Publish Artifact Setup" - condition: succeeded() - inputs: - targetPath: "$(build.artifactstagingdirectory)/setup" - artifactName: "Jellyfin Server Setup" diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index f79a85b210..3522cbf006 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -27,11 +27,6 @@ jobs: Windows: "windows-latest" macOS: "macos-latest" - - template: azure-pipelines-windows.yml - parameters: - WindowsImage: "windows-latest" - TestProjects: $(TestProjects) - - template: azure-pipelines-compat.yml parameters: Packages: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..d886c64871 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,13 @@ +--- +name: Feature Request +about: Request a new feature +title: '' +labels: feature-request +assignees: '' +--- + +**PLEASE DO NOT OPEN FEATURE REQUEST ISSUES ON GITHUB** + +**Feature requests should be opened on our dedicated [feature request](https://features.jellyfin.org/) hub so they can be appropriately discussed and prioritized.** + +However, if you are willing to contribute to the project by adding a new feature yourself, then please ensure that you first review our [documentation](https://docs.jellyfin.org/general/contributing/development.html) on contributing code. Once you have reviewed the documentation, feel free to come back here and open an issue here outlining your proposed approach so that it can be documented, tracked, and discussed by other team members. diff --git a/.gitignore b/.gitignore index 42243f01a8..523c45a7e6 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ ProgramData*/ CorePlugins*/ ProgramData-Server*/ ProgramData-UI*/ +MediaBrowser.WebDashboard/jellyfin-web/** ################# ## Visual Studio diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2a9779cd53..f195c125f1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,40 +1,133 @@ # Jellyfin Contributors - - [JoshuaBoniface](https://github.com/joshuaboniface) - - [nvllsvm](https://github.com/nvllsvm) - - [JustAMan](https://github.com/JustAMan) - - [dcrdev](https://github.com/dcrdev) - - [EraYaN](https://github.com/EraYaN) - - [flemse](https://github.com/flemse) + - [97carmine](https://github.com/97carmine) + - [Abbe98](https://github.com/Abbe98) + - [agrenott](https://github.com/agrenott) + - [AndreCarvalho](https://github.com/AndreCarvalho) + - [anthonylavado](https://github.com/anthonylavado) + - [Artiume](https://github.com/Artiume) + - [AThomsen](https://github.com/AThomsen) + - [bilde2910](https://github.com/bilde2910) - [bfayers](https://github.com/bfayers) - - [Bond_009](https://github.com/Bond-009) - - [AnthonyLavado](https://github.com/anthonylavado) - - [sparky8251](https://github.com/sparky8251) - - [LeoVerto](https://github.com/LeoVerto) - - [grafixeyehero](https://github.com/grafixeyehero) + - [BnMcG](https://github.com/BnMcG) + - [Bond-009](https://github.com/Bond-009) + - [brianjmurrell](https://github.com/brianjmurrell) + - [bugfixin](https://github.com/bugfixin) + - [chaosinnovator](https://github.com/chaosinnovator) + - [ckcr4lyf](https://github.com/ckcr4lyf) + - [crankdoofus](https://github.com/crankdoofus) + - [crobibero](https://github.com/crobibero) + - [cromefire](https://github.com/cromefire) + - [cryptobank](https://github.com/cryptobank) - [cvium](https://github.com/cvium) - - [wtayl0r](https://github.com/wtayl0r) - - [TtheCreator](https://github.com/Tthecreator) + - [dannymichel](https://github.com/dannymichel) + - [DaveChild](https://github.com/DaveChild) + - [dcrdev](https://github.com/dcrdev) + - [dhartung](https://github.com/dhartung) + - [dinki](https://github.com/dinki) - [dkanada](https://github.com/dkanada) - - [LogicalPhallacy](https://github.com/LogicalPhallacy/) - - [RazeLighter777](https://github.com/RazeLighter777) - - [WillWill56](https://github.com/WillWill56) - - [Liggy](https://github.com/Liggy) - - [fruhnow](https://github.com/fruhnow) - - [Lynxy](https://github.com/Lynxy) + - [dlahoti](https://github.com/dlahoti) + - [dmitrylyzo](https://github.com/dmitrylyzo) + - [DMouse10462](https://github.com/DMouse10462) + - [DrPandemic](https://github.com/DrPandemic) + - [EraYaN](https://github.com/EraYaN) + - [escabe](https://github.com/escabe) + - [excelite](https://github.com/excelite) - [fasheng](https://github.com/fasheng) - - [ploughpuff](https://github.com/ploughpuff) - - [pjeanjean](https://github.com/pjeanjean) - - [DrPandemic](https://github.com/drpandemic) - - [joern-h](https://github.com/joern-h) - - [Khinenw](https://github.com/HelloWorld017) + - [ferferga](https://github.com/ferferga) - [fhriley](https://github.com/fhriley) - - [nevado](https://github.com/nevado) - - [mark-monteiro](https://github.com/mark-monteiro) - - [ullmie02](https://github.com/ullmie02) + - [flemse](https://github.com/flemse) + - [Froghut](https://github.com/Froghut) + - [fruhnow](https://github.com/fruhnow) - [geilername](https://github.com/geilername) + - [gnattu](https://github.com/gnattu) + - [grafixeyehero](https://github.com/grafixeyehero) + - [h1nk](https://github.com/h1nk) + - [hawken93](https://github.com/hawken93) + - [HelloWorld017](https://github.com/HelloWorld017) + - [jftuga](https://github.com/jftuga) + - [joern-h](https://github.com/joern-h) + - [joshuaboniface](https://github.com/joshuaboniface) + - [JustAMan](https://github.com/JustAMan) + - [justinfenn](https://github.com/justinfenn) + - [KerryRJ](https://github.com/KerryRJ) + - [Larvitar](https://github.com/Larvitar) + - [LeoVerto](https://github.com/LeoVerto) + - [Liggy](https://github.com/Liggy) + - [LogicalPhallacy](https://github.com/LogicalPhallacy) + - [loli10K](https://github.com/loli10K) + - [lostmypillow](https://github.com/lostmypillow) + - [Lynxy](https://github.com/Lynxy) + - [ManfredRichthofen](https://github.com/ManfredRichthofen) + - [Marenz](https://github.com/Marenz) + - [marius-luca-87](https://github.com/marius-luca-87) + - [mark-monteiro](https://github.com/mark-monteiro) + - [Matt07211](https://github.com/Matt07211) + - [mcarlton00](https://github.com/mcarlton00) + - [mitchfizz05](https://github.com/mitchfizz05) + - [MrTimscampi](https://github.com/MrTimscampi) + - [n8225](https://github.com/n8225) + - [Narfinger](https://github.com/Narfinger) + - [NathanPickard](https://github.com/NathanPickard) + - [neilsb](https://github.com/neilsb) + - [nevado](https://github.com/nevado) + - [Nickbert7](https://github.com/Nickbert7) + - [nvllsvm](https://github.com/nvllsvm) + - [nyanmisaka](https://github.com/nyanmisaka) + - [oddstr13](https://github.com/oddstr13) + - [petermcneil](https://github.com/petermcneil) + - [Phlogi](https://github.com/Phlogi) + - [pjeanjean](https://github.com/pjeanjean) + - [ploughpuff](https://github.com/ploughpuff) - [pR0Ps](https://github.com/pR0Ps) - + - [PrplHaz4](https://github.com/PrplHaz4) + - [RazeLighter777](https://github.com/RazeLighter777) + - [redSpoutnik](https://github.com/redSpoutnik) + - [ringmatter](https://github.com/ringmatter) + - [ryan-hartzell](https://github.com/ryan-hartzell) + - [s0urcelab](https://github.com/s0urcelab) + - [sachk](https://github.com/sachk) + - [sammyrc34](https://github.com/sammyrc34) + - [samuel9554](https://github.com/samuel9554) + - [scheidleon](https://github.com/scheidleon) + - [sebPomme](https://github.com/sebPomme) + - [SegiH](https://github.com/SegiH) + - [SenorSmartyPants](https://github.com/SenorSmartyPants) + - [shemanaev](https://github.com/shemanaev) + - [skaro13](https://github.com/skaro13) + - [sl1288](https://github.com/sl1288) + - [sorinyo2004](https://github.com/sorinyo2004) + - [sparky8251](https://github.com/sparky8251) + - [stanionascu](https://github.com/stanionascu) + - [stevehayles](https://github.com/stevehayles) + - [SuperSandro2000](https://github.com/SuperSandro2000) + - [tbraeutigam](https://github.com/tbraeutigam) + - [teacupx](https://github.com/teacupx) + - [Terror-Gene](https://github.com/Terror-Gene) + - [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu) + - [ThibaultNocchi](https://github.com/ThibaultNocchi) + - [thornbill](https://github.com/thornbill) + - [ThreeFive-O](https://github.com/ThreeFive-O) + - [TrisMcC](https://github.com/TrisMcC) + - [trumblejoe](https://github.com/trumblejoe) + - [TtheCreator](https://github.com/TtheCreator) + - [twinkybot](https://github.com/twinkybot) + - [Ullmie02](https://github.com/Ullmie02) + - [Unhelpful](https://github.com/Unhelpful) + - [viaregio](https://github.com/viaregio) + - [vitorsemeano](https://github.com/vitorsemeano) + - [voodoos](https://github.com/voodoos) + - [whooo](https://github.com/whooo) + - [WiiPlayer2](https://github.com/WiiPlayer2) + - [WillWill56](https://github.com/WillWill56) + - [wtayl0r](https://github.com/wtayl0r) + - [Wuerfelbecher](https://github.com/Wuerfelbecher) + - [Wunax](https://github.com/Wunax) + - [WWWesten](https://github.com/WWWesten) + - [WX9yMOXWId](https://github.com/WX9yMOXWId) + - [xosdy](https://github.com/xosdy) + - [XVicarious](https://github.com/XVicarious) + - [YouKnowBlom](https://github.com/YouKnowBlom) # Emby Contributors diff --git a/Dockerfile b/Dockerfile index 1029a4cee1..caac7500a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,11 +3,10 @@ ARG FFMPEG_VERSION=latest FROM node:alpine as web-builder ARG JELLYFIN_WEB_VERSION=master -RUN apk add curl git \ +RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ && yarn install \ - && yarn build \ && mv dist /dist FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder @@ -21,6 +20,13 @@ RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release -- FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg FROM debian:buster-slim +# https://askubuntu.com/questions/972516/debian-frontend-environment-variable +ARG DEBIAN_FRONTEND="noninteractive" +# http://stackoverflow.com/questions/48162574/ddg#49462622 +ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn +# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) +ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" + COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web @@ -31,16 +37,28 @@ COPY --from=web-builder /dist /jellyfin/jellyfin-web # mesa-va-drivers: needed for VAAPI RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y \ - libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl ca-certificates \ - && apt-get clean autoclean \ - && apt-get autoremove \ + libfontconfig1 \ + libgomp1 \ + libva-drm2 \ + mesa-va-drivers \ + openssl \ + ca-certificates \ + vainfo \ + i965-va-driver \ + locales \ + && apt-get clean autoclean -y\ + && apt-get autoremove -y\ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /cache /config /media \ && chmod 777 /cache /config /media \ && ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \ - && ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin + && ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin \ + && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +ENV LC_ALL en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en EXPOSE 8096 VOLUME /cache /config /media diff --git a/Dockerfile.arm b/Dockerfile.arm index 5847de9189..c5189d6aa3 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -7,11 +7,10 @@ ARG DOTNET_VERSION=3.1 FROM node:alpine as web-builder ARG JELLYFIN_WEB_VERSION=master -RUN apk add curl git \ +RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ && yarn install \ - && yarn build \ && mv dist /dist @@ -27,21 +26,52 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" FROM multiarch/qemu-user-static:x86_64-arm as qemu FROM arm32v7/debian:buster-slim + +# https://askubuntu.com/questions/972516/debian-frontend-environment-variable +ARG DEBIAN_FRONTEND="noninteractive" +# http://stackoverflow.com/questions/48162574/ddg#49462622 +ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn +# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) +ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" + COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin RUN apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ - libssl-dev ca-certificates \ + && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \ + curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ + curl -s https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \ + echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \ + echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \ + apt-get update && \ + apt-get install --no-install-recommends --no-install-suggests -y \ + jellyfin-ffmpeg \ + libssl-dev \ + libfontconfig1 \ + libfreetype6 \ + libomxil-bellagio0 \ + libomxil-bellagio-bin \ + libraspberrypi0 \ + vainfo \ + libva2 \ + locales \ + && apt-get remove curl gnupg -y \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /cache /config /media \ - && chmod 777 /cache /config /media + && chmod 777 /cache /config /media \ + && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen + COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +ENV LC_ALL en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en EXPOSE 8096 VOLUME /cache /config /media ENTRYPOINT ["./jellyfin/jellyfin", \ "--datadir", "/config", \ "--cachedir", "/cache", \ - "--ffmpeg", "/usr/bin/ffmpeg"] + "--ffmpeg", "/usr/lib/jellyfin-ffmpeg"] diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index a9f6c50d9f..1a691b5727 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -7,11 +7,10 @@ ARG DOTNET_VERSION=3.1 FROM node:alpine as web-builder ARG JELLYFIN_WEB_VERSION=master -RUN apk add curl git \ +RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ && yarn install \ - && yarn build \ && mv dist /dist @@ -26,17 +25,38 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu FROM arm64v8/debian:buster-slim + +# https://askubuntu.com/questions/972516/debian-frontend-environment-variable +ARG DEBIAN_FRONTEND="noninteractive" +# http://stackoverflow.com/questions/48162574/ddg#49462622 +ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn +# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) +ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" + COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin -RUN apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ - libssl-dev ca-certificates \ +RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \ + ffmpeg \ + libssl-dev \ + ca-certificates \ + libfontconfig1 \ + libfreetype6 \ + libomxil-bellagio0 \ + libomxil-bellagio-bin \ + locales \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /cache /config /media \ - && chmod 777 /cache /config /media + && chmod 777 /cache /config /media \ + && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen + COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +ENV LC_ALL en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en EXPOSE 8096 VOLUME /cache /config /media diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs index 157b2e197f..c0f9cf4107 100644 --- a/DvdLib/Ifo/Dvd.cs +++ b/DvdLib/Ifo/Dvd.cs @@ -33,7 +33,7 @@ namespace DvdLib.Ifo continue; } - var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries); + var nums = ifo.Name.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber)) { ReadVTS(ifoNumber, ifo.FullName); diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs index 4d9933a0cb..7fba2184a7 100644 --- a/Emby.Dlna/Api/DlnaServerService.cs +++ b/Emby.Dlna/Api/DlnaServerService.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; using System.Threading.Tasks; @@ -152,6 +152,7 @@ namespace Emby.Dlna.Api return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult(new MemoryStream(bytes))); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetContentDirectory request) { var xml = ContentDirectory.GetServiceXml(); @@ -159,6 +160,7 @@ namespace Emby.Dlna.Api return _resultFactory.GetResult(Request, xml, XMLContentType); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetMediaReceiverRegistrar request) { var xml = MediaReceiverRegistrar.GetServiceXml(); @@ -166,6 +168,7 @@ namespace Emby.Dlna.Api return _resultFactory.GetResult(Request, xml, XMLContentType); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetConnnectionManager request) { var xml = ConnectionManager.GetServiceXml(); @@ -314,31 +317,37 @@ namespace Emby.Dlna.Api return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream)); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Subscribe(ProcessContentDirectoryEventRequest request) { return ProcessEventRequest(ContentDirectory); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Subscribe(ProcessConnectionManagerEventRequest request) { return ProcessEventRequest(ConnectionManager); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request) { return ProcessEventRequest(MediaReceiverRegistrar); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Unsubscribe(ProcessContentDirectoryEventRequest request) { return ProcessEventRequest(ContentDirectory); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Unsubscribe(ProcessConnectionManagerEventRequest request) { return ProcessEventRequest(ConnectionManager); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request) { return ProcessEventRequest(MediaReceiverRegistrar); diff --git a/Emby.Dlna/Api/DlnaService.cs b/Emby.Dlna/Api/DlnaService.cs index f10695541d..5f984bb335 100644 --- a/Emby.Dlna/Api/DlnaService.cs +++ b/Emby.Dlna/Api/DlnaService.cs @@ -1,6 +1,6 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 +using System.Diagnostics.CodeAnalysis; using System.Linq; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Net; @@ -53,6 +53,7 @@ namespace Emby.Dlna.Api _dlnaManager = dlnaManager; } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetProfileInfos request) { return _dlnaManager.GetProfileInfos().ToArray(); @@ -63,6 +64,7 @@ namespace Emby.Dlna.Api return _dlnaManager.GetProfile(request.Id); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetDefaultProfile request) { return _dlnaManager.GetDefaultProfile(); diff --git a/Emby.Dlna/Common/Argument.cs b/Emby.Dlna/Common/Argument.cs index c6ab9959e0..f375e6049c 100644 --- a/Emby.Dlna/Common/Argument.cs +++ b/Emby.Dlna/Common/Argument.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Dlna.Common { diff --git a/Emby.Dlna/Common/DeviceIcon.cs b/Emby.Dlna/Common/DeviceIcon.cs index 49d19992d8..c3f7fa8aaa 100644 --- a/Emby.Dlna/Common/DeviceIcon.cs +++ b/Emby.Dlna/Common/DeviceIcon.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Globalization; diff --git a/Emby.Dlna/Common/DeviceService.cs b/Emby.Dlna/Common/DeviceService.cs index 9947ec6b9a..44c0a0412a 100644 --- a/Emby.Dlna/Common/DeviceService.cs +++ b/Emby.Dlna/Common/DeviceService.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Dlna.Common { diff --git a/Emby.Dlna/Common/ServiceAction.cs b/Emby.Dlna/Common/ServiceAction.cs index 15c4be8092..db4f270633 100644 --- a/Emby.Dlna/Common/ServiceAction.cs +++ b/Emby.Dlna/Common/ServiceAction.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; diff --git a/Emby.Dlna/Common/StateVariable.cs b/Emby.Dlna/Common/StateVariable.cs index bade28e4bc..a2c2bf5ddc 100644 --- a/Emby.Dlna/Common/StateVariable.cs +++ b/Emby.Dlna/Common/StateVariable.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs index 84587a7cee..6dd9a445a8 100644 --- a/Emby.Dlna/Configuration/DlnaOptions.cs +++ b/Emby.Dlna/Configuration/DlnaOptions.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Dlna.Configuration { diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs index f8125c12c3..e224d10bd3 100644 --- a/Emby.Dlna/ConfigurationExtension.cs +++ b/Emby.Dlna/ConfigurationExtension.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using Emby.Dlna.Configuration; diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index 365249c54a..e32cc11bfa 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Threading.Tasks; using Emby.Dlna.Service; @@ -16,7 +15,11 @@ namespace Emby.Dlna.ConnectionManager private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient) + public ConnectionManager( + IDlnaManager dlna, + IServerConfigurationManager config, + ILogger logger, + IHttpClient httpClient) : base(logger, httpClient) { _dlna = dlna; diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs index c8c97c79c0..b31d437c3b 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using Emby.Dlna.Common; diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs index b390515b87..d4cc653942 100644 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs b/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs index 019a0f80f4..b853e7eab6 100644 --- a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs +++ b/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using Emby.Dlna.Common; diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index 523430e430..64cd308a2a 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Threading.Tasks; @@ -38,7 +37,7 @@ namespace Emby.Dlna.ContentDirectory ILibraryManager libraryManager, IServerConfigurationManager config, IUserManager userManager, - ILogger logger, + ILogger logger, IHttpClient httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs index 282a47c738..6db4d7cb66 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using Emby.Dlna.Common; diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 1278b367c7..28888f031a 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -79,7 +78,18 @@ namespace Emby.Dlna.ContentDirectory _profile = profile; _config = config; - _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, mediaEncoder); + _didlBuilder = new DidlBuilder( + profile, + user, + imageProcessor, + serverAddress, + accessToken, + userDataManager, + localization, + mediaSourceManager, + Logger, + mediaEncoder, + libraryManager); } /// @@ -154,7 +164,7 @@ namespace Emby.Dlna.ContentDirectory { var id = sparams["ObjectID"]; - var serverItem = GetItemFromObjectId(id, _user); + var serverItem = GetItemFromObjectId(id); var item = serverItem.Item; @@ -277,7 +287,7 @@ namespace Emby.Dlna.ContentDirectory DidlBuilder.WriteXmlRootAttributes(_profile, writer); - var serverItem = GetItemFromObjectId(id, _user); + var serverItem = GetItemFromObjectId(id); var item = serverItem.Item; @@ -294,7 +304,7 @@ namespace Emby.Dlna.ContentDirectory else { var dlnaOptions = _config.GetDlnaConfiguration(); - _didlBuilder.WriteItemElement(dlnaOptions, writer, item, _user, null, null, deviceId, filter); + _didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter); } provided++; @@ -321,7 +331,7 @@ namespace Emby.Dlna.ContentDirectory } else { - _didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, _user, item, serverItem.StubType, deviceId, filter); + _didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter); } } } @@ -388,7 +398,7 @@ namespace Emby.Dlna.ContentDirectory DidlBuilder.WriteXmlRootAttributes(_profile, writer); - var serverItem = GetItemFromObjectId(sparams["ContainerID"], _user); + var serverItem = GetItemFromObjectId(sparams["ContainerID"]); var item = serverItem.Item; @@ -407,7 +417,7 @@ namespace Emby.Dlna.ContentDirectory } else { - _didlBuilder.WriteItemElement(dlnaOptions, writer, i, _user, item, serverItem.StubType, deviceId, filter); + _didlBuilder.WriteItemElement(writer, i, _user, item, serverItem.StubType, deviceId, filter); } } @@ -513,11 +523,11 @@ namespace Emby.Dlna.ContentDirectory } else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) { - return GetFolders(item, user, stubType, sort, startIndex, limit); + return GetFolders(user, startIndex, limit); } else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) { - return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit); + return GetLiveTvChannels(user, sort, startIndex, limit); } } @@ -548,7 +558,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(queryResult); } - private QueryResult GetLiveTvChannels(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -580,7 +590,7 @@ namespace Emby.Dlna.ContentDirectory if (stubType.HasValue && stubType.Value == StubType.Playlists) { - return GetMusicPlaylists(item, user, query); + return GetMusicPlaylists(user, query); } if (stubType.HasValue && stubType.Value == StubType.Albums) @@ -708,7 +718,7 @@ namespace Emby.Dlna.ContentDirectory if (stubType.HasValue && stubType.Value == StubType.Collections) { - return GetMovieCollections(item, user, query); + return GetMovieCollections(user, query); } if (stubType.HasValue && stubType.Value == StubType.Favorites) @@ -721,46 +731,42 @@ namespace Emby.Dlna.ContentDirectory return GetGenres(item, user, query); } - var list = new List(); - - list.Add(new ServerItem(item) + var array = new ServerItem[] { - StubType = StubType.ContinueWatching - }); - - list.Add(new ServerItem(item) - { - StubType = StubType.Latest - }); - - list.Add(new ServerItem(item) - { - StubType = StubType.Movies - }); - - list.Add(new ServerItem(item) - { - StubType = StubType.Collections - }); - - list.Add(new ServerItem(item) - { - StubType = StubType.Favorites - }); - - list.Add(new ServerItem(item) - { - StubType = StubType.Genres - }); + new ServerItem(item) + { + StubType = StubType.ContinueWatching + }, + new ServerItem(item) + { + StubType = StubType.Latest + }, + new ServerItem(item) + { + StubType = StubType.Movies + }, + new ServerItem(item) + { + StubType = StubType.Collections + }, + new ServerItem(item) + { + StubType = StubType.Favorites + }, + new ServerItem(item) + { + StubType = StubType.Genres + } + }; return new QueryResult { - Items = list, - TotalRecordCount = list.Count + Items = array, + TotalRecordCount = array.Length }; } - private QueryResult GetFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetFolders(User user, int? startIndex, int? limit) { var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true) .OrderBy(i => i.SortName) @@ -793,7 +799,7 @@ namespace Emby.Dlna.ContentDirectory if (stubType.HasValue && stubType.Value == StubType.NextUp) { - return GetNextUp(item, user, query); + return GetNextUp(item, query); } if (stubType.HasValue && stubType.Value == StubType.Latest) @@ -911,7 +917,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMovieCollections(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMovieCollections(User user, InternalItemsQuery query) { query.Recursive = true; //query.Parent = parent; @@ -1106,7 +1112,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicPlaylists(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMusicPlaylists(User user, InternalItemsQuery query) { query.Parent = null; query.IncludeItemTypes = new[] { typeof(Playlist).Name }; @@ -1135,7 +1141,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(items); } - private QueryResult GetNextUp(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetNextUp(BaseItem parent, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1290,15 +1296,15 @@ namespace Emby.Dlna.ContentDirectory return result; } - private ServerItem GetItemFromObjectId(string id, User user) + private ServerItem GetItemFromObjectId(string id) { return DidlBuilder.IsIdRoot(id) ? new ServerItem(_libraryManager.GetUserRootFolder()) - : ParseItemId(id, user); + : ParseItemId(id); } - private ServerItem ParseItemId(string id, User user) + private ServerItem ParseItemId(string id) { StubType? stubType = null; diff --git a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs index a385a74cfb..921b14e394 100644 --- a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs +++ b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using Emby.Dlna.Common; diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs index 97ad41c834..a6e03b7e6a 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.IO; using Microsoft.AspNetCore.Http; diff --git a/Emby.Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs index 0215a5e387..140ef9b463 100644 --- a/Emby.Dlna/ControlResponse.cs +++ b/Emby.Dlna/ControlResponse.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 145639ab0e..88cc00e30a 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; @@ -46,6 +45,7 @@ namespace Emby.Dlna.Didl private readonly IMediaSourceManager _mediaSourceManager; private readonly ILogger _logger; private readonly IMediaEncoder _mediaEncoder; + private readonly ILibraryManager _libraryManager; public DidlBuilder( DeviceProfile profile, @@ -57,7 +57,8 @@ namespace Emby.Dlna.Didl ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, - IMediaEncoder mediaEncoder) + IMediaEncoder mediaEncoder, + ILibraryManager libraryManager) { _profile = profile; _user = user; @@ -69,6 +70,7 @@ namespace Emby.Dlna.Didl _mediaSourceManager = mediaSourceManager; _logger = logger; _mediaEncoder = mediaEncoder; + _libraryManager = libraryManager; } public static string NormalizeDlnaMediaUrl(string url) @@ -76,7 +78,7 @@ namespace Emby.Dlna.Didl return url + "&dlnaheaders=true"; } - public string GetItemDidl(DlnaOptions options, BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) + public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) { var settings = new XmlWriterSettings { @@ -101,7 +103,7 @@ namespace Emby.Dlna.Didl WriteXmlRootAttributes(_profile, writer); - WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo); + WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo); writer.WriteFullEndElement(); //writer.WriteEndDocument(); @@ -128,7 +130,6 @@ namespace Emby.Dlna.Didl } public void WriteItemElement( - DlnaOptions options, XmlWriter writer, BaseItem item, User user, @@ -165,25 +166,23 @@ namespace Emby.Dlna.Didl // refID? // storeAttribute(itemNode, object, ClassProperties.REF_ID, false); - var hasMediaSources = item as IHasMediaSources; - - if (hasMediaSources != null) + if (item is IHasMediaSources) { if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { - AddAudioResource(options, writer, item, deviceId, filter, streamInfo); + AddAudioResource(writer, item, deviceId, filter, streamInfo); } else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { - AddVideoResource(options, writer, item, deviceId, filter, streamInfo); + AddVideoResource(writer, item, deviceId, filter, streamInfo); } } - AddCover(item, context, null, writer); + AddCover(item, null, writer); writer.WriteFullEndElement(); } - private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null) + private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null) { if (streamInfo == null) { @@ -227,7 +226,7 @@ namespace Emby.Dlna.Didl foreach (var contentFeature in contentFeatureList) { - AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo); + AddVideoResource(writer, filter, contentFeature, streamInfo); } var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken); @@ -284,7 +283,10 @@ namespace Emby.Dlna.Didl else { writer.WriteStartElement(string.Empty, "res", NS_DIDL); - var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant()); + var protocolInfo = string.Format( + CultureInfo.InvariantCulture, + "http-get:*:text/{0}:*", + info.Format.ToLowerInvariant()); writer.WriteAttributeString("protocolInfo", protocolInfo); writer.WriteString(info.Url); @@ -294,7 +296,7 @@ namespace Emby.Dlna.Didl return true; } - private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo) + private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo) { writer.WriteStartElement(string.Empty, "res", NS_DIDL); @@ -336,7 +338,13 @@ namespace Emby.Dlna.Didl { if (targetWidth.HasValue && targetHeight.HasValue) { - writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value)); + writer.WriteAttributeString( + "resolution", + string.Format( + CultureInfo.InvariantCulture, + "{0}x{1}", + targetWidth.Value, + targetHeight.Value)); } } @@ -370,17 +378,19 @@ namespace Emby.Dlna.Didl streamInfo.TargetVideoCodecTag, streamInfo.IsTargetAVC); - var filename = url.Substring(0, url.IndexOf('?')); + var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal)); var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType) ? MimeTypes.GetMimeType(filename) : mediaProfile.MimeType; - writer.WriteAttributeString("protocolInfo", string.Format( - "http-get:*:{0}:{1}", - mimeType, - contentFeatures - )); + writer.WriteAttributeString( + "protocolInfo", + string.Format( + CultureInfo.InvariantCulture, + "http-get:*:{0}:{1}", + mimeType, + contentFeatures)); writer.WriteString(url); @@ -393,24 +403,24 @@ namespace Emby.Dlna.Didl { switch (itemStubType.Value) { - case StubType.Latest: return _localization.GetLocalizedString("Latest"); - case StubType.Playlists: return _localization.GetLocalizedString("Playlists"); - case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists"); - case StubType.Albums: return _localization.GetLocalizedString("Albums"); - case StubType.Artists: return _localization.GetLocalizedString("Artists"); - case StubType.Songs: return _localization.GetLocalizedString("Songs"); - case StubType.Genres: return _localization.GetLocalizedString("Genres"); - case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums"); - case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists"); - case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs"); + case StubType.Latest: return _localization.GetLocalizedString("Latest"); + case StubType.Playlists: return _localization.GetLocalizedString("Playlists"); + case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists"); + case StubType.Albums: return _localization.GetLocalizedString("Albums"); + case StubType.Artists: return _localization.GetLocalizedString("Artists"); + case StubType.Songs: return _localization.GetLocalizedString("Songs"); + case StubType.Genres: return _localization.GetLocalizedString("Genres"); + case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums"); + case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists"); + case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs"); case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching"); - case StubType.Movies: return _localization.GetLocalizedString("Movies"); - case StubType.Collections: return _localization.GetLocalizedString("Collections"); - case StubType.Favorites: return _localization.GetLocalizedString("Favorites"); - case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp"); - case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); + case StubType.Movies: return _localization.GetLocalizedString("Movies"); + case StubType.Collections: return _localization.GetLocalizedString("Collections"); + case StubType.Favorites: return _localization.GetLocalizedString("Favorites"); + case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp"); + case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); - case StubType.Series: return _localization.GetLocalizedString("Shows"); + case StubType.Series: return _localization.GetLocalizedString("Shows"); default: break; } } @@ -421,7 +431,10 @@ namespace Emby.Dlna.Didl if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0 && season.IndexNumber.HasValue && season.IndexNumber.Value != 0) { - return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name); + return string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("ValueSpecialEpisodeName"), + item.Name); } if (item.IndexNumber.HasValue) @@ -436,11 +449,34 @@ namespace Emby.Dlna.Didl return number + " - " + item.Name; } } + else if (item is Episode ep) + { + var parent = ep.GetParent(); + var name = parent.Name + " - "; + + if (ep.ParentIndexNumber.HasValue) + { + name += "S" + ep.ParentIndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); + } + else if (!item.IndexNumber.HasValue) + { + return name + " - " + item.Name; + } + + name += "E" + ep.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); + if (ep.IndexNumberEnd.HasValue) + { + name += "-" + ep.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture); + } + + name += " - " + item.Name; + return name; + } return item.Name; } - private void AddAudioResource(DlnaOptions options, XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null) + private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null) { writer.WriteStartElement(string.Empty, "res", NS_DIDL); @@ -506,7 +542,7 @@ namespace Emby.Dlna.Didl targetSampleRate, targetAudioBitDepth); - var filename = url.Substring(0, url.IndexOf('?')); + var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal)); var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType) ? MimeTypes.GetMimeType(filename) @@ -522,11 +558,13 @@ namespace Emby.Dlna.Didl streamInfo.RunTimeTicks ?? 0, streamInfo.TranscodeSeekInfo); - writer.WriteAttributeString("protocolInfo", string.Format( - "http-get:*:{0}:{1}", - mimeType, - contentFeatures - )); + writer.WriteAttributeString( + "protocolInfo", + string.Format( + CultureInfo.InvariantCulture, + "http-get:*:{0}:{1}", + mimeType, + contentFeatures)); writer.WriteString(url); @@ -549,7 +587,7 @@ namespace Emby.Dlna.Didl var clientId = GetClientId(folder, stubType); - if (string.Equals(requestedId, "0")) + if (string.Equals(requestedId, "0", StringComparison.Ordinal)) { writer.WriteAttributeString("id", "0"); writer.WriteAttributeString("parentID", "-1"); @@ -578,7 +616,7 @@ namespace Emby.Dlna.Didl AddGeneralProperties(folder, stubType, context, writer, filter); - AddCover(folder, context, stubType, writer); + AddCover(folder, stubType, writer); writer.WriteFullEndElement(); } @@ -611,7 +649,10 @@ namespace Emby.Dlna.Didl if (playbackPositionTicks > 0) { - var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture)); + var elementValue = string.Format( + CultureInfo.InvariantCulture, + "BM={0}", + Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds)); AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value); } } @@ -764,37 +805,36 @@ namespace Emby.Dlna.Didl private void AddPeople(BaseItem item, XmlWriter writer) { - //var types = new[] - //{ - // PersonType.Director, - // PersonType.Writer, - // PersonType.Producer, - // PersonType.Composer, - // "Creator" - //}; + if (!item.SupportsPeople) + { + return; + } - //var people = _libraryManager.GetPeople(item); + var types = new[] + { + PersonType.Director, + PersonType.Writer, + PersonType.Producer, + PersonType.Composer, + "creator" + }; - //var index = 0; + // Seeing some LG models locking up due content with large lists of people + // The actual issue might just be due to processing a more metadata than it can handle + var people = _libraryManager.GetPeople( + new InternalPeopleQuery + { + ItemId = item.Id, + Limit = 6 + }); - //// Seeing some LG models locking up due content with large lists of people - //// The actual issue might just be due to processing a more metadata than it can handle - //var limit = 6; + foreach (var actor in people) + { + var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) + ?? PersonType.Actor; - //foreach (var actor in people) - //{ - // var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) - // ?? PersonType.Actor; - - // AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP); - - // index++; - - // if (index >= limit) - // { - // break; - // } - //} + AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP); + } } private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter) @@ -871,7 +911,7 @@ namespace Emby.Dlna.Didl } } - private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer) + private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer) { ImageDownloadInfo imageInfo = GetImageInfo(item); @@ -916,17 +956,8 @@ namespace Emby.Dlna.Didl } - private void AddEmbeddedImageAsCover(string name, XmlWriter writer) - { - writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP); - writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn); - writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg"); - writer.WriteFullEndElement(); - - writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg"); - } - - private void AddImageResElement(BaseItem item, + private void AddImageResElement( + BaseItem item, XmlWriter writer, int maxWidth, int maxHeight, @@ -952,13 +983,17 @@ namespace Emby.Dlna.Didl var contentFeatures = new ContentFeatureBuilder(_profile) .BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn); - writer.WriteAttributeString("protocolInfo", string.Format( - "http-get:*:{0}:{1}", - MimeTypes.GetMimeType("file." + format), - contentFeatures - )); + writer.WriteAttributeString( + "protocolInfo", + string.Format( + CultureInfo.InvariantCulture, + "http-get:*:{0}:{1}", + MimeTypes.GetMimeType("file." + format), + contentFeatures)); - writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width, height)); + writer.WriteAttributeString( + "resolution", + string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height)); writer.WriteString(albumartUrlInfo.Url); @@ -1097,7 +1132,9 @@ namespace Emby.Dlna.Didl private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format) { - var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0", + var url = string.Format( + CultureInfo.InvariantCulture, + "{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0", _serverAddress, info.ItemId.ToString("N", CultureInfo.InvariantCulture), info.Type, diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs index 792d79770d..412259e904 100644 --- a/Emby.Dlna/Didl/Filter.cs +++ b/Emby.Dlna/Didl/Filter.cs @@ -1,8 +1,6 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; -using MediaBrowser.Model.Extensions; namespace Emby.Dlna.Didl { diff --git a/Emby.Dlna/Didl/StringWriterWithEncoding.cs b/Emby.Dlna/Didl/StringWriterWithEncoding.cs index edc2588995..896fe992bf 100644 --- a/Emby.Dlna/Didl/StringWriterWithEncoding.cs +++ b/Emby.Dlna/Didl/StringWriterWithEncoding.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.IO; @@ -54,6 +53,6 @@ namespace Emby.Dlna.Didl _encoding = encoding; } - public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding; + public override Encoding Encoding => _encoding ?? base.Encoding; } } diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index c5b9e5fbe4..10f881fe76 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Dlna/EventSubscriptionResponse.cs b/Emby.Dlna/EventSubscriptionResponse.cs index f90d273c4b..fd18343e62 100644 --- a/Emby.Dlna/EventSubscriptionResponse.cs +++ b/Emby.Dlna/EventSubscriptionResponse.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs index 7881898803..efbb53b644 100644 --- a/Emby.Dlna/Eventing/EventManager.cs +++ b/Emby.Dlna/Eventing/EventManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Concurrent; diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs index 108ab48303..51eaee9d77 100644 --- a/Emby.Dlna/Eventing/EventSubscription.cs +++ b/Emby.Dlna/Eventing/EventSubscription.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; diff --git a/Emby.Dlna/IConnectionManager.cs b/Emby.Dlna/IConnectionManager.cs index 01fb869f57..7b4a33a98c 100644 --- a/Emby.Dlna/IConnectionManager.cs +++ b/Emby.Dlna/IConnectionManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Dlna { diff --git a/Emby.Dlna/IContentDirectory.cs b/Emby.Dlna/IContentDirectory.cs index a28ad2b9c8..83ef09c665 100644 --- a/Emby.Dlna/IContentDirectory.cs +++ b/Emby.Dlna/IContentDirectory.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Dlna { diff --git a/Emby.Dlna/IEventManager.cs b/Emby.Dlna/IEventManager.cs index d0960aa16c..2872033892 100644 --- a/Emby.Dlna/IEventManager.cs +++ b/Emby.Dlna/IEventManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Dlna { diff --git a/Emby.Dlna/IMediaReceiverRegistrar.cs b/Emby.Dlna/IMediaReceiverRegistrar.cs index d2aaa8f553..b0376b6a99 100644 --- a/Emby.Dlna/IMediaReceiverRegistrar.cs +++ b/Emby.Dlna/IMediaReceiverRegistrar.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Dlna { diff --git a/Emby.Dlna/IUpnpService.cs b/Emby.Dlna/IUpnpService.cs index 289e2df78c..9e78595675 100644 --- a/Emby.Dlna/IUpnpService.cs +++ b/Emby.Dlna/IUpnpService.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Threading.Tasks; diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 1ee4151e4b..c5d60b2a05 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,9 +1,8 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; -using System.Net.Sockets; using System.Globalization; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; @@ -27,7 +26,7 @@ using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; using Rssdp; using Rssdp.Infrastructure; -using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Dlna.Main { @@ -59,7 +58,9 @@ namespace Emby.Dlna.Main private ISsdpCommunicationsServer _communicationsServer; internal IContentDirectory ContentDirectory { get; private set; } + internal IConnectionManager ConnectionManager { get; private set; } + internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } public static DlnaEntryPoint Current; @@ -107,7 +108,7 @@ namespace Emby.Dlna.Main libraryManager, config, userManager, - _logger, + loggerFactory.CreateLogger(), httpClient, localizationManager, mediaSourceManager, @@ -115,9 +116,16 @@ namespace Emby.Dlna.Main mediaEncoder, tvSeriesManager); - ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient); + ConnectionManager = new ConnectionManager.ConnectionManager( + dlnaManager, + config, + loggerFactory.CreateLogger(), + httpClient); - MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config); + MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar( + loggerFactory.CreateLogger(), + httpClient, + config); Current = this; } @@ -254,8 +262,8 @@ namespace Emby.Dlna.Main { if (address.AddressFamily == AddressFamily.InterNetworkV6) { - // Not support IPv6 right now - continue; + // Not supporting IPv6 right now + continue; } var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 815aac5c7c..8bf0cd961b 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index e2d48bc01f..64dfc840a8 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Threading.Tasks; using Emby.Dlna.Service; @@ -13,7 +12,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar { private readonly IServerConfigurationManager _config; - public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config) + public MediaReceiverRegistrar( + ILogger logger, + IHttpClient httpClient, + IServerConfigurationManager config) : base(logger, httpClient) { _config = config; diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs index 465b08f58e..8497025461 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using Emby.Dlna.Common; diff --git a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs index 3e8b2dbd88..13545c6894 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using Emby.Dlna.Common; diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 61db264a2f..6abc3a82c3 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -347,7 +346,12 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - return new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)); + return new SsdpHttpClient(_httpClient).SendCommandAsync( + Properties.BaseUrl, + service, + command.Name, + avCommands.BuildPost(command, service.ServiceType, 1), + cancellationToken: cancellationToken); } public async Task SetPlay(CancellationToken cancellationToken) @@ -516,8 +520,12 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true) - .ConfigureAwait(false); + var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + Properties.BaseUrl, + service, + command.Name, + rendererCommands.BuildPost(command, service.ServiceType), + cancellationToken: cancellationToken).ConfigureAwait(false); if (result == null || result.Document == null) { @@ -562,8 +570,12 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true) - .ConfigureAwait(false); + var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + Properties.BaseUrl, + service, + command.Name, + rendererCommands.BuildPost(command, service.ServiceType), + cancellationToken: cancellationToken).ConfigureAwait(false); if (result == null || result.Document == null) return; @@ -589,8 +601,12 @@ namespace Emby.Dlna.PlayTo return null; } - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false) - .ConfigureAwait(false); + var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + Properties.BaseUrl, + service, + command.Name, + avCommands.BuildPost(command, service.ServiceType), + cancellationToken: cancellationToken).ConfigureAwait(false); if (result == null || result.Document == null) { @@ -600,7 +616,7 @@ namespace Emby.Dlna.PlayTo var transportState = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null); - var transportStateValue = transportState == null ? null : transportState.Value; + var transportStateValue = transportState?.Value; if (transportStateValue != null && Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state)) @@ -627,8 +643,12 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false) - .ConfigureAwait(false); + var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + Properties.BaseUrl, + service, + command.Name, + rendererCommands.BuildPost(command, service.ServiceType), + cancellationToken: cancellationToken).ConfigureAwait(false); if (result == null || result.Document == null) { @@ -690,8 +710,12 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false) - .ConfigureAwait(false); + var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + Properties.BaseUrl, + service, + command.Name, + rendererCommands.BuildPost(command, service.ServiceType), + cancellationToken: cancellationToken).ConfigureAwait(false); if (result == null || result.Document == null) { diff --git a/Emby.Dlna/PlayTo/DeviceInfo.cs b/Emby.Dlna/PlayTo/DeviceInfo.cs index c36f890966..f3aaaebc4a 100644 --- a/Emby.Dlna/PlayTo/DeviceInfo.cs +++ b/Emby.Dlna/PlayTo/DeviceInfo.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using Emby.Dlna.Common; diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 0dbf1a3e66..43e9830540 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -28,6 +27,8 @@ namespace Emby.Dlna.PlayTo { public class PlayToController : ISessionController, IDisposable { + private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); + private Device _device; private readonly SessionInfo _session; private readonly ISessionManager _sessionManager; @@ -46,9 +47,10 @@ namespace Emby.Dlna.PlayTo private readonly string _serverAddress; private readonly string _accessToken; - public bool IsSessionActive => !_disposed && _device != null; + private readonly List _playlist = new List(); + private int _currentPlaylistIndex; - public bool SupportsMediaControl => IsSessionActive; + private bool _disposed; public PlayToController( SessionInfo session, @@ -84,18 +86,22 @@ namespace Emby.Dlna.PlayTo _mediaEncoder = mediaEncoder; } + public bool IsSessionActive => !_disposed && _device != null; + + public bool SupportsMediaControl => IsSessionActive; + public void Init(Device device) { _device = device; _device.OnDeviceUnavailable = OnDeviceUnavailable; - _device.PlaybackStart += _device_PlaybackStart; - _device.PlaybackProgress += _device_PlaybackProgress; - _device.PlaybackStopped += _device_PlaybackStopped; - _device.MediaChanged += _device_MediaChanged; + _device.PlaybackStart += OnDevicePlaybackStart; + _device.PlaybackProgress += OnDevicePlaybackProgress; + _device.PlaybackStopped += OnDevicePlaybackStopped; + _device.MediaChanged += OnDeviceMediaChanged; _device.Start(); - _deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft; + _deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft; } private void OnDeviceUnavailable() @@ -111,7 +117,7 @@ namespace Emby.Dlna.PlayTo } } - void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs e) + private void OnDeviceDiscoveryDeviceLeft(object sender, GenericEventArgs e) { var info = e.Argument; @@ -126,7 +132,7 @@ namespace Emby.Dlna.PlayTo } } - async void _device_MediaChanged(object sender, MediaChangedEventArgs e) + private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e) { if (_disposed) { @@ -138,15 +144,15 @@ namespace Emby.Dlna.PlayTo var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager); if (streamInfo.Item != null) { - var positionTicks = GetProgressPositionTicks(e.OldMediaInfo, streamInfo); + var positionTicks = GetProgressPositionTicks(streamInfo); - ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks); + ReportPlaybackStopped(streamInfo, positionTicks); } streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); if (streamInfo.Item == null) return; - var newItemProgress = GetProgressInfo(e.NewMediaInfo, streamInfo); + var newItemProgress = GetProgressInfo(streamInfo); await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false); } @@ -156,7 +162,7 @@ namespace Emby.Dlna.PlayTo } } - async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e) + private async void OnDevicePlaybackStopped(object sender, PlaybackStoppedEventArgs e) { if (_disposed) { @@ -169,9 +175,9 @@ namespace Emby.Dlna.PlayTo if (streamInfo.Item == null) return; - var positionTicks = GetProgressPositionTicks(e.MediaInfo, streamInfo); + var positionTicks = GetProgressPositionTicks(streamInfo); - ReportPlaybackStopped(e.MediaInfo, streamInfo, positionTicks); + ReportPlaybackStopped(streamInfo, positionTicks); var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); @@ -195,7 +201,7 @@ namespace Emby.Dlna.PlayTo } else { - Playlist.Clear(); + _playlist.Clear(); } } catch (Exception ex) @@ -204,7 +210,7 @@ namespace Emby.Dlna.PlayTo } } - private async void ReportPlaybackStopped(uBaseObject mediaInfo, StreamParams streamInfo, long? positionTicks) + private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) { try { @@ -223,7 +229,7 @@ namespace Emby.Dlna.PlayTo } } - async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e) + private async void OnDevicePlaybackStart(object sender, PlaybackStartEventArgs e) { if (_disposed) { @@ -236,7 +242,7 @@ namespace Emby.Dlna.PlayTo if (info.Item != null) { - var progress = GetProgressInfo(e.MediaInfo, info); + var progress = GetProgressInfo(info); await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false); } @@ -247,7 +253,7 @@ namespace Emby.Dlna.PlayTo } } - async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e) + private async void OnDevicePlaybackProgress(object sender, PlaybackProgressEventArgs e) { if (_disposed) { @@ -267,7 +273,7 @@ namespace Emby.Dlna.PlayTo if (info.Item != null) { - var progress = GetProgressInfo(e.MediaInfo, info); + var progress = GetProgressInfo(info); await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false); } @@ -278,7 +284,7 @@ namespace Emby.Dlna.PlayTo } } - private long? GetProgressPositionTicks(uBaseObject mediaInfo, StreamParams info) + private long? GetProgressPositionTicks(StreamParams info) { var ticks = _device.Position.Ticks; @@ -290,13 +296,13 @@ namespace Emby.Dlna.PlayTo return ticks; } - private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info) + private PlaybackStartInfo GetProgressInfo(StreamParams info) { return new PlaybackStartInfo { ItemId = info.ItemId, SessionId = _session.Id, - PositionTicks = GetProgressPositionTicks(mediaInfo, info), + PositionTicks = GetProgressPositionTicks(info), IsMuted = _device.IsMuted, IsPaused = _device.IsPaused, MediaSourceId = info.MediaSourceId, @@ -311,9 +317,7 @@ namespace Emby.Dlna.PlayTo }; } - #region SendCommands - - public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) + public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) { _logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand); @@ -351,11 +355,12 @@ namespace Emby.Dlna.PlayTo if (command.PlayCommand == PlayCommand.PlayLast) { - Playlist.AddRange(playlist); + _playlist.AddRange(playlist); } + if (command.PlayCommand == PlayCommand.PlayNext) { - Playlist.AddRange(playlist); + _playlist.AddRange(playlist); } if (!command.ControllingUserId.Equals(Guid.Empty)) @@ -364,7 +369,7 @@ namespace Emby.Dlna.PlayTo _session.DeviceName, _session.RemoteEndPoint, user); } - await PlayItems(playlist).ConfigureAwait(false); + return PlayItems(playlist, cancellationToken); } private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) @@ -372,7 +377,7 @@ namespace Emby.Dlna.PlayTo switch (command.Command) { case PlaystateCommand.Stop: - Playlist.Clear(); + _playlist.Clear(); return _device.SetStop(CancellationToken.None); case PlaystateCommand.Pause: @@ -388,10 +393,10 @@ namespace Emby.Dlna.PlayTo return Seek(command.SeekPositionTicks ?? 0); case PlaystateCommand.NextTrack: - return SetPlaylistIndex(_currentPlaylistIndex + 1); + return SetPlaylistIndex(_currentPlaylistIndex + 1, cancellationToken); case PlaystateCommand.PreviousTrack: - return SetPlaylistIndex(_currentPlaylistIndex - 1); + return SetPlaylistIndex(_currentPlaylistIndex - 1, cancellationToken); } return Task.CompletedTask; @@ -427,14 +432,6 @@ namespace Emby.Dlna.PlayTo return info.IsDirectStream; } - #endregion - - #region Playlist - - private int _currentPlaylistIndex; - private readonly List _playlist = new List(); - private List Playlist => _playlist; - private void AddItemFromId(Guid id, List list) { var item = _libraryManager.GetItemById(id); @@ -452,7 +449,7 @@ namespace Emby.Dlna.PlayTo _dlnaManager.GetDefaultProfile(); var mediaSources = item is IHasMediaSources - ? (_mediaSourceManager.GetStaticMediaSources(item, true, user)) + ? _mediaSourceManager.GetStaticMediaSources(item, true, user) : new List(); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); @@ -460,8 +457,19 @@ namespace Emby.Dlna.PlayTo playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken)); - var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder) - .GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); + var itemXml = new DidlBuilder( + profile, + user, + _imageProcessor, + _serverAddress, + _accessToken, + _userDataManager, + _localization, + _mediaSourceManager, + _logger, + _mediaEncoder, + _libraryManager) + .GetItemDidl(item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); playlistItem.Didl = itemXml; @@ -571,30 +579,31 @@ namespace Emby.Dlna.PlayTo /// Plays the items. /// /// The items. - /// - private async Task PlayItems(IEnumerable items) + /// The cancellation token. + /// true on success. + private async Task PlayItems(IEnumerable items, CancellationToken cancellationToken = default) { - Playlist.Clear(); - Playlist.AddRange(items); - _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, Playlist.Count); + _playlist.Clear(); + _playlist.AddRange(items); + _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, _playlist.Count); - await SetPlaylistIndex(0).ConfigureAwait(false); + await SetPlaylistIndex(0, cancellationToken).ConfigureAwait(false); return true; } - private async Task SetPlaylistIndex(int index) + private async Task SetPlaylistIndex(int index, CancellationToken cancellationToken = default) { - if (index < 0 || index >= Playlist.Count) + if (index < 0 || index >= _playlist.Count) { - Playlist.Clear(); - await _device.SetStop(CancellationToken.None); + _playlist.Clear(); + await _device.SetStop(cancellationToken).ConfigureAwait(false); return; } _currentPlaylistIndex = index; - var currentitem = Playlist[index]; + var currentitem = _playlist[index]; - await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, CancellationToken.None); + await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false); var streamInfo = currentitem.StreamInfo; if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo)) @@ -603,10 +612,7 @@ namespace Emby.Dlna.PlayTo } } - #endregion - - private bool _disposed; - + /// public void Dispose() { Dispose(true); @@ -625,19 +631,17 @@ namespace Emby.Dlna.PlayTo _device.Dispose(); } - _device.PlaybackStart -= _device_PlaybackStart; - _device.PlaybackProgress -= _device_PlaybackProgress; - _device.PlaybackStopped -= _device_PlaybackStopped; - _device.MediaChanged -= _device_MediaChanged; - _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; + _device.PlaybackStart -= OnDevicePlaybackStart; + _device.PlaybackProgress -= OnDevicePlaybackProgress; + _device.PlaybackStopped -= OnDevicePlaybackStopped; + _device.MediaChanged -= OnDeviceMediaChanged; + _deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft; _device.OnDeviceUnavailable = null; _device = null; _disposed = true; } - private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); - private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) { if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType)) @@ -714,7 +718,7 @@ namespace Emby.Dlna.PlayTo if (info.Item != null) { - var newPosition = GetProgressPositionTicks(media, info) ?? 0; + var newPosition = GetProgressPositionTicks(info) ?? 0; var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex); @@ -739,7 +743,7 @@ namespace Emby.Dlna.PlayTo if (info.Item != null) { - var newPosition = GetProgressPositionTicks(media, info) ?? 0; + var newPosition = GetProgressPositionTicks(info) ?? 0; var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex); @@ -853,8 +857,11 @@ namespace Emby.Dlna.PlayTo return request; } - var index = url.IndexOf('?'); - if (index == -1) return request; + var index = url.IndexOf('?', StringComparison.Ordinal); + if (index == -1) + { + return request; + } var query = url.Substring(index + 1); Dictionary values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString()); diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 5d75e33600..bbedd1485c 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; @@ -24,7 +23,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.PlayTo { - public class PlayToManager : IDisposable + public sealed class PlayToManager : IDisposable { private readonly ILogger _logger; private readonly ISessionManager _sessionManager; @@ -232,6 +231,7 @@ namespace Emby.Dlna.PlayTo } } + /// public void Dispose() { _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered; @@ -245,6 +245,9 @@ namespace Emby.Dlna.PlayTo } + _sessionLock.Dispose(); + _disposeCancellationTokenSource.Dispose(); + _disposed = true; } } diff --git a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs index bdd2a6c3e4..795618df23 100644 --- a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; diff --git a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs index 485f7ec103..27883ca32a 100644 --- a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs index 2eddb125d4..3b169e5993 100644 --- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; diff --git a/Emby.Dlna/PlayTo/PlaylistItem.cs b/Emby.Dlna/PlayTo/PlaylistItem.cs index 42d73c38ca..85846166cf 100644 --- a/Emby.Dlna/PlayTo/PlaylistItem.cs +++ b/Emby.Dlna/PlayTo/PlaylistItem.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs index f7a750d21d..bedc8b9ad3 100644 --- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.IO; using System.Linq; diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 757e713e1c..8c13620075 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; @@ -33,18 +32,15 @@ namespace Emby.Dlna.PlayTo DeviceService service, string command, string postData, - bool logRequest = true, - string header = null) + string header = null, + CancellationToken cancellationToken = default) { - var cancellationToken = CancellationToken.None; - var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); using (var response = await PostSoapDataAsync( url, $"\"{service.ServiceType}#{command}\"", postData, header, - logRequest, cancellationToken) .ConfigureAwait(false)) using (var stream = response.Content) @@ -64,7 +60,7 @@ namespace Emby.Dlna.PlayTo return serviceUrl; } - if (!serviceUrl.StartsWith("/")) + if (!serviceUrl.StartsWith("/", StringComparison.Ordinal)) { serviceUrl = "/" + serviceUrl; } @@ -128,7 +124,6 @@ namespace Emby.Dlna.PlayTo string soapAction, string postData, string header, - bool logRequest, CancellationToken cancellationToken) { if (soapAction[0] != '\"') diff --git a/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs b/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs index b312c8b6ec..7daefeca86 100644 --- a/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs +++ b/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Dlna.PlayTo { diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs index a00d154f78..c0ce3ab6e9 100644 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ b/Emby.Dlna/PlayTo/TransportCommands.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Dlna/PlayTo/UpnpContainer.cs b/Emby.Dlna/PlayTo/UpnpContainer.cs index 9700d8a5d1..e2d7a10f02 100644 --- a/Emby.Dlna/PlayTo/UpnpContainer.cs +++ b/Emby.Dlna/PlayTo/UpnpContainer.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Xml.Linq; diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs index 6e2e31dc47..a8ed5692c9 100644 --- a/Emby.Dlna/PlayTo/uBaseObject.cs +++ b/Emby.Dlna/PlayTo/uBaseObject.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; diff --git a/Emby.Dlna/PlayTo/uPnpNamespaces.cs b/Emby.Dlna/PlayTo/uPnpNamespaces.cs index fc0f0f704f..dc65cdf43c 100644 --- a/Emby.Dlna/PlayTo/uPnpNamespaces.cs +++ b/Emby.Dlna/PlayTo/uPnpNamespaces.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Xml.Linq; diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs index 97286e3472..d10804b228 100644 --- a/Emby.Dlna/Profiles/DefaultProfile.cs +++ b/Emby.Dlna/Profiles/DefaultProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Linq; using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/DenonAvrProfile.cs b/Emby.Dlna/Profiles/DenonAvrProfile.cs index 3be9805288..73a87c499e 100644 --- a/Emby.Dlna/Profiles/DenonAvrProfile.cs +++ b/Emby.Dlna/Profiles/DenonAvrProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/DirectTvProfile.cs b/Emby.Dlna/Profiles/DirectTvProfile.cs index 33bcae6044..5ca388167b 100644 --- a/Emby.Dlna/Profiles/DirectTvProfile.cs +++ b/Emby.Dlna/Profiles/DirectTvProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs b/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs index 26654b803c..942e369309 100644 --- a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs +++ b/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/Foobar2000Profile.cs b/Emby.Dlna/Profiles/Foobar2000Profile.cs index c1aece8c8a..ea3de686a6 100644 --- a/Emby.Dlna/Profiles/Foobar2000Profile.cs +++ b/Emby.Dlna/Profiles/Foobar2000Profile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/LgTvProfile.cs b/Emby.Dlna/Profiles/LgTvProfile.cs index 63b5b6f311..02301764c0 100644 --- a/Emby.Dlna/Profiles/LgTvProfile.cs +++ b/Emby.Dlna/Profiles/LgTvProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs b/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs index 3a9744e381..1b1423520c 100644 --- a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs +++ b/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/MarantzProfile.cs b/Emby.Dlna/Profiles/MarantzProfile.cs index 05f94a2063..6cfcc3b824 100644 --- a/Emby.Dlna/Profiles/MarantzProfile.cs +++ b/Emby.Dlna/Profiles/MarantzProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs index 10218fa563..7161af7386 100644 --- a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs +++ b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs b/Emby.Dlna/Profiles/PanasonicVieraProfile.cs index 945ec45187..44c35e1425 100644 --- a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs +++ b/Emby.Dlna/Profiles/PanasonicVieraProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/PopcornHourProfile.cs b/Emby.Dlna/Profiles/PopcornHourProfile.cs index 3765d01dc4..9e9f6966fc 100644 --- a/Emby.Dlna/Profiles/PopcornHourProfile.cs +++ b/Emby.Dlna/Profiles/PopcornHourProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs b/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs index 61c5f4dce9..4ff2ab9be2 100644 --- a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs +++ b/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SharpSmartTvProfile.cs b/Emby.Dlna/Profiles/SharpSmartTvProfile.cs index 8967dc16a9..aa8d434e3f 100644 --- a/Emby.Dlna/Profiles/SharpSmartTvProfile.cs +++ b/Emby.Dlna/Profiles/SharpSmartTvProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs index 308d74aa84..42b066d52b 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs index 496c243160..fbdf2c18e8 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs index 987a9af4b1..ce32179a1b 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs index 560193dedd..aa1721d398 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs b/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs index c983d98baf..ecdd2e7a4e 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs index 186c894736..68365ba4ae 100644 --- a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs index a29d143f68..b34af04a50 100644 --- a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs index 9bcdd21b8f..0e75d0cb5e 100644 --- a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs index 900e4ff06e..3300863c90 100644 --- a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs index 963e7993e1..4e833441cc 100644 --- a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyPs3Profile.cs b/Emby.Dlna/Profiles/SonyPs3Profile.cs index 31a764d8d3..7f72356bdc 100644 --- a/Emby.Dlna/Profiles/SonyPs3Profile.cs +++ b/Emby.Dlna/Profiles/SonyPs3Profile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/SonyPs4Profile.cs b/Emby.Dlna/Profiles/SonyPs4Profile.cs index 9376a564ba..411bfe2b0c 100644 --- a/Emby.Dlna/Profiles/SonyPs4Profile.cs +++ b/Emby.Dlna/Profiles/SonyPs4Profile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/WdtvLiveProfile.cs b/Emby.Dlna/Profiles/WdtvLiveProfile.cs index 8e056792a6..2de9a8cd9e 100644 --- a/Emby.Dlna/Profiles/WdtvLiveProfile.cs +++ b/Emby.Dlna/Profiles/WdtvLiveProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Profiles/XboxOneProfile.cs b/Emby.Dlna/Profiles/XboxOneProfile.cs index 364c433549..2cbe4e6acb 100644 --- a/Emby.Dlna/Profiles/XboxOneProfile.cs +++ b/Emby.Dlna/Profiles/XboxOneProfile.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index a72c62b12d..5ecc81a2f1 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 4704ecbe69..161a3434c5 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs index d7e5c541d9..3704bedcd5 100644 --- a/Emby.Dlna/Service/BaseService.cs +++ b/Emby.Dlna/Service/BaseService.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using Emby.Dlna.Eventing; using MediaBrowser.Common.Net; @@ -13,7 +12,7 @@ namespace Emby.Dlna.Service protected IHttpClient HttpClient; protected ILogger Logger; - protected BaseService(ILogger logger, IHttpClient httpClient) + protected BaseService(ILogger logger, IHttpClient httpClient) { Logger = logger; HttpClient = httpClient; diff --git a/Emby.Dlna/Service/ControlErrorHandler.cs b/Emby.Dlna/Service/ControlErrorHandler.cs index a2f5057fb0..047e9f0142 100644 --- a/Emby.Dlna/Service/ControlErrorHandler.cs +++ b/Emby.Dlna/Service/ControlErrorHandler.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.IO; diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs index 0787b8df94..62ffd9e42a 100644 --- a/Emby.Dlna/Service/ServiceXmlBuilder.cs +++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using System.Text; diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index c5e57d0ff0..f95b8ce7de 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Dlna/Ssdp/Extensions.cs b/Emby.Dlna/Ssdp/Extensions.cs index 836d4abfd2..10c1f321be 100644 --- a/Emby.Dlna/Ssdp/Extensions.cs +++ b/Emby.Dlna/Ssdp/Extensions.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Xml.Linq; diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs index b807816eba..33f4468d9f 100644 --- a/Emby.Naming/Audio/AlbumParser.cs +++ b/Emby.Naming/Audio/AlbumParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs index 9f21e93dc4..25d5f8735e 100644 --- a/Emby.Naming/Audio/AudioFileParser.cs +++ b/Emby.Naming/Audio/AudioFileParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.IO; @@ -8,19 +7,12 @@ using Emby.Naming.Common; namespace Emby.Naming.Audio { - public class AudioFileParser + public static class AudioFileParser { - private readonly NamingOptions _options; - - public AudioFileParser(NamingOptions options) - { - _options = options; - } - - public bool IsAudioFile(string path) + public static bool IsAudioFile(string path, NamingOptions options) { var extension = Path.GetExtension(path) ?? string.Empty; - return _options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); } } } diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs index 0bc6ec7e40..c4863b50ab 100644 --- a/Emby.Naming/AudioBook/AudioBookFileInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs @@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook /// The type. public bool IsDirectory { get; set; } - /// + /// public int CompareTo(AudioBookFileInfo other) { if (ReferenceEquals(this, other)) diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs index 8dc2e1b97c..5494df9d63 100644 --- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs +++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs index 68d6ca4d46..e28a58db78 100644 --- a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs +++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.AudioBook { diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 835e83a081..f4ba11a0d1 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using System.Linq; @@ -30,11 +29,7 @@ namespace Emby.Naming.AudioBook // Filter out all extras, otherwise they could cause stacks to not be resolved // See the unit test TestStackedWithTrailer var metadata = audiobookFileInfos - .Select(i => new FileSystemMetadata - { - FullName = i.Path, - IsDirectory = i.IsDirectory - }); + .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); var stackResult = new StackResolver(_options) .ResolveAudioBooks(metadata); @@ -43,11 +38,7 @@ namespace Emby.Naming.AudioBook { var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).ToList(); stackFiles.Sort(); - var info = new AudioBookInfo - { - Files = stackFiles, - Name = stack.Name - }; + var info = new AudioBookInfo { Files = stackFiles, Name = stack.Name }; yield return info; } diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index 0b0d2035e7..5466b46379 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.IO; diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs index f60f7e84b1..07de728514 100644 --- a/Emby.Naming/Common/EpisodeExpression.cs +++ b/Emby.Naming/Common/EpisodeExpression.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Text.RegularExpressions; diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs index a61f10489c..cc18ce4cdd 100644 --- a/Emby.Naming/Common/MediaType.cs +++ b/Emby.Naming/Common/MediaType.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.Common { diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index b4f22ed696..c00181c7e6 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Linq; @@ -506,7 +505,63 @@ namespace Emby.Naming.Common RuleType = ExtraRuleType.Suffix, Token = "-short", MediaType = MediaType.Video - } + }, + new ExtraRule + { + ExtraType = ExtraType.BehindTheScenes, + RuleType = ExtraRuleType.DirectoryName, + Token = "behind the scenes", + MediaType = MediaType.Video, + }, + new ExtraRule + { + ExtraType = ExtraType.DeletedScene, + RuleType = ExtraRuleType.DirectoryName, + Token = "deleted scenes", + MediaType = MediaType.Video, + }, + new ExtraRule + { + ExtraType = ExtraType.Interview, + RuleType = ExtraRuleType.DirectoryName, + Token = "interviews", + MediaType = MediaType.Video, + }, + new ExtraRule + { + ExtraType = ExtraType.Scene, + RuleType = ExtraRuleType.DirectoryName, + Token = "scenes", + MediaType = MediaType.Video, + }, + new ExtraRule + { + ExtraType = ExtraType.Sample, + RuleType = ExtraRuleType.DirectoryName, + Token = "samples", + MediaType = MediaType.Video, + }, + new ExtraRule + { + ExtraType = ExtraType.Clip, + RuleType = ExtraRuleType.DirectoryName, + Token = "shorts", + MediaType = MediaType.Video, + }, + new ExtraRule + { + ExtraType = ExtraType.Clip, + RuleType = ExtraRuleType.DirectoryName, + Token = "featurettes", + MediaType = MediaType.Video, + }, + new ExtraRule + { + ExtraType = ExtraType.Unknown, + RuleType = ExtraRuleType.DirectoryName, + Token = "extras", + MediaType = MediaType.Video, + }, }; Format3DRules = new[] diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/Subtitles/SubtitleInfo.cs index fe42846c61..f39c496b7a 100644 --- a/Emby.Naming/Subtitles/SubtitleInfo.cs +++ b/Emby.Naming/Subtitles/SubtitleInfo.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.Subtitles { diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs index b055b1a6c2..88ec3e2d60 100644 --- a/Emby.Naming/Subtitles/SubtitleParser.cs +++ b/Emby.Naming/Subtitles/SubtitleParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.IO; @@ -38,7 +37,8 @@ namespace Emby.Naming.Subtitles IsForced = _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)) }; - var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase)) + var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) + && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase)) .ToList(); // Should have a name, language and file extension diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs index 667129a57d..250df4e2d3 100644 --- a/Emby.Naming/TV/EpisodeInfo.cs +++ b/Emby.Naming/TV/EpisodeInfo.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.TV { diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs index b97b3137bc..a6af689c72 100644 --- a/Emby.Naming/TV/EpisodePathParser.cs +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 #nullable enable using System; @@ -19,7 +18,13 @@ namespace Emby.Naming.TV _options = options; } - public EpisodePathParserResult Parse(string path, bool isDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true) + public EpisodePathParserResult Parse( + string path, + bool isDirectory, + bool? isNamed = null, + bool? isOptimistic = null, + bool? supportsAbsoluteNumbers = null, + bool fillExtendedInfo = true) { // Added to be able to use regex patterns which require a file extension. // There were no failed tests without this block, but to be safe, we can keep it until @@ -65,7 +70,7 @@ namespace Emby.Naming.TV { result.SeriesName = result.SeriesName .Trim() - .Trim(new[] { '_', '.', '-' }) + .Trim('_', '.', '-') .Trim(); } } diff --git a/Emby.Naming/TV/EpisodePathParserResult.cs b/Emby.Naming/TV/EpisodePathParserResult.cs index 3acbbc101c..05f921edc9 100644 --- a/Emby.Naming/TV/EpisodePathParserResult.cs +++ b/Emby.Naming/TV/EpisodePathParserResult.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.TV { diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index 57659ee131..6994f69fc4 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 #nullable enable using System; diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs index 7715a16a45..2fa6b43531 100644 --- a/Emby.Naming/TV/SeasonPathParser.cs +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -1,10 +1,8 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; using System.IO; -using System.Linq; namespace Emby.Naming.TV { @@ -29,14 +27,14 @@ namespace Emby.Naming.TV { var result = new SeasonPathParserResult(); - var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders); + var (seasonNumber, isSeasonFolder) = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders); - result.SeasonNumber = seasonNumberInfo.seasonNumber; + result.SeasonNumber = seasonNumber; if (result.SeasonNumber.HasValue) { result.Success = true; - result.IsSeasonFolder = seasonNumberInfo.isSeasonFolder; + result.IsSeasonFolder = isSeasonFolder; } return result; @@ -90,12 +88,10 @@ namespace Emby.Naming.TV // Look for one of the season folder names foreach (var name in _seasonFolderNames) { - var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase); - - if (index != -1) + if (filename.Contains(name, StringComparison.OrdinalIgnoreCase)) { var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase)); - if (result.Item1.HasValue) + if (result.seasonNumber.HasValue) { return result; } @@ -105,25 +101,32 @@ namespace Emby.Naming.TV } var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries); - var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue); - return (resultNumber, true); + for (int i = 0; i < parts.Length; i++) + { + if (TryGetSeasonNumberFromPart(parts[i], out int seasonNumber)) + { + return (seasonNumber, true); + } + } + + return (null, true); } - private static int? GetSeasonNumberFromPart(string part) + private static bool TryGetSeasonNumberFromPart(ReadOnlySpan part, out int seasonNumber) { + seasonNumber = 0; if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase)) { - return null; + return false; } - part = part.Substring(1); - - if (int.TryParse(part, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) + if (int.TryParse(part.Slice(1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { - return value; + seasonNumber = value; + return true; } - return null; + return false; } /// @@ -131,7 +134,7 @@ namespace Emby.Naming.TV /// /// The path. /// System.Nullable{System.Int32}. - private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(string path) + private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan path) { var numericStart = -1; var length = 0; @@ -142,7 +145,7 @@ namespace Emby.Naming.TV // Find out where the numbers start, and then keep going until they end for (var i = 0; i < path.Length; i++) { - if (char.IsNumber(path, i)) + if (char.IsNumber(path[i])) { if (!hasOpenParenth) { @@ -177,7 +180,7 @@ namespace Emby.Naming.TV return (null, isSeasonFolder); } - return (int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder); + return (int.Parse(path.Slice(numericStart, length), provider: CultureInfo.InvariantCulture), isSeasonFolder); } } } diff --git a/Emby.Naming/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs index 57c2347548..a142fafea0 100644 --- a/Emby.Naming/TV/SeasonPathParserResult.cs +++ b/Emby.Naming/TV/SeasonPathParserResult.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.TV { @@ -12,7 +11,7 @@ namespace Emby.Naming.TV public int? SeasonNumber { get; set; } /// - /// Gets or sets a value indicating whether this is success. + /// Gets or sets a value indicating whether this is success. /// /// true if success; otherwise, false. public bool Success { get; set; } diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs index 6c74c07d55..579c9e91e1 100644 --- a/Emby.Naming/Video/CleanDateTimeParser.cs +++ b/Emby.Naming/Video/CleanDateTimeParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 #nullable enable using System.Collections.Generic; diff --git a/Emby.Naming/Video/CleanDateTimeResult.cs b/Emby.Naming/Video/CleanDateTimeResult.cs index 73a445612b..57eeaa7e32 100644 --- a/Emby.Naming/Video/CleanDateTimeResult.cs +++ b/Emby.Naming/Video/CleanDateTimeResult.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 #nullable enable namespace Emby.Naming.Video diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index b7b65d8228..3f584d5847 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 #nullable enable using System; diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index ea9a6d6c2a..fc0424faab 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.IO; @@ -32,7 +31,7 @@ namespace Emby.Naming.Video if (rule.MediaType == MediaType.Audio) { - if (!new AudioFileParser(_options).IsAudioFile(path)) + if (!AudioFileParser.IsAudioFile(path, _options)) { return result; } @@ -81,6 +80,15 @@ namespace Emby.Naming.Video 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/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs index 4e991d685d..15db32e876 100644 --- a/Emby.Naming/Video/ExtraResult.cs +++ b/Emby.Naming/Video/ExtraResult.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Entities; diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs index cfaa84ed6b..7c9702e244 100644 --- a/Emby.Naming/Video/ExtraRule.cs +++ b/Emby.Naming/Video/ExtraRule.cs @@ -1,35 +1,33 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Entities; using MediaType = Emby.Naming.Common.MediaType; namespace Emby.Naming.Video { + /// + /// A rule used to match a file path with an . + /// public class ExtraRule { /// - /// Gets or sets the token. + /// Gets or sets the token to use for matching against the file path. /// - /// The token. public string Token { get; set; } /// - /// Gets or sets the type of the extra. + /// Gets or sets the type of the extra to return when matched. /// - /// The type of the extra. public ExtraType ExtraType { get; set; } /// /// Gets or sets the type of the rule. /// - /// The type of the rule. public ExtraRuleType RuleType { get; set; } /// - /// Gets or sets the type of the media. + /// Gets or sets the type of the media to return when matched. /// - /// The type of the media. public MediaType MediaType { get; set; } } } diff --git a/Emby.Naming/Video/ExtraRuleType.cs b/Emby.Naming/Video/ExtraRuleType.cs index 2bf2799ff7..e89876f4ae 100644 --- a/Emby.Naming/Video/ExtraRuleType.cs +++ b/Emby.Naming/Video/ExtraRuleType.cs @@ -1,23 +1,27 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.Video { public enum ExtraRuleType { /// - /// The suffix + /// Match against a suffix in the file name. /// Suffix = 0, /// - /// The filename + /// Match against the file name, excluding the file extension. /// Filename = 1, /// - /// The regex + /// Match against the file name, including the file extension. /// - Regex = 2 + Regex = 2, + + /// + /// Match against the name of the directory containing the file. + /// + DirectoryName = 3, } } diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs index 56adf6add0..3ef190b865 100644 --- a/Emby.Naming/Video/FileStack.cs +++ b/Emby.Naming/Video/FileStack.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs index acf3438c22..a8bd9d5c5d 100644 --- a/Emby.Naming/Video/FlagParser.cs +++ b/Emby.Naming/Video/FlagParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.IO; diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs index 25905f33c1..51c26af863 100644 --- a/Emby.Naming/Video/Format3DParser.cs +++ b/Emby.Naming/Video/Format3DParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Linq; diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs index 6ebd72f6ba..fa0e9d3b80 100644 --- a/Emby.Naming/Video/Format3DResult.cs +++ b/Emby.Naming/Video/Format3DResult.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; diff --git a/Emby.Naming/Video/Format3DRule.cs b/Emby.Naming/Video/Format3DRule.cs index ae9fb5b19f..310ec84e8f 100644 --- a/Emby.Naming/Video/Format3DRule.cs +++ b/Emby.Naming/Video/Format3DRule.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.Video { diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index 8f210fa453..f733cd2620 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -22,31 +21,24 @@ namespace Emby.Naming.Video public IEnumerable ResolveDirectories(IEnumerable files) { - return Resolve(files.Select(i => new FileSystemMetadata - { - FullName = i, - IsDirectory = true - })); + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true })); } public IEnumerable ResolveFiles(IEnumerable files) { - return Resolve(files.Select(i => new FileSystemMetadata - { - FullName = i, - IsDirectory = false - })); + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false })); } public IEnumerable ResolveAudioBooks(IEnumerable files) { - foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName))) + var groupedDirectoryFiles = files.GroupBy(file => + file.IsDirectory + ? file.FullName + : Path.GetDirectoryName(file.FullName)); + + foreach (var directory in groupedDirectoryFiles) { - var stack = new FileStack() - { - Name = Path.GetFileName(directory.Key), - IsDirectoryStack = false - }; + var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false }; foreach (var file in directory) { if (file.IsDirectory) @@ -194,7 +186,7 @@ namespace Emby.Naming.Video } } - private string GetRegexInput(FileSystemMetadata file) + private static string GetRegexInput(FileSystemMetadata file) { // For directories, dummy up an extension otherwise the expressions will fail var input = !file.IsDirectory @@ -204,7 +196,7 @@ namespace Emby.Naming.Video return Path.GetFileName(input); } - private Match FindMatch(FileSystemMetadata input, Regex regex, int offset) + private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset) { var regexInput = GetRegexInput(input); diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs index 4024d6d593..f1b5d7bcca 100644 --- a/Emby.Naming/Video/StubResolver.cs +++ b/Emby.Naming/Video/StubResolver.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 #nullable enable using System; diff --git a/Emby.Naming/Video/StubResult.cs b/Emby.Naming/Video/StubResult.cs index 5ac85528f5..1b8e99b0dc 100644 --- a/Emby.Naming/Video/StubResult.cs +++ b/Emby.Naming/Video/StubResult.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.Video { diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs index 17c3ef8c5e..8285cb51a3 100644 --- a/Emby.Naming/Video/StubTypeRule.cs +++ b/Emby.Naming/Video/StubTypeRule.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Naming.Video { diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index aa4f3a35c3..11e789b663 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -77,7 +77,9 @@ namespace Emby.Naming.Video /// Gets the file name without extension. /// /// The file name without extension. - public string FileNameWithoutExtension => !IsDirectory ? System.IO.Path.GetFileNameWithoutExtension(Path) : System.IO.Path.GetFileName(Path); + public string FileNameWithoutExtension => !IsDirectory + ? System.IO.Path.GetFileNameWithoutExtension(Path) + : System.IO.Path.GetFileName(Path); /// public override string ToString() diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 1366583534..7f755fd25e 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -34,11 +33,7 @@ namespace Emby.Naming.Video // See the unit test TestStackedWithTrailer var nonExtras = videoInfos .Where(i => i.ExtraType == null) - .Select(i => new FileSystemMetadata - { - FullName = i.Path, - IsDirectory = i.IsDirectory - }); + .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); var stackResult = new StackResolver(_options) .Resolve(nonExtras).ToList(); @@ -58,11 +53,7 @@ namespace Emby.Naming.Video info.Year = info.Files[0].Year; - var extraBaseNames = new List - { - stack.Name, - Path.GetFileNameWithoutExtension(stack.Files[0]) - }; + var extraBaseNames = new List { stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0]) }; var extras = GetExtras(remainingFiles, extraBaseNames); @@ -84,10 +75,7 @@ namespace Emby.Naming.Video foreach (var media in standaloneMedia) { - var info = new VideoInfo(media.Name) - { - Files = new List { media } - }; + var info = new VideoInfo(media.Name) { Files = new List { media } }; info.Year = info.Files[0].Year; @@ -223,8 +211,8 @@ namespace Emby.Naming.Video { testFilename = testFilename.Substring(folderName.Length).Trim(); return string.IsNullOrEmpty(testFilename) - || testFilename[0] == '-' - || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); + || testFilename[0] == '-' + || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); } return false; @@ -240,7 +228,8 @@ namespace Emby.Naming.Video return remainingFiles .Where(i => i.ExtraType == null) - .Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) + .Where(i => baseNames.Any(b => + i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) .ToList(); } } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 699bbe40a1..0b75a8cce9 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 #nullable enable using System; diff --git a/Emby.Notifications/Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs index 83845558ad..788750796d 100644 --- a/Emby.Notifications/Api/NotificationsService.cs +++ b/Emby.Notifications/Api/NotificationsService.cs @@ -1,5 +1,10 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1402 +#pragma warning disable SA1649 + using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -16,7 +21,7 @@ namespace Emby.Notifications.Api public class GetNotifications : IReturn { [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public string UserId { get; set; } = string.Empty; [ApiMember(Name = "IsRead", Description = "An optional filter by IsRead", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsRead { get; set; } @@ -30,32 +35,34 @@ namespace Emby.Notifications.Api public class Notification { - public string Id { get; set; } + public string Id { get; set; } = string.Empty; - public string UserId { get; set; } + public string UserId { get; set; } = string.Empty; public DateTime Date { get; set; } public bool IsRead { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; - public string Description { get; set; } + public string Description { get; set; } = string.Empty; - public string Url { get; set; } + public string Url { get; set; } = string.Empty; public NotificationLevel Level { get; set; } } public class NotificationResult { - public Notification[] Notifications { get; set; } + public IReadOnlyList Notifications { get; set; } = Array.Empty(); + public int TotalRecordCount { get; set; } } public class NotificationsSummary { public int UnreadCount { get; set; } + public NotificationLevel MaxUnreadNotificationLevel { get; set; } } @@ -63,7 +70,7 @@ namespace Emby.Notifications.Api public class GetNotificationsSummary : IReturn { [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public string UserId { get; set; } = string.Empty; } [Route("/Notifications/Types", "GET", Summary = "Gets notification types")] @@ -80,16 +87,16 @@ namespace Emby.Notifications.Api public class AddAdminNotification : IReturnVoid { [ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; [ApiMember(Name = "Description", Description = "The notification's description", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Description { get; set; } + public string Description { get; set; } = string.Empty; [ApiMember(Name = "ImageUrl", Description = "The notification's image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ImageUrl { get; set; } + public string? ImageUrl { get; set; } [ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Url { get; set; } + public string? Url { get; set; } [ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public NotificationLevel Level { get; set; } @@ -99,20 +106,20 @@ namespace Emby.Notifications.Api public class MarkRead : IReturnVoid { [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } + public string UserId { get; set; } = string.Empty; [ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string Ids { get; set; } + public string Ids { get; set; } = string.Empty; } [Route("/Notifications/{UserId}/Unread", "POST", Summary = "Marks notifications as unread")] public class MarkUnread : IReturnVoid { [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } + public string UserId { get; set; } = string.Empty; [ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string Ids { get; set; } + public string Ids { get; set; } = string.Empty; } [Authenticated] @@ -127,32 +134,29 @@ namespace Emby.Notifications.Api _userManager = userManager; } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetNotificationTypes request) { return _notificationManager.GetNotificationTypes(); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetNotificationServices request) { return _notificationManager.GetNotificationServices().ToList(); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetNotificationsSummary request) { return new NotificationsSummary { - }; } public Task Post(AddAdminNotification request) { // This endpoint really just exists as post of a real with sickbeard - return AddNotification(request); - } - - private Task AddNotification(AddAdminNotification request) - { var notification = new NotificationRequest { Date = DateTime.UtcNow, @@ -166,14 +170,17 @@ namespace Emby.Notifications.Api return _notificationManager.SendNotification(notification, CancellationToken.None); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public void Post(MarkRead request) { } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public void Post(MarkUnread request) { } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetNotifications request) { return new NotificationResult(); diff --git a/Emby.Notifications/CoreNotificationTypes.cs b/Emby.Notifications/CoreNotificationTypes.cs index d11e01e334..a602b72213 100644 --- a/Emby.Notifications/CoreNotificationTypes.cs +++ b/Emby.Notifications/CoreNotificationTypes.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj index 004ded77b1..e6bf785bff 100644 --- a/Emby.Notifications/Emby.Notifications.csproj +++ b/Emby.Notifications/Emby.Notifications.csproj @@ -4,6 +4,8 @@ netstandard2.1 false true + true + enable @@ -16,4 +18,16 @@ + + + + + + + + + + ../jellyfin.ruleset + + diff --git a/Emby.Notifications/NotificationConfigurationFactory.cs b/Emby.Notifications/NotificationConfigurationFactory.cs index d08475f7d2..3fb3553d0e 100644 --- a/Emby.Notifications/NotificationConfigurationFactory.cs +++ b/Emby.Notifications/NotificationConfigurationFactory.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Notifications; @@ -13,7 +15,7 @@ namespace Emby.Notifications new ConfigurationStore { Key = "notifications", - ConfigurationType = typeof (NotificationOptions) + ConfigurationType = typeof(NotificationOptions) } }; } diff --git a/Emby.Notifications/Notifications.cs b/Emby.Notifications/NotificationEntryPoint.cs similarity index 68% rename from Emby.Notifications/Notifications.cs rename to Emby.Notifications/NotificationEntryPoint.cs index 7aa1e7ae89..befecc570b 100644 --- a/Emby.Notifications/Notifications.cs +++ b/Emby.Notifications/NotificationEntryPoint.cs @@ -21,70 +21,85 @@ using Microsoft.Extensions.Logging; namespace Emby.Notifications { /// - /// Creates notifications for various system events + /// Creates notifications for various system events. /// - public class Notifications : IServerEntryPoint + public class NotificationEntryPoint : IServerEntryPoint { private readonly ILogger _logger; - + private readonly IActivityManager _activityManager; + private readonly ILocalizationManager _localization; private readonly INotificationManager _notificationManager; - private readonly ILibraryManager _libraryManager; private readonly IServerApplicationHost _appHost; - - private Timer LibraryUpdateTimer { get; set; } - private readonly object _libraryChangedSyncLock = new object(); - private readonly IConfigurationManager _config; - private readonly ILocalizationManager _localization; - private readonly IActivityManager _activityManager; + + private readonly object _libraryChangedSyncLock = new object(); + private readonly List _itemsAdded = new List(); + + private Timer? _libraryUpdateTimer; private string[] _coreNotificationTypes; - public Notifications( + private bool _disposed = false; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The activity manager. + /// The localization manager. + /// The notification manager. + /// The library manager. + /// The application host. + /// The configuration manager. + public NotificationEntryPoint( + ILogger logger, IActivityManager activityManager, ILocalizationManager localization, - ILogger logger, INotificationManager notificationManager, ILibraryManager libraryManager, IServerApplicationHost appHost, IConfigurationManager config) { _logger = logger; + _activityManager = activityManager; + _localization = localization; _notificationManager = notificationManager; _libraryManager = libraryManager; _appHost = appHost; _config = config; - _localization = localization; - _activityManager = activityManager; _coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray(); } + /// public Task RunAsync() { - _libraryManager.ItemAdded += _libraryManager_ItemAdded; - _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged; - _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged; - _activityManager.EntryCreated += _activityManager_EntryCreated; + _libraryManager.ItemAdded += OnLibraryManagerItemAdded; + _appHost.HasPendingRestartChanged += OnAppHostHasPendingRestartChanged; + _appHost.HasUpdateAvailableChanged += OnAppHostHasUpdateAvailableChanged; + _activityManager.EntryCreated += OnActivityManagerEntryCreated; return Task.CompletedTask; } - private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e) + private async void OnAppHostHasPendingRestartChanged(object sender, EventArgs e) { var type = NotificationType.ServerRestartRequired.ToString(); var notification = new NotificationRequest { NotificationType = type, - Name = string.Format(_localization.GetLocalizedString("ServerNameNeedsToBeRestarted"), _appHost.Name) + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("ServerNameNeedsToBeRestarted"), + _appHost.Name) }; await SendNotification(notification, null).ConfigureAwait(false); } - private async void _activityManager_EntryCreated(object sender, GenericEventArgs e) + private async void OnActivityManagerEntryCreated(object sender, GenericEventArgs e) { var entry = e.Argument; @@ -117,7 +132,7 @@ namespace Emby.Notifications return _config.GetConfiguration("notifications"); } - private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e) + private async void OnAppHostHasUpdateAvailableChanged(object sender, EventArgs e) { if (!_appHost.HasUpdateAvailable) { @@ -136,8 +151,7 @@ namespace Emby.Notifications await SendNotification(notification, null).ConfigureAwait(false); } - private readonly List _itemsAdded = new List(); - private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + private void OnLibraryManagerItemAdded(object sender, ItemChangeEventArgs e) { if (!FilterItem(e.Item)) { @@ -146,14 +160,17 @@ namespace Emby.Notifications lock (_libraryChangedSyncLock) { - if (LibraryUpdateTimer == null) + if (_libraryUpdateTimer == null) { - LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, 5000, - Timeout.Infinite); + _libraryUpdateTimer = new Timer( + LibraryUpdateTimerCallback, + null, + 5000, + Timeout.Infinite); } else { - LibraryUpdateTimer.Change(5000, Timeout.Infinite); + _libraryUpdateTimer.Change(5000, Timeout.Infinite); } _itemsAdded.Add(e.Item); @@ -188,7 +205,8 @@ namespace Emby.Notifications { items = _itemsAdded.ToList(); _itemsAdded.Clear(); - DisposeLibraryUpdateTimer(); + _libraryUpdateTimer!.Dispose(); // Shouldn't be null as it just set off this callback + _libraryUpdateTimer = null; } items = items.Take(10).ToList(); @@ -198,7 +216,10 @@ namespace Emby.Notifications var notification = new NotificationRequest { NotificationType = NotificationType.NewLibraryContent.ToString(), - Name = string.Format(_localization.GetLocalizedString("ValueHasBeenAddedToLibrary"), GetItemName(item)), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("ValueHasBeenAddedToLibrary"), + GetItemName(item)), Description = item.Overview }; @@ -206,6 +227,11 @@ namespace Emby.Notifications } } + /// + /// Creates a human readable name for the item. + /// + /// The item. + /// A human readable name for the item. public static string GetItemName(BaseItem item) { var name = item.Name; @@ -219,6 +245,7 @@ namespace Emby.Notifications episode.IndexNumber.Value, name); } + if (episode.ParentIndexNumber.HasValue) { name = string.Format( @@ -229,7 +256,6 @@ namespace Emby.Notifications } } - if (item is IHasSeries hasSeries) { name = hasSeries.SeriesName + " - " + name; @@ -257,7 +283,7 @@ namespace Emby.Notifications return name; } - private async Task SendNotification(NotificationRequest notification, BaseItem relatedItem) + private async Task SendNotification(NotificationRequest notification, BaseItem? relatedItem) { try { @@ -269,23 +295,37 @@ namespace Emby.Notifications } } + /// public void Dispose() { - DisposeLibraryUpdateTimer(); - - _libraryManager.ItemAdded -= _libraryManager_ItemAdded; - _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged; - _appHost.HasUpdateAvailableChanged -= _appHost_HasUpdateAvailableChanged; - _activityManager.EntryCreated -= _activityManager_EntryCreated; + Dispose(true); + GC.SuppressFinalize(this); } - private void DisposeLibraryUpdateTimer() + /// + /// 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 disposing) { - if (LibraryUpdateTimer != null) + if (_disposed) { - LibraryUpdateTimer.Dispose(); - LibraryUpdateTimer = null; + return; } + + if (disposing) + { + _libraryUpdateTimer?.Dispose(); + } + + _libraryUpdateTimer = null; + + _libraryManager.ItemAdded -= OnLibraryManagerItemAdded; + _appHost.HasPendingRestartChanged -= OnAppHostHasPendingRestartChanged; + _appHost.HasUpdateAvailableChanged -= OnAppHostHasUpdateAvailableChanged; + _activityManager.EntryCreated -= OnActivityManagerEntryCreated; + + _disposed = true; } } } diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index eecbbea071..639a5e1aad 100644 --- a/Emby.Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -16,20 +16,32 @@ using Microsoft.Extensions.Logging; namespace Emby.Notifications { + /// + /// NotificationManager class. + /// public class NotificationManager : INotificationManager { private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly IServerConfigurationManager _config; - private INotificationService[] _services; - private INotificationTypeFactory[] _typeFactories; + private INotificationService[] _services = Array.Empty(); + private INotificationTypeFactory[] _typeFactories = Array.Empty(); - public NotificationManager(ILoggerFactory loggerFactory, IUserManager userManager, IServerConfigurationManager config) + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The user manager. + /// The server configuration manager. + public NotificationManager( + ILogger logger, + IUserManager userManager, + IServerConfigurationManager config) { + _logger = logger; _userManager = userManager; _config = config; - _logger = loggerFactory.CreateLogger(GetType().Name); } private NotificationOptions GetConfiguration() @@ -37,12 +49,14 @@ namespace Emby.Notifications return _config.GetConfiguration("notifications"); } + /// public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken) { return SendNotification(request, null, cancellationToken); } - public Task SendNotification(NotificationRequest request, BaseItem relatedItem, CancellationToken cancellationToken) + /// + public Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken) { var notificationType = request.NotificationType; @@ -64,7 +78,8 @@ namespace Emby.Notifications return Task.WhenAll(tasks); } - private Task SendNotification(NotificationRequest request, + private Task SendNotification( + NotificationRequest request, INotificationService service, IEnumerable users, string title, @@ -79,7 +94,7 @@ namespace Emby.Notifications return Task.WhenAll(tasks); } - private IEnumerable GetUserIds(NotificationRequest request, NotificationOption options) + private IEnumerable GetUserIds(NotificationRequest request, NotificationOption? options) { if (request.SendToUserMode.HasValue) { @@ -109,7 +124,8 @@ namespace Emby.Notifications return request.UserIds; } - private async Task SendNotification(NotificationRequest request, + private async Task SendNotification( + NotificationRequest request, INotificationService service, string title, string description, @@ -161,12 +177,14 @@ namespace Emby.Notifications return GetConfiguration().IsServiceEnabled(service.Name, notificationType); } + /// public void AddParts(IEnumerable services, IEnumerable notificationTypeFactories) { _services = services.ToArray(); _typeFactories = notificationTypeFactories.ToArray(); } + /// public List GetNotificationTypes() { var list = _typeFactories.Select(i => @@ -180,7 +198,6 @@ namespace Emby.Notifications _logger.LogError(ex, "Error in GetNotificationTypes"); return new List(); } - }).SelectMany(i => i).ToList(); var config = GetConfiguration(); @@ -193,13 +210,13 @@ namespace Emby.Notifications return list; } + /// public IEnumerable GetNotificationServices() { return _services.Select(i => new NameIdPair { Name = i.Name, Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture) - }).OrderBy(i => i.Name); } } diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index ed6918dba6..cc3fbb43f9 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -17,6 +17,7 @@ false true true + enable diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index c91506b85b..13733b60f8 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -9,7 +8,6 @@ using System.Text; using System.Threading.Tasks; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; -using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Entities; @@ -29,7 +27,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Activity { - public class ActivityLogEntryPoint : IServerEntryPoint + public sealed class ActivityLogEntryPoint : IServerEntryPoint { private readonly ILogger _logger; private readonly IInstallationManager _installationManager; @@ -39,7 +37,6 @@ namespace Emby.Server.Implementations.Activity private readonly ILocalizationManager _localization; private readonly ISubtitleManager _subManager; private readonly IUserManager _userManager; - private readonly IServerApplicationHost _appHost; private readonly IDeviceManager _deviceManager; /// @@ -64,8 +61,7 @@ namespace Emby.Server.Implementations.Activity ILocalizationManager localization, IInstallationManager installationManager, ISubtitleManager subManager, - IUserManager userManager, - IServerApplicationHost appHost) + IUserManager userManager) { _logger = logger; _sessionManager = sessionManager; @@ -76,7 +72,6 @@ namespace Emby.Server.Implementations.Activity _installationManager = installationManager; _subManager = subManager; _userManager = userManager; - _appHost = appHost; } public Task RunAsync() @@ -141,7 +136,7 @@ namespace Emby.Server.Implementations.Activity CultureInfo.InvariantCulture, _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, - Notifications.Notifications.GetItemName(e.Item)), + Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)), Type = "SubtitleDownloadFailure", ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture), ShortOverview = e.Exception.Message @@ -533,6 +528,7 @@ namespace Emby.Server.Implementations.Activity private void CreateLogEntry(ActivityLogEntry entry) => _activityManager.Create(entry); + /// public void Dispose() { _taskManager.TaskCompleted -= OnTaskCompleted; diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs index 6712c47828..ee10845cfa 100644 --- a/Emby.Server.Implementations/Activity/ActivityManager.cs +++ b/Emby.Server.Implementations/Activity/ActivityManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using MediaBrowser.Controller.Library; diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 633343bb6d..7be72319ea 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index c3cdcc2222..bc47817438 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration; namespace Emby.Server.Implementations.AppBase { /// - /// Provides a base class to hold common application paths used by both the Ui and Server. + /// Provides a base class to hold common application paths used by both the UI and Server. /// This can be subclassed to add application-specific paths. /// public abstract class BaseApplicationPaths : IApplicationPaths @@ -37,10 +37,7 @@ namespace Emby.Server.Implementations.AppBase /// The program data path. public string ProgramDataPath { get; } - /// - /// Gets the path to the web UI resources folder. - /// - /// The web UI resources path. + /// public string WebPath { get; } /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 3cf4d6c6df..c926896cf3 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Concurrent; @@ -44,6 +43,7 @@ using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; +using Emby.Server.Implementations.Services; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; @@ -100,8 +100,8 @@ using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Updates; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; +using MediaBrowser.Providers.Plugins.TheTvdb; using MediaBrowser.Providers.Subtitles; -using MediaBrowser.Providers.TV.TheTVDB; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; @@ -118,8 +118,12 @@ namespace Emby.Server.Implementations /// public abstract class ApplicationHost : IServerApplicationHost, IDisposable { - private SqliteUserRepository _userRepository; + /// + /// The environment variable prefixes to log at server startup. + /// + private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" }; + private SqliteUserRepository _userRepository; private SqliteDisplayPreferencesRepository _displayPreferencesRepository; /// @@ -167,10 +171,9 @@ namespace Emby.Server.Implementations public bool IsShuttingDown { get; private set; } /// - /// Gets or sets the logger. + /// Gets the logger. /// - /// The logger. - protected ILogger Logger { get; set; } + protected ILogger Logger { get; } private IPlugin[] _plugins; @@ -181,10 +184,9 @@ namespace Emby.Server.Implementations public IReadOnlyList Plugins => _plugins; /// - /// Gets or sets the logger factory. + /// Gets the logger factory. /// - /// The logger factory. - public ILoggerFactory LoggerFactory { get; protected set; } + protected ILoggerFactory LoggerFactory { get; } /// /// Gets or sets the application paths. @@ -226,11 +228,6 @@ namespace Emby.Server.Implementations /// public int HttpsPort { get; private set; } - /// - /// Gets the content root for the webhost. - /// - public string ContentRoot { get; private set; } - /// /// Gets the server configuration manager. /// @@ -315,8 +312,6 @@ namespace Emby.Server.Implementations private IMediaSourceManager MediaSourceManager { get; set; } - private readonly IConfiguration _configuration; - /// /// Gets the installation manager. /// @@ -354,11 +349,8 @@ namespace Emby.Server.Implementations IStartupOptions options, IFileSystem fileSystem, IImageEncoder imageEncoder, - INetworkManager networkManager, - IConfiguration configuration) + INetworkManager networkManager) { - _configuration = configuration; - XmlSerializer = new MyXmlSerializer(); NetworkManager = networkManager; @@ -574,7 +566,8 @@ namespace Emby.Server.Implementations } } - public async Task InitAsync(IServiceCollection serviceCollection) + /// + public async Task InitAsync(IServiceCollection serviceCollection, IConfiguration startupConfig) { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -607,13 +600,7 @@ namespace Emby.Server.Implementations DiscoverTypes(); - await RegisterResources(serviceCollection).ConfigureAwait(false); - - ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; - if (string.IsNullOrEmpty(ContentRoot)) - { - ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; - } + await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false); } public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) @@ -639,14 +626,14 @@ namespace Emby.Server.Implementations var response = context.Response; var localPath = context.Request.Path.ToString(); - var req = new WebSocketSharpRequest(request, response, request.Path, Logger); + var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger()); await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false); } /// - /// Registers resources that classes will depend on + /// Registers services/resources with the service collection that will be available via DI. /// - protected async Task RegisterResources(IServiceCollection serviceCollection) + protected async Task RegisterServices(IServiceCollection serviceCollection, IConfiguration startupConfig) { serviceCollection.AddMemoryCache(); @@ -655,16 +642,13 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ApplicationPaths); - serviceCollection.AddSingleton(_configuration); - serviceCollection.AddSingleton(JsonSerializer); - serviceCollection.AddSingleton(LoggerFactory); - serviceCollection.AddLogging(); - serviceCollection.AddSingleton(Logger); + // TODO: Support for injecting ILogger should be deprecated in favour of ILogger and this removed + serviceCollection.AddSingleton(Logger); serviceCollection.AddSingleton(FileSystemManager); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); HttpClient = new HttpClientManager.HttpClientManager( ApplicationPaths, @@ -749,7 +733,7 @@ namespace Emby.Server.Implementations ProcessFactory, LocalizationManager, () => SubtitleEncoder, - _configuration, + startupConfig, StartupOptions.FFmpegPath); serviceCollection.AddSingleton(MediaEncoder); @@ -767,20 +751,9 @@ namespace Emby.Server.Implementations CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); - HttpServer = new HttpListenerHost( - this, - LoggerFactory.CreateLogger(), - ServerConfigurationManager, - _configuration, - NetworkManager, - JsonSerializer, - XmlSerializer, - CreateHttpListener()) - { - GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading") - }; - - serviceCollection.AddSingleton(HttpServer); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); serviceCollection.AddSingleton(ImageProcessor); @@ -806,7 +779,18 @@ namespace Emby.Server.Implementations ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); serviceCollection.AddSingleton(ChannelManager); - SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, this, AuthenticationRepository, DeviceManager, MediaSourceManager); + SessionManager = new SessionManager( + LoggerFactory.CreateLogger(), + UserDataManager, + LibraryManager, + UserManager, + musicManager, + DtoService, + ImageProcessor, + this, + AuthenticationRepository, + DeviceManager, + MediaSourceManager); serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton( @@ -823,15 +807,23 @@ namespace Emby.Server.Implementations UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); serviceCollection.AddSingleton(UserViewManager); - NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); + NotificationManager = new NotificationManager( + LoggerFactory.CreateLogger(), + UserManager, + ServerConfigurationManager); serviceCollection.AddSingleton(NotificationManager); serviceCollection.AddSingleton(new DeviceDiscovery(ServerConfigurationManager)); - ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); + ChapterManager = new ChapterManager(ItemRepository); serviceCollection.AddSingleton(ChapterManager); - EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); + EncodingManager = new MediaEncoder.EncodingManager( + LoggerFactory.CreateLogger(), + FileSystemManager, + MediaEncoder, + ChapterManager, + LibraryManager); serviceCollection.AddSingleton(EncodingManager); var activityLogRepo = GetActivityLogRepository(); @@ -874,6 +866,14 @@ namespace Emby.Server.Implementations ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; } + /// + /// Create services registered with the service container that need to be initialized at application startup. + /// + public void InitializeServices() + { + HttpServer = Resolve(); + } + public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) { // Distinct these to prevent users from reporting problems that aren't actually problems @@ -881,6 +881,18 @@ namespace Emby.Server.Implementations .GetCommandLineArgs() .Distinct(); + // Get all relevant environment variables + var allEnvVars = Environment.GetEnvironmentVariables(); + var relevantEnvVars = new Dictionary(); + foreach (var key in allEnvVars.Keys) + { + if (_relevantEnvVarPrefixes.Any(prefix => key.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) + { + relevantEnvVars.Add(key, allEnvVars[key]); + } + } + + logger.LogInformation("Environment Variables: {EnvVars}", relevantEnvVars); logger.LogInformation("Arguments: {Args}", commandLineArgs); logger.LogInformation("Operating system: {OS}", OperatingSystem.Name); logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture); @@ -990,48 +1002,12 @@ namespace Emby.Server.Implementations AuthenticatedAttribute.AuthService = AuthService; } - private async void PluginInstalled(object sender, GenericEventArgs args) - { - string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name); - var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories) - .Select(Assembly.LoadFrom) - .SelectMany(x => x.ExportedTypes) - .Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType) - .ToArray(); - - int oldLen = _allConcreteTypes.Length; - Array.Resize(ref _allConcreteTypes, oldLen + types.Length); - types.CopyTo(_allConcreteTypes, oldLen); - - var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin))) - .Select(CreateInstanceSafe) - .Where(x => x != null) - .Cast() - .Select(LoadPlugin) - .Where(x => x != null) - .ToArray(); - - oldLen = _plugins.Length; - Array.Resize(ref _plugins, oldLen + plugins.Length); - plugins.CopyTo(_plugins, oldLen); - - var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint))) - .Select(CreateInstanceSafe) - .Where(x => x != null) - .Cast() - .ToList(); - - await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false); - await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false); - } - /// /// Finds the parts. /// public void FindParts() { InstallationManager = ServiceProvider.GetService(); - InstallationManager.PluginInstalled += PluginInstalled; if (!ServerConfigurationManager.Configuration.IsPortAuthorized) { @@ -1045,7 +1021,7 @@ namespace Emby.Server.Implementations .Where(i => i != null) .ToArray(); - HttpServer.Init(GetExports(false), GetExports(), GetUrlPrefixes()); + HttpServer.Init(GetExportTypes(), GetExports(), GetUrlPrefixes()); LibraryManager.AddParts( GetExports(), @@ -1139,7 +1115,7 @@ namespace Emby.Server.Implementations { exportedTypes = ass.GetExportedTypes(); } - catch (TypeLoadException ex) + catch (FileNotFoundException ex) { Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); continue; @@ -1179,8 +1155,6 @@ namespace Emby.Server.Implementations }); } - protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(Logger); - private CertificateInfo GetCertificateInfo(bool generateCertificate) { // Custom cert @@ -1490,18 +1464,10 @@ namespace Emby.Server.Implementations public string GetLocalApiUrl(ReadOnlySpan host) { var url = new StringBuilder(64); - if (EnableHttps) - { - url.Append("https://"); - } - else - { - url.Append("http://"); - } - - url.Append(host) + url.Append(EnableHttps ? "https://" : "http://") + .Append(host) .Append(':') - .Append(HttpPort); + .Append(EnableHttps ? HttpsPort : HttpPort); string baseUrl = ServerConfigurationManager.Configuration.BaseUrl; if (baseUrl.Length != 0) @@ -1774,7 +1740,7 @@ namespace Emby.Server.Implementations } _userRepository?.Dispose(); - _displayPreferencesRepository.Dispose(); + _displayPreferencesRepository?.Dispose(); } _userRepository = null; diff --git a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs index 15aee63a05..93000ae127 100644 --- a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs +++ b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using MediaBrowser.Common.Configuration; diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index f5da0d0183..96096e142a 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -1,51 +1,48 @@ using System; using MediaBrowser.Controller; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Browser { /// - /// Class BrowserLauncher. + /// Assists in opening application URLs in an external browser. /// public static class BrowserLauncher { /// - /// Opens the dashboard page. - /// - /// The page. - /// The app host. - private static void OpenDashboardPage(string page, IServerApplicationHost appHost) - { - var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page; - - OpenUrl(appHost, url); - } - - /// - /// Opens the web client. + /// Opens the home page of the web client. /// /// The app host. public static void OpenWebApp(IServerApplicationHost appHost) { - OpenDashboardPage("index.html", appHost); + TryOpenUrl(appHost, "/web/index.html"); } /// - /// Opens the URL. + /// Opens the swagger API page. /// - /// The application host instance. + /// The app host. + public static void OpenSwaggerPage(IServerApplicationHost appHost) + { + TryOpenUrl(appHost, "/swagger/index.html"); + } + + /// + /// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored. + /// + /// The application host. /// The URL. - private static void OpenUrl(IServerApplicationHost appHost, string url) + private static void TryOpenUrl(IServerApplicationHost appHost, string url) { try { - appHost.LaunchUrl(url); + string baseUrl = appHost.GetLocalApiUrl("localhost"); + appHost.LaunchUrl(baseUrl + url); } - catch (NotSupportedException) - { - - } - catch (Exception) + catch (Exception ex) { + var logger = appHost.Resolve(); + logger?.LogError(ex, "Failed to open browser window with URL {URL}", url); } } } diff --git a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs index aae416b374..6016fed079 100644 --- a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs +++ b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs index fe64f1b157..62aeb9bcb9 100644 --- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs +++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index de2e123af3..6e1baddfed 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Concurrent; diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index 6cbd04fea9..266d539d0a 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Linq; diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index 03e6abcfba..367efcb134 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -10,6 +9,7 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.Channels { @@ -19,23 +19,30 @@ namespace Emby.Server.Implementations.Channels private readonly IUserManager _userManager; private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; - public RefreshChannelsScheduledTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager) + public RefreshChannelsScheduledTask( + IChannelManager channelManager, + IUserManager userManager, + ILogger logger, + ILibraryManager libraryManager, + ILocalizationManager localization) { _channelManager = channelManager; _userManager = userManager; _logger = logger; _libraryManager = libraryManager; + _localization = localization; } /// - public string Name => "Refresh Channels"; + public string Name => _localization.GetLocalizedString("TasksRefreshChannels"); /// - public string Description => "Refreshes internet channel information."; + public string Description => _localization.GetLocalizedString("TasksRefreshChannelsDescription"); /// - public string Category => "Internet Channels"; + public string Category => _localization.GetLocalizedString("TasksChannelsCategory"); /// public bool IsHidden => ((ChannelManager)_channelManager).Channels.Length == 0; diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index 8b14079844..21ba0288ec 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index efdef8481b..3219528749 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -348,7 +347,10 @@ namespace Emby.Server.Implementations.Collections private readonly IServerConfigurationManager _config; private readonly ILogger _logger; - public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger) + public CollectionManagerEntryPoint( + ICollectionManager collectionManager, + IServerConfigurationManager config, + ILogger logger) { _collectionManager = (CollectionManager)collectionManager; _config = config; diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index 30b654886b..f407317ec7 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Configuration var newPath = newConfig.MetadataPath; if (!string.IsNullOrWhiteSpace(newPath) - && !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal)) + && !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal)) { // Validate if (!Directory.Exists(newPath)) diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 2ea7ff6e91..20bdd18e70 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,16 +1,27 @@ using System.Collections.Generic; +using Emby.Server.Implementations.HttpServer; +using Emby.Server.Implementations.Updates; +using MediaBrowser.Providers.Music; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations { + /// + /// Static class containing the default configuration options for the web server. + /// public static class ConfigurationOptions { - public static Dictionary Configuration => new Dictionary + /// + /// Gets a new copy of the default configuration options. + /// + public static Dictionary DefaultConfiguration => new Dictionary { - { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, - { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }, + { HostWebClientKey, bool.TrueString }, + { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, + { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" }, { FfmpegProbeSizeKey, "1G" }, - { FfmpegAnalyzeDurationKey, "200M" } + { FfmpegAnalyzeDurationKey, "200M" }, + { PlaylistsAllowDuplicatesKey, bool.TrueString } }; } } diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index b7f6438193..0654132f41 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 8a5387e9b4..37c678a5d1 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Threading; @@ -15,7 +14,7 @@ namespace Emby.Server.Implementations.Data private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; - public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger) + public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger) { _libraryManager = libraryManager; _logger = logger; diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 2c2f19cd30..5c094ddd2d 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 8087419ceb..d474f1c6ba 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index 55c24ccc05..e7c5270b9f 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -288,7 +287,7 @@ namespace Emby.Server.Implementations.Data } } - public static void TryBind(this IStatement statement, string name, byte[] value) + public static void TryBind(this IStatement statement, string name, ReadOnlySpan value) { if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam)) { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index c514846e58..33ff74bb58 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -454,7 +454,7 @@ namespace Emby.Server.Implementations.Data private static string GetSaveItemCommandText() { - var saveColumns = new [] + var saveColumns = new[] { "guid", "type", @@ -560,7 +560,7 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(item)); } - SaveItems(new [] { item }, cancellationToken); + SaveItems(new[] { item }, cancellationToken); } public void SaveImages(BaseItem item) @@ -1622,7 +1622,7 @@ namespace Emby.Server.Implementations.Data { IEnumerable GetLockedFields(string s) { - foreach (var i in s.Split(new [] { '|' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var i in s.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)) { if (Enum.TryParse(i, true, out MetadataFields parsedValue)) { @@ -1818,7 +1818,7 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - item.ProductionLocations = reader.GetString(index).Split(new [] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); + item.ProductionLocations = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); } index++; } @@ -2006,7 +2006,7 @@ namespace Emby.Server.Implementations.Data /// /// Saves the chapters. /// - public void SaveChapters(Guid id, List chapters) + public void SaveChapters(Guid id, IReadOnlyList chapters) { CheckDisposed(); @@ -2035,22 +2035,24 @@ namespace Emby.Server.Implementations.Data } } - private void InsertChapters(byte[] idBlob, List chapters, IDatabaseConnection db) + private void InsertChapters(byte[] idBlob, IReadOnlyList chapters, IDatabaseConnection db) { var startIndex = 0; var limit = 100; var chapterIndex = 0; + const string StartInsertText = "insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values "; + var insertText = new StringBuilder(StartInsertText, 256); + while (startIndex < chapters.Count) { - var insertText = new StringBuilder("insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values "); - var endIndex = Math.Min(chapters.Count, startIndex + limit); for (var i = startIndex; i < endIndex; i++) { insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture)); } + insertText.Length -= 1; // Remove last , using (var statement = PrepareStatement(db, insertText.ToString())) @@ -2077,6 +2079,7 @@ namespace Emby.Server.Implementations.Data } startIndex += limit; + insertText.Length = StartInsertText.Length; } } @@ -2897,8 +2900,8 @@ namespace Emby.Server.Implementations.Data BindSimilarParams(query, statement); BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } @@ -2914,29 +2917,30 @@ namespace Emby.Server.Implementations.Data private string GetOrderByText(InternalItemsQuery query) { var orderBy = query.OrderBy; - if (string.IsNullOrEmpty(query.SearchTerm)) + bool hasSimilar = query.SimilarTo != null; + bool hasSearch = !string.IsNullOrEmpty(query.SearchTerm); + + if (hasSimilar || hasSearch) { - int oldLen = orderBy.Count; - if (oldLen == 0 && query.SimilarTo != null) + List<(string, SortOrder)> prepend = new List<(string, SortOrder)>(4); + if (hasSearch) { - var arr = new (string, SortOrder)[oldLen + 2]; - orderBy.CopyTo(arr, 0); - arr[oldLen] = ("SimilarityScore", SortOrder.Descending); - arr[oldLen + 1] = (ItemSortBy.Random, SortOrder.Ascending); - query.OrderBy = arr; + prepend.Add(("SearchScore", SortOrder.Descending)); + prepend.Add((ItemSortBy.SortName, SortOrder.Ascending)); } - } - else - { - query.OrderBy = new[] + + if (hasSimilar) { - ("SearchScore", SortOrder.Descending), - (ItemSortBy.SortName, SortOrder.Ascending) - }; + prepend.Add(("SimilarityScore", SortOrder.Descending)); + prepend.Add((ItemSortBy.Random, SortOrder.Ascending)); + } + + var arr = new (string, SortOrder)[prepend.Count + orderBy.Count]; + prepend.CopyTo(arr, 0); + orderBy.CopyTo(arr, prepend.Count); + orderBy = query.OrderBy = arr; } - - - if (orderBy.Count == 0) + else if (orderBy.Count == 0) { return string.Empty; } @@ -3265,8 +3269,8 @@ namespace Emby.Server.Implementations.Data BindSimilarParams(query, statement); BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); foreach (var row in statement.ExecuteQuery()) { @@ -3287,8 +3291,8 @@ namespace Emby.Server.Implementations.Data BindSimilarParams(query, statement); BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } @@ -3311,7 +3315,7 @@ namespace Emby.Server.Implementations.Data for (int i = 0; i < str.Length; i++) { - if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i])))) + if (!char.IsLetter(str[i]) && !char.IsNumber(str[i])) { return false; } @@ -3335,7 +3339,7 @@ namespace Emby.Server.Implementations.Data return IsAlphaNumeric(value); } - private List GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "") + private List GetWhereClauses(InternalItemsQuery query, IStatement statement) { if (query.IsResumable ?? false) { @@ -3347,27 +3351,27 @@ namespace Emby.Server.Implementations.Data if (query.IsHD.HasValue) { - var threshold = 1200; + const int Threshold = 1200; if (query.IsHD.Value) { - minWidth = threshold; + minWidth = Threshold; } else { - maxWidth = threshold - 1; + maxWidth = Threshold - 1; } } if (query.Is4K.HasValue) { - var threshold = 3800; + const int Threshold = 3800; if (query.Is4K.Value) { - minWidth = threshold; + minWidth = Threshold; } else { - maxWidth = threshold - 1; + maxWidth = Threshold - 1; } } @@ -3376,93 +3380,61 @@ namespace Emby.Server.Implementations.Data if (minWidth.HasValue) { whereClauses.Add("Width>=@MinWidth"); - if (statement != null) - { - statement.TryBind("@MinWidth", minWidth); - } + statement?.TryBind("@MinWidth", minWidth); } + if (query.MinHeight.HasValue) { whereClauses.Add("Height>=@MinHeight"); - if (statement != null) - { - statement.TryBind("@MinHeight", query.MinHeight); - } + statement?.TryBind("@MinHeight", query.MinHeight); } + if (maxWidth.HasValue) { whereClauses.Add("Width<=@MaxWidth"); - if (statement != null) - { - statement.TryBind("@MaxWidth", maxWidth); - } + statement?.TryBind("@MaxWidth", maxWidth); } + if (query.MaxHeight.HasValue) { whereClauses.Add("Height<=@MaxHeight"); - if (statement != null) - { - statement.TryBind("@MaxHeight", query.MaxHeight); - } + statement?.TryBind("@MaxHeight", query.MaxHeight); } if (query.IsLocked.HasValue) { whereClauses.Add("IsLocked=@IsLocked"); - if (statement != null) - { - statement.TryBind("@IsLocked", query.IsLocked); - } + statement?.TryBind("@IsLocked", query.IsLocked); } var tags = query.Tags.ToList(); var excludeTags = query.ExcludeTags.ToList(); - if (query.IsMovie ?? false) + if (query.IsMovie == true) { - var alternateTypes = new List(); - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name)) + if (query.IncludeItemTypes.Length == 0 + || query.IncludeItemTypes.Contains(nameof(Movie)) + || query.IncludeItemTypes.Contains(nameof(Trailer))) { - alternateTypes.Add(typeof(Movie).FullName); - } - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) - { - alternateTypes.Add(typeof(Trailer).FullName); - } - - var programAttribtues = new List(); - if (alternateTypes.Count == 0) - { - programAttribtues.Add("IsMovie=@IsMovie"); + whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)"); } else { - programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)"); + whereClauses.Add("IsMovie=@IsMovie"); } - if (statement != null) - { - statement.TryBind("@IsMovie", true); - } - - whereClauses.Add("(" + string.Join(" OR ", programAttribtues) + ")"); + statement?.TryBind("@IsMovie", true); } else if (query.IsMovie.HasValue) { whereClauses.Add("IsMovie=@IsMovie"); - if (statement != null) - { - statement.TryBind("@IsMovie", query.IsMovie); - } + statement?.TryBind("@IsMovie", query.IsMovie); } if (query.IsSeries.HasValue) { whereClauses.Add("IsSeries=@IsSeries"); - if (statement != null) - { - statement.TryBind("@IsSeries", query.IsSeries); - } + statement?.TryBind("@IsSeries", query.IsSeries); } if (query.IsSports.HasValue) @@ -3514,27 +3486,10 @@ namespace Emby.Server.Implementations.Data if (query.IsFolder.HasValue) { whereClauses.Add("IsFolder=@IsFolder"); - if (statement != null) - { - statement.TryBind("@IsFolder", query.IsFolder); - } + statement?.TryBind("@IsFolder", query.IsFolder); } var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); - if (includeTypes.Length == 1) - { - whereClauses.Add("type=@type"); - if (statement != null) - { - statement.TryBind("@type", includeTypes[0]); - } - } - else if (includeTypes.Length > 1) - { - var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'")); - whereClauses.Add($"type in ({inClause})"); - } - // Only specify excluded types if no included types are specified if (includeTypes.Length == 0) { @@ -3542,10 +3497,7 @@ namespace Emby.Server.Implementations.Data if (excludeTypes.Length == 1) { whereClauses.Add("type<>@type"); - if (statement != null) - { - statement.TryBind("@type", excludeTypes[0]); - } + statement?.TryBind("@type", excludeTypes[0]); } else if (excludeTypes.Length > 1) { @@ -3553,14 +3505,21 @@ namespace Emby.Server.Implementations.Data whereClauses.Add($"type not in ({inClause})"); } } + else if (includeTypes.Length == 1) + { + whereClauses.Add("type=@type"); + statement?.TryBind("@type", includeTypes[0]); + } + else if (includeTypes.Length > 1) + { + var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'")); + whereClauses.Add($"type in ({inClause})"); + } if (query.ChannelIds.Length == 1) { whereClauses.Add("ChannelId=@ChannelId"); - if (statement != null) - { - statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture)); - } + statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture)); } else if (query.ChannelIds.Length > 1) { @@ -3571,98 +3530,65 @@ namespace Emby.Server.Implementations.Data if (!query.ParentId.Equals(Guid.Empty)) { whereClauses.Add("ParentId=@ParentId"); - if (statement != null) - { - statement.TryBind("@ParentId", query.ParentId); - } + statement?.TryBind("@ParentId", query.ParentId); } if (!string.IsNullOrWhiteSpace(query.Path)) { whereClauses.Add("Path=@Path"); - if (statement != null) - { - statement.TryBind("@Path", GetPathToSave(query.Path)); - } + statement?.TryBind("@Path", GetPathToSave(query.Path)); } if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) { whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey"); - if (statement != null) - { - statement.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey); - } + statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey); } if (query.MinCommunityRating.HasValue) { whereClauses.Add("CommunityRating>=@MinCommunityRating"); - if (statement != null) - { - statement.TryBind("@MinCommunityRating", query.MinCommunityRating.Value); - } + statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value); } if (query.MinIndexNumber.HasValue) { whereClauses.Add("IndexNumber>=@MinIndexNumber"); - if (statement != null) - { - statement.TryBind("@MinIndexNumber", query.MinIndexNumber.Value); - } + statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value); } if (query.MinDateCreated.HasValue) { whereClauses.Add("DateCreated>=@MinDateCreated"); - if (statement != null) - { - statement.TryBind("@MinDateCreated", query.MinDateCreated.Value); - } + statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value); } if (query.MinDateLastSaved.HasValue) { whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)"); - if (statement != null) - { - statement.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value); - } + statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value); } if (query.MinDateLastSavedForUser.HasValue) { whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)"); - if (statement != null) - { - statement.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value); - } + statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value); } if (query.IndexNumber.HasValue) { whereClauses.Add("IndexNumber=@IndexNumber"); - if (statement != null) - { - statement.TryBind("@IndexNumber", query.IndexNumber.Value); - } + statement?.TryBind("@IndexNumber", query.IndexNumber.Value); } if (query.ParentIndexNumber.HasValue) { whereClauses.Add("ParentIndexNumber=@ParentIndexNumber"); - if (statement != null) - { - statement.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value); - } + statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value); } if (query.ParentIndexNumberNotEquals.HasValue) { whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)"); - if (statement != null) - { - statement.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value); - } + statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value); } var minEndDate = query.MinEndDate; @@ -3683,73 +3609,59 @@ namespace Emby.Server.Implementations.Data if (minEndDate.HasValue) { whereClauses.Add("EndDate>=@MinEndDate"); - if (statement != null) - { - statement.TryBind("@MinEndDate", minEndDate.Value); - } + statement?.TryBind("@MinEndDate", minEndDate.Value); } if (maxEndDate.HasValue) { whereClauses.Add("EndDate<=@MaxEndDate"); - if (statement != null) - { - statement.TryBind("@MaxEndDate", maxEndDate.Value); - } + statement?.TryBind("@MaxEndDate", maxEndDate.Value); } if (query.MinStartDate.HasValue) { whereClauses.Add("StartDate>=@MinStartDate"); - if (statement != null) - { - statement.TryBind("@MinStartDate", query.MinStartDate.Value); - } + statement?.TryBind("@MinStartDate", query.MinStartDate.Value); } if (query.MaxStartDate.HasValue) { whereClauses.Add("StartDate<=@MaxStartDate"); - if (statement != null) - { - statement.TryBind("@MaxStartDate", query.MaxStartDate.Value); - } + statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value); } if (query.MinPremiereDate.HasValue) { whereClauses.Add("PremiereDate>=@MinPremiereDate"); - if (statement != null) - { - statement.TryBind("@MinPremiereDate", query.MinPremiereDate.Value); - } + statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value); } + if (query.MaxPremiereDate.HasValue) { whereClauses.Add("PremiereDate<=@MaxPremiereDate"); - if (statement != null) - { - statement.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value); - } + statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value); } - if (query.TrailerTypes.Length > 0) + var trailerTypes = query.TrailerTypes; + int trailerTypesLen = trailerTypes.Length; + if (trailerTypesLen > 0) { - var clauses = new List(); - var index = 0; - foreach (var type in query.TrailerTypes) + const string Or = " OR "; + StringBuilder clause = new StringBuilder("(", trailerTypesLen * 32); + for (int i = 0; i < trailerTypesLen; i++) { - var paramName = "@TrailerTypes" + index; - - clauses.Add("TrailerTypes like " + paramName); - if (statement != null) - { - statement.TryBind(paramName, "%" + type + "%"); - } - index++; + var paramName = "@TrailerTypes" + i; + clause.Append("TrailerTypes like ") + .Append(paramName) + .Append(Or); + statement?.TryBind(paramName, "%" + trailerTypes[i] + "%"); } - var clause = "(" + string.Join(" OR ", clauses) + ")"; - whereClauses.Add(clause); + + // Remove last " OR " + clause.Length -= Or.Length; + clause.Append(')'); + + whereClauses.Add(clause.ToString()); } if (query.IsAiring.HasValue) @@ -3757,24 +3669,15 @@ namespace Emby.Server.Implementations.Data if (query.IsAiring.Value) { whereClauses.Add("StartDate<=@MaxStartDate"); - if (statement != null) - { - statement.TryBind("@MaxStartDate", DateTime.UtcNow); - } + statement?.TryBind("@MaxStartDate", DateTime.UtcNow); whereClauses.Add("EndDate>=@MinEndDate"); - if (statement != null) - { - statement.TryBind("@MinEndDate", DateTime.UtcNow); - } + statement?.TryBind("@MinEndDate", DateTime.UtcNow); } else { whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)"); - if (statement != null) - { - statement.TryBind("@IsAiringDate", DateTime.UtcNow); - } + statement?.TryBind("@IsAiringDate", DateTime.UtcNow); } } @@ -3789,13 +3692,10 @@ namespace Emby.Server.Implementations.Data var paramName = "@PersonId" + index; clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))"); - - if (statement != null) - { - statement.TryBind(paramName, personId.ToByteArray()); - } + statement?.TryBind(paramName, personId.ToByteArray()); index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3803,47 +3703,31 @@ namespace Emby.Server.Implementations.Data if (!string.IsNullOrWhiteSpace(query.Person)) { whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)"); - if (statement != null) - { - statement.TryBind("@PersonName", query.Person); - } + statement?.TryBind("@PersonName", query.Person); } if (!string.IsNullOrWhiteSpace(query.MinSortName)) { whereClauses.Add("SortName>=@MinSortName"); - if (statement != null) - { - statement.TryBind("@MinSortName", query.MinSortName); - } + statement?.TryBind("@MinSortName", query.MinSortName); } if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId)) { whereClauses.Add("ExternalSeriesId=@ExternalSeriesId"); - if (statement != null) - { - statement.TryBind("@ExternalSeriesId", query.ExternalSeriesId); - } + statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId); } if (!string.IsNullOrWhiteSpace(query.ExternalId)) { whereClauses.Add("ExternalId=@ExternalId"); - if (statement != null) - { - statement.TryBind("@ExternalId", query.ExternalId); - } + statement?.TryBind("@ExternalId", query.ExternalId); } if (!string.IsNullOrWhiteSpace(query.Name)) { whereClauses.Add("CleanName=@Name"); - - if (statement != null) - { - statement.TryBind("@Name", GetCleanValue(query.Name)); - } + statement?.TryBind("@Name", GetCleanValue(query.Name)); } // These are the same, for now @@ -3862,28 +3746,21 @@ namespace Emby.Server.Implementations.Data if (!string.IsNullOrWhiteSpace(query.NameStartsWith)) { whereClauses.Add("SortName like @NameStartsWith"); - if (statement != null) - { - statement.TryBind("@NameStartsWith", query.NameStartsWith + "%"); - } + statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%"); } + if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater)) { whereClauses.Add("SortName >= @NameStartsWithOrGreater"); // lowercase this because SortName is stored as lowercase - if (statement != null) - { - statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant()); - } + statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant()); } + if (!string.IsNullOrWhiteSpace(query.NameLessThan)) { whereClauses.Add("SortName < @NameLessThan"); // lowercase this because SortName is stored as lowercase - if (statement != null) - { - statement.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant()); - } + statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant()); } if (query.ImageTypes.Length > 0) @@ -3899,18 +3776,12 @@ namespace Emby.Server.Implementations.Data if (query.IsLiked.Value) { whereClauses.Add("rating>=@UserRating"); - if (statement != null) - { - statement.TryBind("@UserRating", UserItemData.MinLikeValue); - } + statement?.TryBind("@UserRating", UserItemData.MinLikeValue); } else { whereClauses.Add("(rating is null or rating<@UserRating)"); - if (statement != null) - { - statement.TryBind("@UserRating", UserItemData.MinLikeValue); - } + statement?.TryBind("@UserRating", UserItemData.MinLikeValue); } } @@ -3924,10 +3795,8 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)"); } - if (statement != null) - { - statement.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value); - } + + statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value); } if (query.IsFavorite.HasValue) @@ -3940,10 +3809,8 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)"); } - if (statement != null) - { - statement.TryBind("@IsFavorite", query.IsFavorite.Value); - } + + statement?.TryBind("@IsFavorite", query.IsFavorite.Value); } if (EnableJoinUserData(query)) @@ -3972,10 +3839,8 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("(played is null or played=@IsPlayed)"); } - if (statement != null) - { - statement.TryBind("@IsPlayed", query.IsPlayed.Value); - } + + statement?.TryBind("@IsPlayed", query.IsPlayed.Value); } } } @@ -4007,6 +3872,7 @@ namespace Emby.Server.Implementations.Data } index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4026,6 +3892,7 @@ namespace Emby.Server.Implementations.Data } index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4759,18 +4626,22 @@ namespace Emby.Server.Implementations.Data { list.Add(typeof(Person).Name); } + if (IsTypeInQuery(typeof(Genre).Name, query)) { list.Add(typeof(Genre).Name); } + if (IsTypeInQuery(typeof(MusicGenre).Name, query)) { list.Add(typeof(MusicGenre).Name); } + if (IsTypeInQuery(typeof(MusicArtist).Name, query)) { list.Add(typeof(MusicArtist).Name); } + if (IsTypeInQuery(typeof(Studio).Name, query)) { list.Add(typeof(Studio).Name); @@ -4844,7 +4715,7 @@ namespace Emby.Server.Implementations.Data return false; } - private static readonly Type[] KnownTypes = + private static readonly Type[] _knownTypes = { typeof(LiveTvProgram), typeof(LiveTvChannel), @@ -4913,7 +4784,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var t in KnownTypes) + foreach (var t in _knownTypes) { dict[t.Name] = new[] { t.FullName }; } @@ -4925,9 +4796,9 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } // Not crazy about having this all the way down here, but at least it's in one place - readonly Dictionary _types = GetTypeMapDictionary(); + private readonly Dictionary _types = GetTypeMapDictionary(); - private IEnumerable MapIncludeItemTypes(string value) + private string[] MapIncludeItemTypes(string value) { if (_types.TryGetValue(value, out string[] result)) { @@ -4942,7 +4813,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return Array.Empty(); } - public void DeleteItem(Guid id, CancellationToken cancellationToken) + public void DeleteItem(Guid id) { if (id == Guid.Empty) { @@ -4978,7 +4849,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - private void ExecuteWithSingleParam(IDatabaseConnection db, string query, byte[] value) + private void ExecuteWithSingleParam(IDatabaseConnection db, string query, ReadOnlySpan value) { using (var statement = PrepareStatement(db, query)) { @@ -5008,6 +4879,11 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += " order by ListOrder"; + if (query.Limit > 0) + { + commandText += " LIMIT " + query.Limit; + } + using (var connection = GetConnection(true)) { var list = new List(); @@ -5046,6 +4922,11 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += " order by ListOrder"; + if (query.Limit > 0) + { + commandText += " LIMIT " + query.Limit; + } + using (var connection = GetConnection(true)) { var list = new List(); @@ -5528,6 +5409,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { GetWhereClauses(typeSubQuery, null); } + BindSimilarParams(query, statement); BindSearchParams(query, statement); GetWhereClauses(innerQuery, statement); @@ -5569,7 +5451,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) - .ToLookup(i => i); + .ToLookup(x => x); foreach (var type in allTypes) { @@ -5611,32 +5493,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return counts; } - private List> GetItemValuesToSave(BaseItem item, List inheritedTags) + private List<(int, string)> GetItemValuesToSave(BaseItem item, List inheritedTags) { - var list = new List>(); + var list = new List<(int, string)>(); if (item is IHasArtist hasArtist) { - list.AddRange(hasArtist.Artists.Select(i => new Tuple(0, i))); + list.AddRange(hasArtist.Artists.Select(i => (0, i))); } if (item is IHasAlbumArtist hasAlbumArtist) { - list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => new Tuple(1, i))); + list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (1, i))); } - list.AddRange(item.Genres.Select(i => new Tuple(2, i))); - list.AddRange(item.Studios.Select(i => new Tuple(3, i))); - list.AddRange(item.Tags.Select(i => new Tuple(4, i))); + list.AddRange(item.Genres.Select(i => (2, i))); + list.AddRange(item.Studios.Select(i => (3, i))); + list.AddRange(item.Tags.Select(i => (4, i))); // keywords was 5 - list.AddRange(inheritedTags.Select(i => new Tuple(6, i))); + list.AddRange(inheritedTags.Select(i => (6, i))); return list; } - private void UpdateItemValues(Guid itemId, List> values, IDatabaseConnection db) + private void UpdateItemValues(Guid itemId, List<(int, string)> values, IDatabaseConnection db) { if (itemId.Equals(Guid.Empty)) { @@ -5658,32 +5540,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type InsertItemValues(guidBlob, values, db); } - private void InsertItemValues(byte[] idBlob, List> values, IDatabaseConnection db) + private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db) { + const int Limit = 100; var startIndex = 0; - var limit = 100; while (startIndex < values.Count) { var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values "); - var endIndex = Math.Min(values.Count, startIndex + limit); - var isSubsequentRow = false; + var endIndex = Math.Min(values.Count, startIndex + Limit); for (var i = startIndex; i < endIndex; i++) { - if (isSubsequentRow) - { - insertText.Append(','); - } - insertText.AppendFormat( CultureInfo.InvariantCulture, - "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})", + "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),", i); - isSubsequentRow = true; } + // Remove last comma + insertText.Length--; + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", idBlob); @@ -5711,7 +5589,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.MoveNext(); } - startIndex += limit; + startIndex += Limit; } } @@ -5746,28 +5624,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type private void InsertPeople(byte[] idBlob, List people, IDatabaseConnection db) { + const int Limit = 100; var startIndex = 0; - var limit = 100; var listIndex = 0; while (startIndex < people.Count) { var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values "); - var endIndex = Math.Min(people.Count, startIndex + limit); - var isSubsequentRow = false; - + var endIndex = Math.Min(people.Count, startIndex + Limit); for (var i = startIndex; i < endIndex; i++) { - if (isSubsequentRow) - { - insertText.Append(','); - } - - insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture)); - isSubsequentRow = true; + insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture)); } + // Remove last comma + insertText.Length--; + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", idBlob); @@ -5791,16 +5664,17 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.MoveNext(); } - startIndex += limit; + startIndex += Limit; } } private PersonInfo GetPerson(IReadOnlyList reader) { - var item = new PersonInfo(); - - item.ItemId = reader.GetGuid(0); - item.Name = reader.GetString(1); + var item = new PersonInfo + { + ItemId = reader.GetGuid(0), + Name = reader.GetString(1) + }; if (!reader.IsDBNull(2)) { @@ -5907,20 +5781,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type private void InsertMediaStreams(byte[] idBlob, List streams, IDatabaseConnection db) { + const int Limit = 10; var startIndex = 0; - var limit = 10; while (startIndex < streams.Count) { - var insertText = new StringBuilder(string.Format("insert into mediastreams ({0}) values ", string.Join(",", _mediaStreamSaveColumns))); + var insertText = new StringBuilder("insert into mediastreams ("); + foreach (var column in _mediaStreamSaveColumns) + { + insertText.Append(column).Append(','); + } - var endIndex = Math.Min(streams.Count, startIndex + limit); + // Remove last comma + insertText.Length--; + insertText.Append(") values "); + + var endIndex = Math.Min(streams.Count, startIndex + Limit); for (var i = startIndex; i < endIndex; i++) { if (i != startIndex) { - insertText.Append(","); + insertText.Append(','); } var index = i.ToString(CultureInfo.InvariantCulture); @@ -5928,11 +5810,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type foreach (var column in _mediaStreamSaveColumns.Skip(1)) { - insertText.Append("@" + column + index + ","); + insertText.Append('@').Append(column).Append(index).Append(','); } + insertText.Length -= 1; // Remove the last comma - insertText.Append(")"); + insertText.Append(')'); } using (var statement = PrepareStatement(db, insertText.ToString())) @@ -5994,7 +5877,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.MoveNext(); } - startIndex += limit; + startIndex += Limit; } } @@ -6011,7 +5894,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type Index = reader[1].ToInt() }; - item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader[2].ToString(), true); + item.Type = Enum.Parse(reader[2].ToString(), true); if (reader[3].SQLiteType != SQLiteType.Null) { @@ -6159,7 +6042,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type item.ColorTransfer = reader[34].ToString(); } - if (item.Type == MediaStreamType.Subtitle){ + if (item.Type == MediaStreamType.Subtitle) + { item.localizedUndefined = _localization.GetLocalizedString("Undefined"); item.localizedDefault = _localization.GetLocalizedString("Default"); item.localizedForced = _localization.GetLocalizedString("Forced"); @@ -6288,8 +6172,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@Codec" + index, attachment.Codec); statement.TryBind("@CodecTag" + index, attachment.CodecTag); statement.TryBind("@Comment" + index, attachment.Comment); - statement.TryBind("@FileName" + index, attachment.FileName); - statement.TryBind("@MimeType" + index, attachment.MimeType); + statement.TryBind("@Filename" + index, attachment.FileName); + statement.TryBind("@MIMEType" + index, attachment.MimeType); } statement.Reset(); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index f6c37e4e5b..22955850ab 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index c82c93ffc3..0c3f26974f 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -26,7 +25,7 @@ namespace Emby.Server.Implementations.Data IServerApplicationPaths appPaths) : base(logger) { - _jsonOptions = JsonDefaults.GetOptions();; + _jsonOptions = JsonDefaults.GetOptions(); DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); } diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index ff75efa592..f0d43e665b 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 2bd0b840a5..adb8e793d2 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -142,11 +141,10 @@ namespace Emby.Server.Implementations.Devices public QueryResult GetDevices(DeviceQuery query) { - var sessions = _authRepo.Get(new AuthenticationInfoQuery + IEnumerable sessions = _authRepo.Get(new AuthenticationInfoQuery { //UserId = query.UserId HasUser = true - }).Items; // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger. @@ -154,23 +152,19 @@ namespace Emby.Server.Implementations.Devices { var val = query.SupportsSync.Value; - sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val).ToArray(); + sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val); } if (!query.UserId.Equals(Guid.Empty)) { var user = _userManager.GetUserById(query.UserId); - sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)).ToArray(); + sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); } var array = sessions.Select(ToDeviceInfo).ToArray(); - return new QueryResult - { - Items = array, - TotalRecordCount = array.Length - }; + return new QueryResult(array); } private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo) @@ -186,7 +180,7 @@ namespace Emby.Server.Implementations.Devices LastUserName = authInfo.UserName, Name = authInfo.DeviceName, DateLastActivity = authInfo.DateLastActivity, - IconUrl = caps == null ? null : caps.IconUrl + IconUrl = caps?.IconUrl }; } @@ -412,7 +406,10 @@ namespace Emby.Server.Implementations.Devices private readonly IServerConfigurationManager _config; private ILogger _logger; - public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger) + public DeviceManagerEntryPoint( + IDeviceManager deviceManager, + IServerConfigurationManager config, + ILogger logger) { _deviceManager = (DeviceManager)deviceManager; _config = config; diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index f8b7541515..bfa49ac5ff 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Diagnostics; diff --git a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs b/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs index 219f73c785..02ad3c1a89 100644 --- a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs +++ b/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using MediaBrowser.Model.Diagnostics; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 960f3f2d67..34a342cf7e 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -1057,30 +1056,19 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.SpecialFeatureCount)) { - if (allExtras == null) - { - allExtras = item.GetExtras().ToArray(); - } - + allExtras = item.GetExtras().ToArray(); dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value)); } if (options.ContainsField(ItemFields.LocalTrailerCount)) { - int trailerCount = 0; - if (allExtras == null) - { - allExtras = item.GetExtras().ToArray(); - } - - trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer); + allExtras ??= item.GetExtras().ToArray(); + dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer); if (item is IHasTrailers hasTrailers) { - trailerCount += hasTrailers.GetTrailerCount(); + dto.LocalTrailerCount += hasTrailers.GetTrailerCount(); } - - dto.LocalTrailerCount = trailerCount; } // Add EpisodeInfo diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index f8560ca856..d46b9507ba 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,14 +29,14 @@ - - - + + + + - + - diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 4e4ef3be01..e290c62e16 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 06458baedc..8e32364071 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -56,7 +55,12 @@ namespace Emby.Server.Implementations.EntryPoints private readonly IProviderManager _providerManager; - public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, IProviderManager providerManager) + public LibraryChangedNotifier( + ILibraryManager libraryManager, + ISessionManager sessionManager, + IUserManager userManager, + ILogger logger, + IProviderManager providerManager) { _libraryManager = libraryManager; _sessionManager = sessionManager; diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index e0aa18e895..41c0c5115c 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Linq; @@ -13,14 +12,18 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints { - public class RecordingNotifier : IServerEntryPoint + public sealed class RecordingNotifier : IServerEntryPoint { private readonly ILiveTvManager _liveTvManager; private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; private readonly ILogger _logger; - public RecordingNotifier(ISessionManager sessionManager, IUserManager userManager, ILogger logger, ILiveTvManager liveTvManager) + public RecordingNotifier( + ISessionManager sessionManager, + IUserManager userManager, + ILogger logger, + ILiveTvManager liveTvManager) { _sessionManager = sessionManager; _userManager = userManager; @@ -28,32 +31,33 @@ namespace Emby.Server.Implementations.EntryPoints _liveTvManager = liveTvManager; } + /// public Task RunAsync() { - _liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled; - _liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled; - _liveTvManager.TimerCreated += _liveTvManager_TimerCreated; - _liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated; + _liveTvManager.TimerCancelled += OnLiveTvManagerTimerCancelled; + _liveTvManager.SeriesTimerCancelled += OnLiveTvManagerSeriesTimerCancelled; + _liveTvManager.TimerCreated += OnLiveTvManagerTimerCreated; + _liveTvManager.SeriesTimerCreated += OnLiveTvManagerSeriesTimerCreated; return Task.CompletedTask; } - private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { SendMessage("SeriesTimerCreated", e.Argument); } - private void _liveTvManager_TimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { SendMessage("TimerCreated", e.Argument); } - private void _liveTvManager_SeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { SendMessage("SeriesTimerCancelled", e.Argument); } - private void _liveTvManager_TimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { SendMessage("TimerCancelled", e.Argument); } @@ -64,11 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None); - } - catch (ObjectDisposedException) - { - // TODO Log exception or Investigate and properly fix. + await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -76,12 +76,13 @@ namespace Emby.Server.Implementations.EntryPoints } } + /// public void Dispose() { - _liveTvManager.TimerCancelled -= _liveTvManager_TimerCancelled; - _liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled; - _liveTvManager.TimerCreated -= _liveTvManager_TimerCreated; - _liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated; + _liveTvManager.TimerCancelled -= OnLiveTvManagerTimerCancelled; + _liveTvManager.SeriesTimerCancelled -= OnLiveTvManagerSeriesTimerCancelled; + _liveTvManager.TimerCreated -= OnLiveTvManagerTimerCreated; + _liveTvManager.SeriesTimerCreated -= OnLiveTvManagerSeriesTimerCreated; } } } diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs index f00996b5fe..54f4b67e66 100644 --- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints { @@ -15,21 +14,17 @@ namespace Emby.Server.Implementations.EntryPoints /// public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask { - private readonly ILogger _logger; - /// /// The user manager. /// private readonly IUserManager _userManager; - - private IFileSystem _fileSystem; + private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// - public RefreshUsersMetadata(ILogger logger, IUserManager userManager, IFileSystem fileSystem) + public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem) { - _logger = logger; _userManager = userManager; _fileSystem = fileSystem; } diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 161788c636..8e97719311 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -2,38 +2,30 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Plugins; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; namespace Emby.Server.Implementations.EntryPoints { /// /// Class StartupWizard. /// - public class StartupWizard : IServerEntryPoint + public sealed class StartupWizard : IServerEntryPoint { - /// - /// The app host. - /// private readonly IServerApplicationHost _appHost; - - /// - /// The user manager. - /// - private readonly ILogger _logger; - - private IServerConfigurationManager _config; + private readonly IConfiguration _appConfig; + private readonly IServerConfigurationManager _config; /// /// Initializes a new instance of the class. /// /// The application host. - /// The logger. /// The configuration manager. - public StartupWizard(IServerApplicationHost appHost, ILogger logger, IServerConfigurationManager config) + public StartupWizard(IServerApplicationHost appHost, IConfiguration appConfig, IServerConfigurationManager config) { _appHost = appHost; - _logger = logger; + _appConfig = appConfig; _config = config; } @@ -45,7 +37,11 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - if (!_config.Configuration.IsStartupWizardCompleted) + if (!_appConfig.HostWebClient()) + { + BrowserLauncher.OpenSwaggerPage(_appHost); + } + else if (!_config.Configuration.IsStartupWizardCompleted) { BrowserLauncher.OpenWebApp(_appHost); } diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 529f835606..50ba0f8fac 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -3,8 +3,6 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Udp; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -23,9 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints /// The logger. /// private readonly ILogger _logger; - private readonly ISocketFactory _socketFactory; private readonly IServerApplicationHost _appHost; - private readonly IJsonSerializer _json; /// /// The UDP server. @@ -64,7 +60,7 @@ namespace Emby.Server.Implementations.EntryPoints _cancellationTokenSource.Cancel(); _udpServer.Dispose(); - + _cancellationTokenSource.Dispose(); _cancellationTokenSource = null; _udpServer = null; diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 3e22080fc0..3618b88c5d 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -13,39 +12,38 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Session; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints { - public class UserDataChangeNotifier : IServerEntryPoint + public sealed class UserDataChangeNotifier : IServerEntryPoint { + private const int UpdateDuration = 500; + private readonly ISessionManager _sessionManager; - private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; private readonly IUserManager _userManager; - private readonly object _syncLock = new object(); - private Timer UpdateTimer { get; set; } - private const int UpdateDuration = 500; - private readonly Dictionary> _changedItems = new Dictionary>(); - public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager) + private readonly object _syncLock = new object(); + private Timer _updateTimer; + + + public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager) { _userDataManager = userDataManager; _sessionManager = sessionManager; - _logger = logger; _userManager = userManager; } public Task RunAsync() { - _userDataManager.UserDataSaved += _userDataManager_UserDataSaved; + _userDataManager.UserDataSaved += OnUserDataManagerUserDataSaved; return Task.CompletedTask; } - void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e) + void OnUserDataManagerUserDataSaved(object sender, UserDataSaveEventArgs e) { if (e.SaveReason == UserDataSaveReason.PlaybackProgress) { @@ -54,14 +52,17 @@ namespace Emby.Server.Implementations.EntryPoints lock (_syncLock) { - if (UpdateTimer == null) + if (_updateTimer == null) { - UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration, - Timeout.Infinite); + _updateTimer = new Timer( + UpdateTimerCallback, + null, + UpdateDuration, + Timeout.Infinite); } else { - UpdateTimer.Change(UpdateDuration, Timeout.Infinite); + _updateTimer.Change(UpdateDuration, Timeout.Infinite); } if (!_changedItems.TryGetValue(e.UserId, out List keys)) @@ -97,10 +98,10 @@ namespace Emby.Server.Implementations.EntryPoints var task = SendNotifications(changes, CancellationToken.None); - if (UpdateTimer != null) + if (_updateTimer != null) { - UpdateTimer.Dispose(); - UpdateTimer = null; + _updateTimer.Dispose(); + _updateTimer = null; } } } @@ -145,13 +146,13 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { - if (UpdateTimer != null) + if (_updateTimer != null) { - UpdateTimer.Dispose(); - UpdateTimer = null; + _updateTimer.Dispose(); + _updateTimer = null; } - _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved; + _userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved; } } } diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 8a2bc83fb0..882bfe2f6e 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (!string.IsNullOrWhiteSpace(userInfo)) { _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url); - url = url.Replace(userInfo + '@', string.Empty); + url = url.Replace(userInfo + '@', string.Empty, StringComparison.Ordinal); } var request = new HttpRequestMessage(method, url); @@ -96,13 +96,13 @@ namespace Emby.Server.Implementations.HttpClientManager switch (options.DecompressionMethod) { - case CompressionMethod.Deflate | CompressionMethod.Gzip: + case CompressionMethods.Deflate | CompressionMethods.Gzip: request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" }); break; - case CompressionMethod.Deflate: + case CompressionMethods.Deflate: request.Headers.Add(HeaderNames.AcceptEncoding, "deflate"); break; - case CompressionMethod.Gzip: + case CompressionMethods.Gzip: request.Headers.Add(HeaderNames.AcceptEncoding, "gzip"); break; default: @@ -239,15 +239,10 @@ namespace Emby.Server.Implementations.HttpClientManager var httpWebRequest = GetRequestMessage(options, httpMethod); - if (options.RequestContentBytes != null - || !string.IsNullOrEmpty(options.RequestContent) + if (!string.IsNullOrEmpty(options.RequestContent) || httpMethod == HttpMethod.Post) { - if (options.RequestContentBytes != null) - { - httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes); - } - else if (options.RequestContent != null) + if (options.RequestContent != null) { httpWebRequest.Content = new StringContent( options.RequestContent, diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index d36f230d64..0b61e40b05 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -12,8 +11,8 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index b0126f7fa5..72667a3144 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -18,11 +17,13 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ServiceStack.Text.Jsv; @@ -30,6 +31,12 @@ namespace Emby.Server.Implementations.HttpServer { public class HttpListenerHost : IHttpServer, IDisposable { + /// + /// The key for a setting that specifies the default redirect path + /// to use for requests where the URL base prefix is invalid or missing. + /// + public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; @@ -40,9 +47,11 @@ namespace Emby.Server.Implementations.HttpServer private readonly Func> _funcParseFn; private readonly string _defaultRedirectPath; private readonly string _baseUrlPrefix; - private readonly Dictionary ServiceOperationsMap = new Dictionary(); - private IWebSocketListener[] _webSocketListeners = Array.Empty(); + private readonly Dictionary _serviceOperationsMap = new Dictionary(); private readonly List _webSocketConnections = new List(); + private readonly IHostEnvironment _hostEnvironment; + + private IWebSocketListener[] _webSocketListeners = Array.Empty(); private bool _disposed = false; public HttpListenerHost( @@ -53,25 +62,34 @@ namespace Emby.Server.Implementations.HttpServer INetworkManager networkManager, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, - IHttpListener socketListener) + IHttpListener socketListener, + ILocalizationManager localizationManager, + ServiceController serviceController, + IHostEnvironment hostEnvironment) { _appHost = applicationHost; _logger = logger; _config = config; - _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; + _defaultRedirectPath = configuration[DefaultRedirectKey]; _baseUrlPrefix = _config.Configuration.BaseUrl; _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; _socketListener = socketListener; + ServiceController = serviceController; + _socketListener.WebSocketConnected = OnWebSocketConnected; + _hostEnvironment = hostEnvironment; _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); Instance = this; ResponseFilters = Array.Empty>(); + GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); } + public event EventHandler> WebSocketConnected; + public Action[] ResponseFilters { get; set; } public static HttpListenerHost Instance { get; protected set; } @@ -80,9 +98,7 @@ namespace Emby.Server.Implementations.HttpServer public string GlobalResponse { get; set; } - public ServiceController ServiceController { get; private set; } - - public event EventHandler> WebSocketConnected; + public ServiceController ServiceController { get; } public object CreateInstance(Type type) { @@ -91,7 +107,7 @@ namespace Emby.Server.Implementations.HttpServer private static string NormalizeUrlPath(string path) { - if (path.StartsWith("/")) + if (path.Length > 0 && path[0] == '/') { // If the path begins with a leading slash, just return it as-is return path; @@ -131,13 +147,13 @@ namespace Emby.Server.Implementations.HttpServer public Type GetServiceTypeByRequest(Type requestType) { - ServiceOperationsMap.TryGetValue(requestType, out var serviceType); + _serviceOperationsMap.TryGetValue(requestType, out var serviceType); return serviceType; } public void AddServiceInfo(Type serviceType, Type requestType) { - ServiceOperationsMap[requestType] = serviceType; + _serviceOperationsMap[requestType] = serviceType; } private List GetRequestFilterAttributes(Type requestDtoType) @@ -199,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer else { var inners = agg.InnerExceptions; - if (inners != null && inners.Count > 0) + if (inners.Count > 0) { return GetActualException(inners[0]); } @@ -362,7 +378,7 @@ namespace Emby.Server.Implementations.HttpServer return true; } - host = host ?? string.Empty; + host ??= string.Empty; if (_networkManager.IsInPrivateAddressSpace(host)) { @@ -433,7 +449,7 @@ namespace Emby.Server.Implementations.HttpServer } /// - /// Overridable method that can be used to implement a custom hnandler + /// Overridable method that can be used to implement a custom handler. /// public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { @@ -492,7 +508,7 @@ namespace Emby.Server.Implementations.HttpServer || string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(localPath) - || !localPath.StartsWith(_baseUrlPrefix)) + || !localPath.StartsWith(_baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) { // Always redirect back to the default path if the base prefix is invalid or missing _logger.LogDebug("Normalizing a URL at {0}", localPath); @@ -519,22 +535,25 @@ namespace Emby.Server.Implementations.HttpServer } else { - await ErrorHandler(new FileNotFoundException(), httpReq, false).ConfigureAwait(false); + throw new FileNotFoundException(); } } - catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException) - { - await ErrorHandler(ex, httpReq, false).ConfigureAwait(false); - } - catch (SecurityException ex) - { - await ErrorHandler(ex, httpReq, false).ConfigureAwait(false); - } catch (Exception ex) { - var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase); + // Do not handle exceptions manually when in development mode + // The framework-defined development exception page will be returned instead + if (_hostEnvironment.IsDevelopment()) + { + throw; + } - await ErrorHandler(ex, httpReq, logException).ConfigureAwait(false); + bool ignoreStackTrace = + ex is SocketException + || ex is IOException + || ex is OperationCanceledException + || ex is SecurityException + || ex is FileNotFoundException; + await ErrorHandler(ex, httpReq, ignoreStackTrace).ConfigureAwait(false); } finally { @@ -586,17 +605,15 @@ namespace Emby.Server.Implementations.HttpServer /// /// Adds the rest handlers. /// - /// The services. - /// - /// - public void Init(IEnumerable services, IEnumerable listeners, IEnumerable urlPrefixes) + /// The service types to register with the . + /// The web socket listeners. + /// The URL prefixes. See . + public void Init(IEnumerable serviceTypes, IEnumerable listeners, IEnumerable urlPrefixes) { _webSocketListeners = listeners.ToArray(); UrlPrefixes = urlPrefixes.ToArray(); - ServiceController = new ServiceController(); - var types = services.Select(r => r.GetType()); - ServiceController.Init(this, types); + ServiceController.Init(this, serviceTypes); ResponseFilters = new Action[] { @@ -693,7 +710,10 @@ namespace Emby.Server.Implementations.HttpServer protected virtual void Dispose(bool disposing) { - if (_disposed) return; + if (_disposed) + { + return; + } if (disposing) { diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 98a4f140e3..b42662420b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 1c3496e5d5..5015937256 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Threading; diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 7cb113a58c..8b9028f6bc 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 03b5b748df..58421aaf19 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Linq; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index e8884bca04..129faeaab0 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index a6a0f5b032..166952c646 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs index 5be1444525..545d73e05f 100644 --- a/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs +++ b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs @@ -1,12 +1,13 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 namespace Emby.Server.Implementations.IO { public class ExtendedFileSystemInfo { public bool IsHidden { get; set; } + public bool IsReadOnly { get; set; } + public bool Exists { get; set; } } } diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index cf92ddbcd7..ef93779aab 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -15,27 +14,29 @@ namespace Emby.Server.Implementations.IO { public class FileRefresher : IDisposable { - private ILogger Logger { get; set; } - private ILibraryManager LibraryManager { get; set; } - private IServerConfigurationManager ConfigurationManager { get; set; } - private readonly List _affectedPaths = new List(); - private Timer _timer; - private readonly object _timerLock = new object(); - public string Path { get; private set; } + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _configurationManager; - public event EventHandler Completed; + private readonly List _affectedPaths = new List(); + private readonly object _timerLock = new object(); + private Timer _timer; public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger) { logger.LogDebug("New file refresher created for {0}", path); Path = path; - ConfigurationManager = configurationManager; - LibraryManager = libraryManager; - Logger = logger; + _configurationManager = configurationManager; + _libraryManager = libraryManager; + _logger = logger; AddPath(path); } + public event EventHandler Completed; + + public string Path { get; private set; } + private void AddAffectedPath(string path) { if (string.IsNullOrEmpty(path)) @@ -80,11 +81,11 @@ namespace Emby.Server.Implementations.IO if (_timer == null) { - _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); } else { - _timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + _timer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); } } } @@ -93,7 +94,7 @@ namespace Emby.Server.Implementations.IO { lock (_timerLock) { - Logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path); + _logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path); Path = path; AddAffectedPath(path); @@ -116,7 +117,7 @@ namespace Emby.Server.Implementations.IO paths = _affectedPaths.ToList(); } - Logger.LogDebug("Timer stopped."); + _logger.LogDebug("Timer stopped."); DisposeTimer(); Completed?.Invoke(this, EventArgs.Empty); @@ -127,7 +128,7 @@ namespace Emby.Server.Implementations.IO } catch (Exception ex) { - Logger.LogError(ex, "Error processing directory changes"); + _logger.LogError(ex, "Error processing directory changes"); } } @@ -147,7 +148,7 @@ namespace Emby.Server.Implementations.IO continue; } - Logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path); + _logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path); try { @@ -158,11 +159,11 @@ namespace Emby.Server.Implementations.IO // For now swallow and log. // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) // Should we remove it from it's parent? - Logger.LogError(ex, "Error refreshing {name}", item.Name); + _logger.LogError(ex, "Error refreshing {name}", item.Name); } catch (Exception ex) { - Logger.LogError(ex, "Error refreshing {name}", item.Name); + _logger.LogError(ex, "Error refreshing {name}", item.Name); } } } @@ -178,7 +179,7 @@ namespace Emby.Server.Implementations.IO while (item == null && !string.IsNullOrEmpty(path)) { - item = LibraryManager.FindByPath(path, null); + item = _libraryManager.FindByPath(path, null); path = System.IO.Path.GetDirectoryName(path); } diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 7777efc3b1..b1fb8cc635 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Concurrent; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index da5a4d50ed..7461ec4f1d 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -588,11 +587,11 @@ namespace Emby.Server.Implementations.IO // some drives on linux have no actual size or are used for other purposes return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram) .Select(d => new FileSystemMetadata - { - Name = d.Name, - FullName = d.RootDirectory.FullName, - IsDirectory = true - }).ToList(); + { + Name = d.Name, + FullName = d.RootDirectory.FullName, + IsDirectory = true + }).ToList(); } public virtual IEnumerable GetDirectories(string path, bool recursive = false) diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs index 574b63ae63..e6696b8c4c 100644 --- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs +++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.IO; diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index c99018e40c..40b397edc2 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Buffers; diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 6e915de3d1..16b68170be 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -3,33 +3,38 @@ namespace Emby.Server.Implementations public interface IStartupOptions { /// - /// --ffmpeg + /// Gets the value of the --ffmpeg command line option. /// string FFmpegPath { get; } /// - /// --service + /// Gets the value of the --service command line option. /// bool IsService { get; } /// - /// --noautorunwebapp + /// Gets the value of the --noautorunwebapp command line option. /// bool NoAutoRunWebApp { get; } /// - /// --package-name + /// Gets the value of the --package-name command line option. /// string PackageName { get; } /// - /// --restartpath + /// Gets the value of the --restartpath command line option. /// string RestartPath { get; } /// - /// --restartargs + /// Gets the value of the --restartargs command line option. /// string RestartArgs { get; } + + /// + /// Gets the value of the --plugin-manifest-url command line option. + /// + string PluginManifestUrl { get; } } } diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index acf3a3b231..fd50f156af 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 3eb64c29c6..9a71868988 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 55566ed517..4e2cf334c0 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Concurrent; @@ -438,10 +437,10 @@ namespace Emby.Server.Implementations.Library item.SetParent(null); - ItemRepository.DeleteItem(item.Id, CancellationToken.None); + ItemRepository.DeleteItem(item.Id); foreach (var child in children) { - ItemRepository.DeleteItem(child.Id, CancellationToken.None); + ItemRepository.DeleteItem(child.Id); } _libraryItemsCache.TryRemove(item.Id, out BaseItem removed); @@ -944,7 +943,6 @@ namespace Emby.Server.Implementations.Library IncludeItemTypes = new[] { typeof(T).Name }, Name = name, DtoOptions = options - }).Cast() .OrderBy(i => i.IsAccessedByName ? 1 : 0) .Cast() @@ -1080,7 +1078,7 @@ namespace Emby.Server.Implementations.Library var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => progress.Report(pct * pct * 0.96)); + innerProgress.RegisterAction(pct => progress.Report(pct * 0.96)); // Validate the entire media library await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false); @@ -1174,7 +1172,6 @@ namespace Emby.Server.Implementations.Library return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath) .Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue)) - .OrderBy(i => i.Name) .ToList(); } @@ -1406,25 +1403,32 @@ namespace Emby.Server.Implementations.Library private void SetTopParentOrAncestorIds(InternalItemsQuery query) { - if (query.AncestorIds.Length == 0) + var ancestorIds = query.AncestorIds; + int len = ancestorIds.Length; + if (len == 0) { return; } - var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList(); - - if (parents.All(i => i is ICollectionFolder || i is UserView)) + var parents = new BaseItem[len]; + for (int i = 0; i < len; i++) { - // Optimize by querying against top level views - query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); - query.AncestorIds = Array.Empty(); - - // Prevent searching in all libraries due to empty filter - if (query.TopParentIds.Length == 0) + parents[i] = GetItemById(ancestorIds[i]); + if (!(parents[i] is ICollectionFolder || parents[i] is UserView)) { - query.TopParentIds = new[] { Guid.NewGuid() }; + return; } } + + // Optimize by querying against top level views + query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); + query.AncestorIds = Array.Empty(); + + // Prevent searching in all libraries due to empty filter + if (query.TopParentIds.Length == 0) + { + query.TopParentIds = new[] { Guid.NewGuid() }; + } } public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) @@ -1585,7 +1589,7 @@ namespace Emby.Server.Implementations.Library public async Task> GetIntros(BaseItem item, User user) { var tasks = IntroProviders - .OrderBy(i => i.GetType().Name.IndexOf("Default", StringComparison.OrdinalIgnoreCase) == -1 ? 0 : 1) + .OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0) .Take(1) .Select(i => GetIntros(i, item, user)); @@ -2363,33 +2367,22 @@ namespace Emby.Server.Implementations.Library new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); } - public bool IsVideoFile(string path, LibraryOptions libraryOptions) + /// + public bool IsVideoFile(string path) { var resolver = new VideoResolver(GetNamingOptions()); return resolver.IsVideoFile(path); } - public bool IsVideoFile(string path) - { - return IsVideoFile(path, new LibraryOptions()); - } - - public bool IsAudioFile(string path, LibraryOptions libraryOptions) - { - var parser = new AudioFileParser(GetNamingOptions()); - return parser.IsAudioFile(path); - } - + /// public bool IsAudioFile(string path) - { - return IsAudioFile(path, new LibraryOptions()); - } + => AudioFileParser.IsAudioFile(path, GetNamingOptions()); + /// public int? GetSeasonNumberFromPath(string path) - { - return SeasonPathParser.Parse(path, true, true).SeasonNumber; - } + => SeasonPathParser.Parse(path, true, true).SeasonNumber; + /// public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) { var series = episode.Series; @@ -2616,14 +2609,12 @@ namespace Emby.Server.Implementations.Library }).OrderBy(i => i.Path); } - private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" }; - public IEnumerable /// The HTTP result factory. private readonly IHttpResultFactory _resultFactory; + private readonly IServerApplicationHost _appHost; + private readonly IConfiguration _appConfig; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IFileSystem _fileSystem; + private readonly IResourceFileManager _resourceFileManager; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The application host. + /// The application configuration. + /// The resource file manager. + /// The server configuration manager. + /// The file system. + /// The result factory. + public DashboardService( + ILogger logger, + IServerApplicationHost appHost, + IConfiguration appConfig, + IResourceFileManager resourceFileManager, + IServerConfigurationManager serverConfigurationManager, + IFileSystem fileSystem, + IHttpResultFactory resultFactory) + { + _logger = logger; + _appHost = appHost; + _appConfig = appConfig; + _resourceFileManager = resourceFileManager; + _serverConfigurationManager = serverConfigurationManager; + _fileSystem = fileSystem; + _resultFactory = resultFactory; + } /// /// Gets or sets the request context. @@ -102,54 +144,33 @@ namespace MediaBrowser.WebDashboard.Api public IRequest Request { get; set; } /// - /// The _app host + /// Gets the path of the directory containing the static web interface content, or null if the server is not + /// hosting the web client. /// - private readonly IServerApplicationHost _appHost; + public string DashboardUIPath => GetDashboardUIPath(_appConfig, _serverConfigurationManager); /// - /// The _server configuration manager + /// Gets the path of the directory containing the static web interface content. /// - private readonly IServerConfigurationManager _serverConfigurationManager; - - private readonly IFileSystem _fileSystem; - private IResourceFileManager _resourceFileManager; - - /// - /// Initializes a new instance of the class. - /// - public DashboardService( - IServerApplicationHost appHost, - IResourceFileManager resourceFileManager, - IServerConfigurationManager serverConfigurationManager, - IFileSystem fileSystem, - ILogger logger, - IHttpResultFactory resultFactory) + /// The app configuration. + /// The server configuration manager. + /// The directory path, or null if the server is not hosting the web client. + public static string GetDashboardUIPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager) { - _appHost = appHost; - _serverConfigurationManager = serverConfigurationManager; - _fileSystem = fileSystem; - _logger = logger; - _resultFactory = resultFactory; - _resourceFileManager = resourceFileManager; - } - - /// - /// Gets the path for the web interface. - /// - /// The path for the web interface. - public string DashboardUIPath - { - get + if (!appConfig.HostWebClient()) { - if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.DashboardSourcePath)) - { - return _serverConfigurationManager.Configuration.DashboardSourcePath; - } - - return _serverConfigurationManager.ApplicationPaths.WebPath; + return null; } + + if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath)) + { + return serverConfigManager.Configuration.DashboardSourcePath; + } + + return serverConfigManager.ApplicationPaths.WebPath; } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetFavIcon request) { return Get(new GetDashboardResource @@ -163,6 +184,7 @@ namespace MediaBrowser.WebDashboard.Api /// /// The request. /// System.Object. + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public Task Get(GetDashboardConfigurationPage request) { IPlugin plugin = null; @@ -187,7 +209,7 @@ namespace MediaBrowser.WebDashboard.Api stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath); isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); - isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html"); + isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal); } } @@ -203,7 +225,7 @@ namespace MediaBrowser.WebDashboard.Api return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); } - return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersionString, null)); + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => PackageCreator.ModifyHtml(false, stream, null, _appHost.ApplicationVersionString, null)); } throw new ResourceNotFoundException(); @@ -235,7 +257,6 @@ namespace MediaBrowser.WebDashboard.Api // Don't allow a failing plugin to fail them all var configPages = pages.Select(p => { - try { return new ConfigurationPageInfo(p); @@ -286,6 +307,7 @@ namespace MediaBrowser.WebDashboard.Api return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1)); } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetRobotsTxt request) { return Get(new GetDashboardResource @@ -301,6 +323,11 @@ namespace MediaBrowser.WebDashboard.Api /// System.Object. public async Task Get(GetDashboardResource request) { + if (!_appConfig.HostWebClient() || DashboardUIPath == null) + { + throw new ResourceNotFoundException(); + } + var path = request.ResourceName; var contentType = MimeTypes.GetMimeType(path); @@ -348,7 +375,7 @@ namespace MediaBrowser.WebDashboard.Api return await _resultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(basePath, path, localizationCulture)).ConfigureAwait(false); } - return await _resultFactory.GetStaticFileResult(Request, _resourceFileManager.GetResourcePath(basePath, path)); + return await _resultFactory.GetStaticFileResult(Request, _resourceFileManager.GetResourcePath(basePath, path)).ConfigureAwait(false); } private string GetLocalizationCulture() @@ -372,6 +399,11 @@ namespace MediaBrowser.WebDashboard.Api public async Task Get(GetDashboardPackage request) { + if (!_appConfig.HostWebClient() || DashboardUIPath == null) + { + throw new ResourceNotFoundException(); + } + var mode = request.Mode; var inputPath = string.IsNullOrWhiteSpace(mode) ? @@ -390,9 +422,9 @@ namespace MediaBrowser.WebDashboard.Api { Directory.Delete(targetPath, true); } - catch (IOException) + catch (IOException ex) { - + _logger.LogError(ex, "Error deleting {Path}", targetPath); } CopyDirectory(inputPath, targetPath); @@ -400,9 +432,9 @@ namespace MediaBrowser.WebDashboard.Api var appVersion = _appHost.ApplicationVersionString; - await DumpHtml(packageCreator, inputPath, targetPath, mode, appVersion); + await DumpHtml(packageCreator, inputPath, targetPath, mode, appVersion).ConfigureAwait(false); - return ""; + return string.Empty; } private async Task DumpHtml(PackageCreator packageCreator, string source, string destination, string mode, string appVersion) @@ -425,7 +457,7 @@ namespace MediaBrowser.WebDashboard.Api using (var stream = await packageCreator.GetResource(resourceVirtualPath, mode, null, appVersion).ConfigureAwait(false)) using (var fs = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) { - await stream.CopyToAsync(fs); + await stream.CopyToAsync(fs).ConfigureAwait(false); } } @@ -433,14 +465,17 @@ namespace MediaBrowser.WebDashboard.Api { Directory.CreateDirectory(destination); - //Now Create all of the directories + // Now Create all of the directories foreach (var dirPath in _fileSystem.GetDirectories(source, true)) - Directory.CreateDirectory(dirPath.FullName.Replace(source, destination)); + { + Directory.CreateDirectory(dirPath.FullName.Replace(source, destination, StringComparison.Ordinal)); + } - //Copy all the files & Replaces any files with the same name + // Copy all the files & Replaces any files with the same name foreach (var newPath in _fileSystem.GetFiles(source, true)) - File.Copy(newPath.FullName, newPath.FullName.Replace(source, destination), true); + { + File.Copy(newPath.FullName, newPath.FullName.Replace(source, destination, StringComparison.Ordinal), true); + } } } - } diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 133bf61e8c..b7c15a840e 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -1,4 +1,7 @@ +#pragma warning disable CS1591 + using System; +using System.Globalization; using System.IO; using System.Text; using System.Threading.Tasks; @@ -28,7 +31,8 @@ namespace MediaBrowser.WebDashboard.Api if (resourceStream != null && IsCoreHtml(virtualPath)) { - resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); + bool isMainIndexPage = string.Equals(virtualPath, "index.html", StringComparison.OrdinalIgnoreCase); + resourceStream = await ModifyHtml(isMainIndexPage, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); } return resourceStream; @@ -45,18 +49,24 @@ namespace MediaBrowser.WebDashboard.Api } /// - /// Modifies the HTML by adding common meta tags, css and js. + /// Modifies the source HTML stream by adding common meta tags, css and js. /// - /// Task{Stream}. - public async Task ModifyHtml( - string path, + /// True if the stream contains content for the main index page. + /// The stream whose content should be modified. + /// The client mode ('cordova', 'android', etc). + /// The application version. + /// The localization culture. + /// + /// A task that represents the async operation to read and modify the input stream. + /// The task result contains a stream containing the modified HTML content. + /// + public static async Task ModifyHtml( + bool isMainIndexPage, Stream sourceStream, string mode, string appVersion, string localizationCulture) { - var isMainIndexPage = string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase); - string html; using (var reader = new StreamReader(sourceStream, Encoding.UTF8)) { @@ -67,30 +77,29 @@ namespace MediaBrowser.WebDashboard.Api { var lang = localizationCulture.Split('-')[0]; - html = html.Replace("", "" + GetMetaTags(mode)); + html = html.Replace("", "" + GetMetaTags(mode), StringComparison.Ordinal); } // Disable embedded scripts from plugins. We'll run them later once resources have loaded if (html.IndexOf("", "-->"); + html = html.Replace("", "-->", StringComparison.Ordinal); } if (isMainIndexPage) { - html = html.Replace("", GetCommonJavascript(mode, appVersion) + ""); + html = html.Replace("", GetCommonJavascript(mode, appVersion) + "", StringComparison.Ordinal); } var bytes = Encoding.UTF8.GetBytes(html); return new MemoryStream(bytes); - } /// @@ -123,11 +132,11 @@ namespace MediaBrowser.WebDashboard.Api builder.Append(""); diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 1d256d6895..da52b852a4 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -19,6 +19,19 @@ netstandard2.1 false true + true + + + + + + + + + + + + ../jellyfin.ruleset diff --git a/MediaBrowser.WebDashboard/ServerEntryPoint.cs b/MediaBrowser.WebDashboard/ServerEntryPoint.cs index 18ed54a786..5c7e8b3c76 100644 --- a/MediaBrowser.WebDashboard/ServerEntryPoint.cs +++ b/MediaBrowser.WebDashboard/ServerEntryPoint.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -6,24 +8,25 @@ using MediaBrowser.Controller.Plugins; namespace MediaBrowser.WebDashboard { - public class ServerEntryPoint : IServerEntryPoint + public sealed class ServerEntryPoint : IServerEntryPoint { - /// - /// Gets the list of plugin configuration pages - /// - /// The configuration pages. - public List PluginConfigurationPages { get; private set; } - private readonly IApplicationHost _appHost; - public static ServerEntryPoint Instance { get; private set; } - public ServerEntryPoint(IApplicationHost appHost) { _appHost = appHost; Instance = this; } + public static ServerEntryPoint Instance { get; private set; } + + /// + /// Gets the list of plugin configuration pages. + /// + /// The configuration pages. + public List PluginConfigurationPages { get; private set; } + + /// public Task RunAsync() { PluginConfigurationPages = _appHost.GetExports().ToList(); @@ -31,6 +34,7 @@ namespace MediaBrowser.WebDashboard return Task.CompletedTask; } + /// public void Dispose() { } diff --git a/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationExtension.cs b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationExtension.cs new file mode 100644 index 0000000000..fe3bc3cd30 --- /dev/null +++ b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationExtension.cs @@ -0,0 +1,15 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.XbmcMetadata.Configuration +{ + public static class NfoConfigurationExtension + { + public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager manager) + { + return manager.GetConfiguration("xbmcmetadata"); + } + } +} diff --git a/MediaBrowser.XbmcMetadata/Configuration/NfoOptions.cs b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs similarity index 62% rename from MediaBrowser.XbmcMetadata/Configuration/NfoOptions.cs rename to MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs index 60dcde4dba..8325bfdbd5 100644 --- a/MediaBrowser.XbmcMetadata/Configuration/NfoOptions.cs +++ b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs @@ -1,10 +1,12 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; namespace MediaBrowser.XbmcMetadata.Configuration { - public class ConfigurationFactory : IConfigurationFactory + public class NfoConfigurationFactory : IConfigurationFactory { /// public IEnumerable GetConfigurations() @@ -19,12 +21,4 @@ namespace MediaBrowser.XbmcMetadata.Configuration }; } } - - public static class ConfigurationExtension - { - public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager manager) - { - return manager.GetConfiguration("xbmcmetadata"); - } - } } diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs index fe4d50efaa..571953b471 100644 --- a/MediaBrowser.XbmcMetadata/EntryPoint.cs +++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -12,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata { - public class EntryPoint : IServerEntryPoint + public sealed class EntryPoint : IServerEntryPoint { private readonly IUserDataManager _userDataManager; private readonly ILogger _logger; @@ -21,7 +23,7 @@ namespace MediaBrowser.XbmcMetadata public EntryPoint( IUserDataManager userDataManager, - ILogger logger, + ILogger logger, IProviderManager providerManager, IConfigurationManager config) { diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index 0d62cf8c59..e262820958 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -13,6 +13,19 @@ netstandard2.1 false true + true + + + + + + + + + + + + ../jellyfin.ruleset diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 62d7a8cf4a..36b9a9c1f8 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; @@ -27,6 +29,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// /// Initializes a new instance of the class. /// + /// The logger. + /// the configuration manager. + /// The provider manager. public BaseNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) { Logger = logger; @@ -48,13 +53,13 @@ namespace MediaBrowser.XbmcMetadata.Parsers protected virtual string MovieDbParserSearchString => "themoviedb.org/movie/"; /// - /// Fetches metadata for an item from one xml file + /// Fetches metadata for an item from one xml file. /// /// The item. /// The metadata file. /// The cancellation token. - /// - /// + /// item is null. + /// metadataFile is null or empty. public void Fetch(MetadataResult item, string metadataFile, CancellationToken cancellationToken) { if (item == null) @@ -80,7 +85,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - //Additional Mappings + // Additional Mappings _validProviderIds.Add("collectionnumber", "TmdbCollection"); _validProviderIds.Add("tmdbcolid", "TmdbCollection"); _validProviderIds.Add("imdb_id", "Imdb"); @@ -123,6 +128,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } } + return; } @@ -196,14 +202,13 @@ namespace MediaBrowser.XbmcMetadata.Parsers } catch (XmlException) { - } } } protected void ParseProviderLinks(T item, string xml) { - //Look for a match for the Regex pattern "tt" followed by 7 digits + // Look for a match for the Regex pattern "tt" followed by 7 digits var m = Regex.Match(xml, @"tt([0-9]{7})", RegexOptions.IgnoreCase); if (m.Success) { @@ -267,6 +272,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers Logger.LogWarning("Invalid Added value found: " + val); } } + break; } @@ -278,6 +284,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { item.OriginalTitle = val; } + break; } @@ -309,6 +316,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { item.ForcedSortName = val; } + break; } @@ -358,7 +366,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers } return null; - }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); } @@ -373,6 +380,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { item.Tagline = val; } + break; } @@ -387,6 +395,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers .Where(i => !string.IsNullOrWhiteSpace(i)) .ToArray(); } + break; } @@ -398,6 +407,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { item.OfficialRating = rating; } + break; } @@ -409,6 +419,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { item.CustomRating = val; } + break; } @@ -423,6 +434,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } } + break; } @@ -435,6 +447,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { hasAspectRatio.AspectRatio = val; } + break; } @@ -446,6 +459,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { item.IsLocked = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); } + break; } @@ -455,16 +469,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - //var parts = val.Split('/') - // .Select(i => i.Trim()) - // .Where(i => !string.IsNullOrWhiteSpace(i)); - - //foreach (var p in parts) - //{ - // item.AddStudio(p); - //} item.AddStudio(val); } + break; } @@ -477,10 +484,13 @@ namespace MediaBrowser.XbmcMetadata.Parsers { continue; } + itemResult.AddPerson(p); } + break; } + case "credits": { var val = reader.ReadElementContentAsString(); @@ -496,9 +506,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers { continue; } + itemResult.AddPerson(p); } } + break; } @@ -511,8 +523,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers { continue; } + itemResult.AddPerson(p); } + break; } @@ -534,6 +548,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { reader.Read(); } + break; } @@ -547,6 +562,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers item.AddTrailerUrl(val); } + break; } @@ -562,6 +578,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers hasDisplayOrder.DisplayOrder = val; } } + break; } @@ -582,7 +599,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers case "rating": { - var rating = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(rating)) @@ -593,6 +609,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers item.CommunityRating = val; } } + break; } @@ -649,6 +666,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers item.AddGenre(p); } } + break; } @@ -660,6 +678,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { item.AddTag(val); } + break; } @@ -676,6 +695,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { reader.Read(); } + break; } @@ -693,6 +713,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { reader.Skip(); } + break; } } @@ -716,10 +737,12 @@ namespace MediaBrowser.XbmcMetadata.Parsers reader.Read(); continue; } + using (var subtree = reader.ReadSubtree()) { FetchFromStreamDetailsNode(subtree, item); } + break; } @@ -754,10 +777,12 @@ namespace MediaBrowser.XbmcMetadata.Parsers reader.Read(); continue; } + using (var subtree = reader.ReadSubtree()) { FetchFromVideoNode(subtree, item); } + break; } @@ -814,6 +839,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers video.Video3DFormat = Video3DFormat.MVC; } } + break; } @@ -863,8 +889,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers { role = val; } + break; } + case "sortorder": { var val = reader.ReadElementContentAsString(); @@ -876,6 +904,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers sortOrder = intVal; } } + break; } @@ -909,7 +938,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers }; /// - /// Used to split names of comma or pipe delimeted genres and people + /// Used to split names of comma or pipe delimeted genres and people. /// /// The value. /// IEnumerable{System.String}. @@ -919,7 +948,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers // Only split by comma if there is no pipe in the string // We have to be careful to not split names like Matthew, Jr. - var separator = value.IndexOf('|') == -1 && value.IndexOf(';') == -1 ? new[] { ',' } : new[] { '|', ';' }; + var separator = value.IndexOf('|', StringComparison.Ordinal) == -1 && value.IndexOf(';', StringComparison.Ordinal) == -1 + ? new[] { ',' } + : new[] { '|', ';' }; value = value.Trim().Trim(separator); diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index 82ac6c548a..9cc0344c1c 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -11,8 +11,17 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers { + /// + /// Nfo parser for episodes. + /// public class EpisodeNfoParser : BaseNfoParser { + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// the configuration manager. + /// The provider manager. public EpisodeNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(logger, config, providerManager) { @@ -63,7 +72,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers } catch (XmlException) { - } } } @@ -86,6 +94,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers item.ParentIndexNumber = num; } } + break; } @@ -100,6 +109,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers item.IndexNumber = num; } } + break; } @@ -114,6 +124,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers item.IndexNumberEnd = num; } } + break; } @@ -197,7 +208,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers break; } - default: base.FetchDataFromXmlNode(reader, itemResult); break; diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs index 79d9111fe4..c17212f315 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs @@ -11,8 +11,17 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers { + /// + /// Nfo parser for movies. + /// public class MovieNfoParser : BaseNfoParser - /// Task. private void AddCommonNodes( BaseItem item, XmlWriter writer, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, - IFileSystem fileSystem, IServerConfigurationManager config) { var writtenProviderIds = new HashSet(StringComparer.OrdinalIgnoreCase); var overview = (item.Overview ?? string.Empty) .StripHtml() - .Replace(""", "'"); + .Replace(""", "'", StringComparison.Ordinal); var options = config.GetNfoConfiguration(); @@ -455,7 +453,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { var outline = (item.Tagline ?? string.Empty) .StripHtml() - .Replace(""", "'"); + .Replace(""", "'", StringComparison.Ordinal); writer.WriteElementString("outline", outline); } @@ -476,7 +474,7 @@ namespace MediaBrowser.XbmcMetadata.Savers writer.WriteElementString("lockedfields", string.Join("|", item.LockedFields)); } - writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat)); + writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat, CultureInfo.InvariantCulture)); writer.WriteElementString("title", item.Name ?? string.Empty); @@ -590,6 +588,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteElementString("language", item.PreferredMetadataLanguage); } + if (!string.IsNullOrEmpty(item.PreferredMetadataCountryCode)) { writer.WriteElementString("countrycode", item.PreferredMetadataCountryCode); @@ -603,16 +602,16 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteElementString( "formed", - item.PremiereDate.Value.ToLocalTime().ToString(formatString)); + item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); } else { writer.WriteElementString( "premiered", - item.PremiereDate.Value.ToLocalTime().ToString(formatString)); + item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); writer.WriteElementString( "releasedate", - item.PremiereDate.Value.ToLocalTime().ToString(formatString)); + item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); } } @@ -624,7 +623,7 @@ namespace MediaBrowser.XbmcMetadata.Savers writer.WriteElementString( "enddate", - item.EndDate.Value.ToLocalTime().ToString(formatString)); + item.EndDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); } } @@ -780,12 +779,12 @@ namespace MediaBrowser.XbmcMetadata.Savers if (options.SaveImagePathsInNfo) { - AddImages(item, writer, libraryManager, config); + AddImages(item, writer, libraryManager); } AddUserData(item, writer, userManager, userDataRepo, options); - AddActors(people, writer, libraryManager, fileSystem, config, options.SaveImagePathsInNfo); + AddActors(people, writer, libraryManager, options.SaveImagePathsInNfo); if (item is BoxSet folder) { @@ -828,7 +827,7 @@ namespace MediaBrowser.XbmcMetadata.Savers return url.Replace(YouTubeWatchUrl, "plugin://plugin.video.youtube/?action=play_video&videoid=", StringComparison.OrdinalIgnoreCase); } - private void AddImages(BaseItem item, XmlWriter writer, ILibraryManager libraryManager, IServerConfigurationManager config) + private void AddImages(BaseItem item, XmlWriter writer, ILibraryManager libraryManager) { writer.WriteStartElement("art"); @@ -836,12 +835,12 @@ namespace MediaBrowser.XbmcMetadata.Savers if (image != null) { - writer.WriteElementString("poster", GetImagePathToSave(image, libraryManager, config)); + writer.WriteElementString("poster", GetImagePathToSave(image, libraryManager)); } foreach (var backdrop in item.GetImages(ImageType.Backdrop)) { - writer.WriteElementString("fanart", GetImagePathToSave(backdrop, libraryManager, config)); + writer.WriteElementString("fanart", GetImagePathToSave(backdrop, libraryManager)); } writer.WriteEndElement(); @@ -893,7 +892,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteElementString( "lastplayed", - userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss").ToLowerInvariant()); + userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLowerInvariant()); } writer.WriteStartElement("resume"); @@ -911,7 +910,7 @@ namespace MediaBrowser.XbmcMetadata.Savers writer.WriteEndElement(); } - private void AddActors(List people, XmlWriter writer, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager config, bool saveImagePath) + private void AddActors(List people, XmlWriter writer, ILibraryManager libraryManager, bool saveImagePath) { foreach (var person in people) { @@ -953,7 +952,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteElementString( "thumb", - GetImagePathToSave(image, libraryManager, config)); + GetImagePathToSave(image, libraryManager)); } } @@ -961,7 +960,7 @@ namespace MediaBrowser.XbmcMetadata.Savers } } - private string GetImagePathToSave(ItemImageInfo image, ILibraryManager libraryManager, IServerConfigurationManager config) + private string GetImagePathToSave(ItemImageInfo image, ILibraryManager libraryManager) { if (!image.IsLocalFile) { diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs index 091c1957eb..ac2fbb8d24 100644 --- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs @@ -12,15 +12,33 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Savers { + /// + /// Nfo saver for episodes. + /// public class EpisodeNfoSaver : BaseNfoSaver { - public EpisodeNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// the server configuration manager. + /// The library manager. + /// The user manager. + /// The user data manager. + /// The logger. + public EpisodeNfoSaver( + IFileSystem fileSystem, + IServerConfigurationManager configurationManager, + ILibraryManager libraryManager, + IUserManager userManager, + IUserDataManager userDataManager, + ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// protected override string GetLocalSavePath(BaseItem item) => Path.ChangeExtension(item.Path, ".nfo"); @@ -57,7 +75,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat; - writer.WriteElementString("aired", episode.PremiereDate.Value.ToLocalTime().ToString(formatString)); + writer.WriteElementString("aired", episode.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); } if (!episode.ParentIndexNumber.HasValue || episode.ParentIndexNumber.Value == 0) diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index 08a752e338..eef989a5b2 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs @@ -14,9 +14,27 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Savers { + /// + /// Nfo saver for movies. + /// public class MovieNfoSaver : BaseNfoSaver { - public MovieNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// the server configuration manager. + /// The library manager. + /// The user manager. + /// The user data manager. + /// The logger. + public MovieNfoSaver( + IFileSystem fileSystem, + IServerConfigurationManager configurationManager, + ILibraryManager libraryManager, + IUserManager userManager, + IUserDataManager userDataManager, + ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } @@ -25,7 +43,7 @@ namespace MediaBrowser.XbmcMetadata.Savers protected override string GetLocalSavePath(BaseItem item) => GetMovieSavePaths(new ItemInfo(item)).FirstOrDefault(); - public static IEnumerable GetMovieSavePaths(ItemInfo item) + internal static IEnumerable GetMovieSavePaths(ItemInfo item) { if (item.VideoType == VideoType.Dvd && !item.IsPlaceHolder) { @@ -42,13 +60,6 @@ namespace MediaBrowser.XbmcMetadata.Savers } else { - // http://kodi.wiki/view/NFO_files/Movies - // movie.nfo will override all and any .nfo files in the same folder as the media files if you use the "Use foldernames for lookups" setting. If you don't, then moviename.nfo is used - //if (!item.IsInMixedFolder && item.ItemType == typeof(Movie)) - //{ - // list.Add(Path.Combine(item.ContainingFolderPath, "movie.nfo")); - //} - yield return Path.ChangeExtension(item.Path, ".nfo"); if (!item.IsInMixedFolder) @@ -95,6 +106,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteElementString("artist", artist); } + if (!string.IsNullOrEmpty(musicVideo.Album)) { writer.WriteElementString("album", musicVideo.Album); diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs index 25695121d2..925a230bdb 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs @@ -11,15 +11,27 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Savers { + /// + /// Nfo saver for seasons. + /// public class SeasonNfoSaver : BaseNfoSaver { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// the server configuration manager. + /// The library manager. + /// The user manager. + /// The user data manager. + /// The logger. public SeasonNfoSaver( IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, - ILogger logger) + ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } diff --git a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs index 8d7faece71..2a5d36d408 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs @@ -12,15 +12,27 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Savers { + /// + /// Nfo saver for series. + /// public class SeriesNfoSaver : BaseNfoSaver { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// the server configuration manager. + /// The library manager. + /// The user manager. + /// The user data manager. + /// The logger. public SeriesNfoSaver( IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, - ILogger logger) + ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } @@ -56,7 +68,7 @@ namespace MediaBrowser.XbmcMetadata.Savers : language; writer.WriteStartElement("url"); - writer.WriteAttributeString("cache", string.Format("{0}.xml", tvdb)); + writer.WriteAttributeString("cache", tvdb + ".xml"); writer.WriteString( string.Format( CultureInfo.InvariantCulture, diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 50570deecf..1c84622ac0 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -60,6 +60,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api.Tests", "tests EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -170,6 +172,10 @@ Global {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Release|Any CPU.Build.0 = Release|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -201,5 +207,6 @@ Global {3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index ea54b8c8b0..681076e3e7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Current Release -Translation Status +Translation Status Azure Builds @@ -38,6 +38,12 @@ Join our Subreddit + +Release RSS Feed + + +Master Commits RSS Feed +

--- diff --git a/RSSDP/DisposableManagedObjectBase.cs b/RSSDP/DisposableManagedObjectBase.cs index bb36229c40..39589f0225 100644 --- a/RSSDP/DisposableManagedObjectBase.cs +++ b/RSSDP/DisposableManagedObjectBase.cs @@ -72,7 +72,7 @@ namespace Rssdp.Infrastructure /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes. /// /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification="We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfer with the dispose process.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfer with the dispose process.")] public void Dispose() { IsDisposed = true; diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 0aa985a261..18097ef241 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -8,9 +8,9 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; -using Microsoft.Extensions.Logging; -using MediaBrowser.Model.Net; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Net; +using Microsoft.Extensions.Logging; namespace Rssdp.Infrastructure { diff --git a/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj b/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj index bea2e6f0fc..47aeed05ef 100644 --- a/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj +++ b/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.0 + netcoreapp3.1 diff --git a/deployment/centos-package-x64/Dockerfile b/deployment/centos-package-x64/Dockerfile index 2b346f46a2..08219a2e4a 100644 --- a/deployment/centos-package-x64/Dockerfile +++ b/deployment/centos-package-x64/Dockerfile @@ -17,7 +17,7 @@ RUN yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fo # Install recent NodeJS and Yarn RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \ - && rpm -i https://rpm.nodesource.com/pub_8.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \ + && rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \ && yum install -y yarn # Install DotNET SDK diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/fedora-package-x64/Dockerfile index 05a4ef21f9..87120f3a05 100644 --- a/deployment/fedora-package-x64/Dockerfile +++ b/deployment/fedora-package-x64/Dockerfile @@ -1,4 +1,4 @@ -FROM fedora:29 +FROM fedora:31 # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/fedora-package-x64 diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index 914f3d44a1..33c6f6f648 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -26,13 +26,13 @@ Source16: jellyfin-firewalld.xml %{?systemd_requires} BuildRequires: systemd Requires(pre): shadow-utils -BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel +BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel, git %if 0%{?fedora} -BuildRequires: nodejs-yarn +BuildRequires: nodejs-yarn, git %else # Requirements not packaged in main repos -# From https://rpm.nodesource.com/pub_8.x/el/7/x86_64/ -BuildRequires: nodejs >= 8 yarn +# From https://rpm.nodesource.com/pub_10.x/el/7/x86_64/ +BuildRequires: nodejs >= 10 yarn %endif Requires: libcurl, fontconfig, freetype, openssl, glibc libicu # Requirements not packaged in main repos diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1 deleted file mode 100644 index c762137a75..0000000000 --- a/deployment/windows/build-jellyfin.ps1 +++ /dev/null @@ -1,190 +0,0 @@ -[CmdletBinding()] -param( - [switch]$MakeNSIS, - [switch]$InstallNSIS, - [switch]$InstallFFMPEG, - [switch]$InstallNSSM, - [switch]$SkipJellyfinBuild, - [switch]$GenerateZip, - [string]$InstallLocation = "./dist/jellyfin-win-nsis", - [string]$UXLocation = "../jellyfin-ux", - [switch]$InstallTrayApp, - [ValidateSet('Debug','Release')][string]$BuildType = 'Release', - [ValidateSet('Quiet','Minimal', 'Normal')][string]$DotNetVerbosity = 'Minimal', - [ValidateSet('win','win7', 'win8','win81','win10')][string]$WindowsVersion = 'win', - [ValidateSet('x64','x86', 'arm', 'arm64')][string]$Architecture = 'x64' -) - -$ProgressPreference = 'SilentlyContinue' # Speedup all downloads by hiding progress bars. - -#PowershellCore and *nix check to make determine which temp dir to use. -if(($PSVersionTable.PSEdition -eq 'Core') -and (-not $IsWindows)){ - $TempDir = mktemp -d -}else{ - $TempDir = $env:Temp -} -#Create staging dir -New-Item -ItemType Directory -Force -Path $InstallLocation -$ResolvedInstallLocation = Resolve-Path $InstallLocation -$ResolvedUXLocation = Resolve-Path $UXLocation - -function Build-JellyFin { - if(($Architecture -eq 'arm64') -and ($WindowsVersion -ne 'win10')){ - Write-Error "arm64 only supported with Windows10 Version" - exit - } - if(($Architecture -eq 'arm') -and ($WindowsVersion -notin @('win10','win81','win8'))){ - Write-Error "arm only supported with Windows 8 or higher" - exit - } - Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture" - Write-Verbose "InstallLocation: $ResolvedInstallLocation" - Write-Verbose "DotNetVerbosity: $DotNetVerbosity" - dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server -} - -function Install-FFMPEG { - param( - [string]$ResolvedInstallLocation, - [string]$Architecture, - [string]$FFMPEGVersionX86 = "ffmpeg-4.2.1-win32-shared" - ) - Write-Verbose "Checking Architecture" - if($Architecture -notin @('x86','x64')){ - Write-Warning "No builds available for your selected architecture of $Architecture" - Write-Warning "FFMPEG will not be installed" - }elseif($Architecture -eq 'x64'){ - Write-Verbose "Downloading 64 bit FFMPEG" - Invoke-WebRequest -Uri https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose - }else{ - Write-Verbose "Downloading 32 bit FFMPEG" - Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/shared/$FFMPEGVersionX86.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose - } - - Expand-Archive "$tempdir/ffmpeg.zip" -DestinationPath "$tempdir/ffmpeg/" -Force | Write-Verbose - if($Architecture -eq 'x64'){ - Write-Verbose "Copying Binaries to Jellyfin location" - Get-ChildItem "$tempdir/ffmpeg" | ForEach-Object { - Copy-Item $_.FullName -Destination $installLocation | Write-Verbose - } - }else{ - Write-Verbose "Copying Binaries to Jellyfin location" - Get-ChildItem "$tempdir/ffmpeg/$FFMPEGVersionX86/bin" | ForEach-Object { - Copy-Item $_.FullName -Destination $installLocation | Write-Verbose - } - } - Remove-Item "$tempdir/ffmpeg/" -Recurse -Force -ErrorAction Continue | Write-Verbose - Remove-Item "$tempdir/ffmpeg.zip" -Force -ErrorAction Continue | Write-Verbose -} - -function Install-NSSM { - param( - [string]$ResolvedInstallLocation, - [string]$Architecture - ) - Write-Verbose "Checking Architecture" - if($Architecture -notin @('x86','x64')){ - Write-Warning "No builds available for your selected architecture of $Architecture" - Write-Warning "NSSM will not be installed" - }else{ - Write-Verbose "Downloading NSSM" - # [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - # Temporary workaround, file is hosted in an azure blob with a custom domain in front for brevity - Invoke-WebRequest -Uri http://files.evilt.win/nssm/nssm-2.24-101-g897c7ad.zip -UseBasicParsing -OutFile "$tempdir/nssm.zip" | Write-Verbose - } - - Expand-Archive "$tempdir/nssm.zip" -DestinationPath "$tempdir/nssm/" -Force | Write-Verbose - if($Architecture -eq 'x64'){ - Write-Verbose "Copying Binaries to Jellyfin location" - Get-ChildItem "$tempdir/nssm/nssm-2.24-101-g897c7ad/win64" | ForEach-Object { - Copy-Item $_.FullName -Destination $installLocation | Write-Verbose - } - }else{ - Write-Verbose "Copying Binaries to Jellyfin location" - Get-ChildItem "$tempdir/nssm/nssm-2.24-101-g897c7ad/win32" | ForEach-Object { - Copy-Item $_.FullName -Destination $installLocation | Write-Verbose - } - } - Remove-Item "$tempdir/nssm/" -Recurse -Force -ErrorAction Continue | Write-Verbose - Remove-Item "$tempdir/nssm.zip" -Force -ErrorAction Continue | Write-Verbose -} - -function Make-NSIS { - param( - [string]$ResolvedInstallLocation - ) - - $env:InstallLocation = $ResolvedInstallLocation - if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){ - & "$tempdir/nsis/nsis-3.04/makensis.exe" /D$Architecture /DUXPATH=$ResolvedUXLocation ".\deployment\windows\jellyfin.nsi" - } else { - & "makensis" /D$Architecture /DUXPATH=$ResolvedUXLocation ".\deployment\windows\jellyfin.nsi" - } - Copy-Item .\deployment\windows\jellyfin_*.exe $ResolvedInstallLocation\..\ -} - - -function Install-NSIS { - Write-Verbose "Downloading NSIS" - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest -Uri https://nchc.dl.sourceforge.net/project/nsis/NSIS%203/3.04/nsis-3.04.zip -UseBasicParsing -OutFile "$tempdir/nsis.zip" | Write-Verbose - - Expand-Archive "$tempdir/nsis.zip" -DestinationPath "$tempdir/nsis/" -Force | Write-Verbose -} - -function Cleanup-NSIS { - Remove-Item "$tempdir/nsis/" -Recurse -Force -ErrorAction Continue | Write-Verbose - Remove-Item "$tempdir/nsis.zip" -Force -ErrorAction Continue | Write-Verbose -} - -function Install-TrayApp { - param( - [string]$ResolvedInstallLocation, - [string]$Architecture - ) - Write-Verbose "Checking Architecture" - if($Architecture -ne 'x64'){ - Write-Warning "No builds available for your selected architecture of $Architecture" - Write-Warning "The tray app will not be available." - }else{ - Write-Verbose "Downloading Tray App and copying to Jellyfin location" - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest -Uri https://github.com/jellyfin/jellyfin-windows-tray/releases/latest/download/JellyfinTray.exe -UseBasicParsing -OutFile "$installLocation/JellyfinTray.exe" | Write-Verbose - } -} - -if(-not $SkipJellyfinBuild.IsPresent -and -not ($InstallNSIS -eq $true)){ - Write-Verbose "Starting Build Process: Selected Environment is $WindowsVersion-$Architecture" - Build-JellyFin -} -if($InstallFFMPEG.IsPresent -or ($InstallFFMPEG -eq $true)){ - Write-Verbose "Starting FFMPEG Install" - Install-FFMPEG $ResolvedInstallLocation $Architecture -} -if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){ - Write-Verbose "Starting NSSM Install" - Install-NSSM $ResolvedInstallLocation $Architecture -} -if($InstallTrayApp.IsPresent -or ($InstallTrayApp -eq $true)){ - Write-Verbose "Downloading Windows Tray App" - Install-TrayApp $ResolvedInstallLocation $Architecture -} -#Copy-Item .\deployment\windows\install-jellyfin.ps1 $ResolvedInstallLocation\install-jellyfin.ps1 -#Copy-Item .\deployment\windows\install.bat $ResolvedInstallLocation\install.bat -Copy-Item .\LICENSE $ResolvedInstallLocation\LICENSE -if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){ - Write-Verbose "Installing NSIS" - Install-NSIS -} -if($MakeNSIS.IsPresent -or ($MakeNSIS -eq $true)){ - Write-Verbose "Starting NSIS Package creation" - Make-NSIS $ResolvedInstallLocation -} -if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){ - Write-Verbose "Cleanup NSIS" - Cleanup-NSIS -} -if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){ - Compress-Archive -Path $ResolvedInstallLocation -DestinationPath "$ResolvedInstallLocation/jellyfin.zip" -Force -} -Write-Verbose "Finished" diff --git a/deployment/windows/dependencies.txt b/deployment/windows/dependencies.txt deleted file mode 100644 index 16f77cce7c..0000000000 --- a/deployment/windows/dependencies.txt +++ /dev/null @@ -1,2 +0,0 @@ -dotnet -nsis diff --git a/deployment/windows/dialogs/confirmation.nsddef b/deployment/windows/dialogs/confirmation.nsddef deleted file mode 100644 index 969ebacd62..0000000000 --- a/deployment/windows/dialogs/confirmation.nsddef +++ /dev/null @@ -1,24 +0,0 @@ - - - - !include "helpers\StrSlash.nsh" - ${StrSlash} '$0' $INSTDIR - - ${StrSlash} '$1' $_JELLYFINDATADIR_ - - ${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \ - \pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \ - Installation Folder:\b0 $0\line\b \ - Service install:\b0 $_INSTALLSERVICE_\line\b \ - Service start:\b0 $_SERVICESTART_\line\b \ - Service account:\b0 $_SERVICEACCOUNTTYPE_\line\b \ - Jellyfin Data Folder:\b0 $1\par \ -\ - \pard\sa200\sl276\slmult1\f1\lang1043\par \ - }" - - diff --git a/deployment/windows/dialogs/confirmation.nsdinc b/deployment/windows/dialogs/confirmation.nsdinc deleted file mode 100644 index f00e9b43ab..0000000000 --- a/deployment/windows/dialogs/confirmation.nsdinc +++ /dev/null @@ -1,61 +0,0 @@ -; ========================================================= -; This file was generated by NSISDialogDesigner 1.4.4.0 -; http://coolsoft.altervista.org/nsisdialogdesigner -; -; Do not edit it manually, use NSISDialogDesigner instead! -; Modified by EraYaN (2019-09-01) -; ========================================================= - -; handle variables -Var hCtl_confirmation -Var hCtl_confirmation_ConfirmRichText - -; HeaderCustomScript -!include "helpers\StrSlash.nsh" - - - -; dialog create function -Function fnc_confirmation_Create - - ; === confirmation (type: Dialog) === - nsDialogs::Create 1018 - Pop $hCtl_confirmation - ${If} $hCtl_confirmation == error - Abort - ${EndIf} - !insertmacro MUI_HEADER_TEXT "Confirmation Page" "Please confirm your choices for Jellyfin Server installation" - - ; === ConfirmRichText (type: RichText) === - nsDialogs::CreateControl /NOUNLOAD "RichEdit20A" ${ES_READONLY}|${WS_VISIBLE}|${WS_CHILD}|${WS_TABSTOP}|${WS_VSCROLL}|${ES_MULTILINE}|${ES_WANTRETURN} ${WS_EX_STATICEDGE} 8u 7u 280u 126u "" - Pop $hCtl_confirmation_ConfirmRichText - ${NSD_AddExStyle} $hCtl_confirmation_ConfirmRichText ${WS_EX_STATICEDGE} - - ; CreateFunctionCustomScript - ${StrSlash} '$0' $INSTDIR - - ${StrSlash} '$1' $_JELLYFINDATADIR_ - - ${If} $_INSTALLSERVICE_ == "Yes" - ${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \ - \pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \ - Installation Folder:\b0 $0\line\b \ - Service install:\b0 $_INSTALLSERVICE_\line\b \ - Service start:\b0 $_SERVICESTART_\line\b \ - Service account:\b0 $_SERVICEACCOUNTTYPE_\line\b \ - Jellyfin Data Folder:\b0 $1\par \ - \ - \pard\sa200\sl276\slmult1\f1\lang1043\par \ - }" - ${Else} - ${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \ - \pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \ - Installation Folder:\b0 $0\line\b \ - Service install:\b0 $_INSTALLSERVICE_\line\b \ - Jellyfin Data Folder:\b0 $1\par \ - \ - \pard\sa200\sl276\slmult1\f1\lang1043\par \ - }" - ${EndIf} - -FunctionEnd diff --git a/deployment/windows/dialogs/service-config.nsddef b/deployment/windows/dialogs/service-config.nsddef deleted file mode 100644 index 3509ada249..0000000000 --- a/deployment/windows/dialogs/service-config.nsddef +++ /dev/null @@ -1,13 +0,0 @@ - - - - - \ No newline at end of file diff --git a/deployment/windows/dialogs/service-config.nsdinc b/deployment/windows/dialogs/service-config.nsdinc deleted file mode 100644 index 58c350f2ec..0000000000 --- a/deployment/windows/dialogs/service-config.nsdinc +++ /dev/null @@ -1,56 +0,0 @@ -; ========================================================= -; This file was generated by NSISDialogDesigner 1.4.4.0 -; http://coolsoft.altervista.org/nsisdialogdesigner -; -; Do not edit it manually, use NSISDialogDesigner instead! -; ========================================================= - -; handle variables -Var hCtl_service_config -Var hCtl_service_config_StartServiceAfterInstall -Var hCtl_service_config_LocalSystemAccountLabel -Var hCtl_service_config_NetworkServiceAccountLabel -Var hCtl_service_config_UseLocalSystemAccount -Var hCtl_service_config_UseNetworkServiceAccount -Var hCtl_service_config_Font1 - - -; dialog create function -Function fnc_service_config_Create - - ; custom font definitions - CreateFont $hCtl_service_config_Font1 "Microsoft Sans Serif" "8.25" "700" - - ; === service_config (type: Dialog) === - nsDialogs::Create 1018 - Pop $hCtl_service_config - ${If} $hCtl_service_config == error - Abort - ${EndIf} - !insertmacro MUI_HEADER_TEXT "Configure the service" "This controls what type of access the server gets to this system." - - ; === StartServiceAfterInstall (type: Checkbox) === - ${NSD_CreateCheckbox} 8u 118u 280u 15u "Start Service after Install" - Pop $hCtl_service_config_StartServiceAfterInstall - ${NSD_Check} $hCtl_service_config_StartServiceAfterInstall - - ; === LocalSystemAccountLabel (type: Label) === - ${NSD_CreateLabel} 8u 71u 280u 28u "The Local System account has full access to every resource and file on the system. This can have very real security implications, do not use unless absolutely neseccary." - Pop $hCtl_service_config_LocalSystemAccountLabel - - ; === NetworkServiceAccountLabel (type: Label) === - ${NSD_CreateLabel} 8u 24u 280u 28u "The NetworkService account is a predefined local account used by the service control manager. It is the recommended way to install the Jellyfin Server service." - Pop $hCtl_service_config_NetworkServiceAccountLabel - - ; === UseLocalSystemAccount (type: RadioButton) === - ${NSD_CreateRadioButton} 8u 54u 280u 15u "Use Local System account" - Pop $hCtl_service_config_UseLocalSystemAccount - ${NSD_AddStyle} $hCtl_service_config_UseLocalSystemAccount ${WS_GROUP} - - ; === UseNetworkServiceAccount (type: RadioButton) === - ${NSD_CreateRadioButton} 8u 7u 280u 15u "Use Network Service account (Recommended)" - Pop $hCtl_service_config_UseNetworkServiceAccount - SendMessage $hCtl_service_config_UseNetworkServiceAccount ${WM_SETFONT} $hCtl_service_config_Font1 0 - ${NSD_Check} $hCtl_service_config_UseNetworkServiceAccount - -FunctionEnd diff --git a/deployment/windows/dialogs/setuptype.nsddef b/deployment/windows/dialogs/setuptype.nsddef deleted file mode 100644 index b55ceeaaa6..0000000000 --- a/deployment/windows/dialogs/setuptype.nsddef +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/deployment/windows/dialogs/setuptype.nsdinc b/deployment/windows/dialogs/setuptype.nsdinc deleted file mode 100644 index 8746ad2cc6..0000000000 --- a/deployment/windows/dialogs/setuptype.nsdinc +++ /dev/null @@ -1,50 +0,0 @@ -; ========================================================= -; This file was generated by NSISDialogDesigner 1.4.4.0 -; http://coolsoft.altervista.org/nsisdialogdesigner -; -; Do not edit it manually, use NSISDialogDesigner instead! -; ========================================================= - -; handle variables -Var hCtl_setuptype -Var hCtl_setuptype_InstallasaServiceLabel -Var hCtl_setuptype_InstallasaService -Var hCtl_setuptype_BasicInstallLabel -Var hCtl_setuptype_BasicInstall -Var hCtl_setuptype_Font1 - - -; dialog create function -Function fnc_setuptype_Create - - ; custom font definitions - CreateFont $hCtl_setuptype_Font1 "Microsoft Sans Serif" "8.25" "700" - - ; === setuptype (type: Dialog) === - nsDialogs::Create 1018 - Pop $hCtl_setuptype - ${If} $hCtl_setuptype == error - Abort - ${EndIf} - !insertmacro MUI_HEADER_TEXT "Setup Type" "Control how Jellyfin is installed." - - ; === InstallasaServiceLabel (type: Label) === - ${NSD_CreateLabel} 8u 71u 280u 28u "Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares." - Pop $hCtl_setuptype_InstallasaServiceLabel - - ; === InstallasaService (type: RadioButton) === - ${NSD_CreateRadioButton} 8u 54u 280u 15u "Install as a Service (Advanced Users)" - Pop $hCtl_setuptype_InstallasaService - ${NSD_AddStyle} $hCtl_setuptype_InstallasaService ${WS_GROUP} - - ; === BasicInstallLabel (type: Label) === - ${NSD_CreateLabel} 8u 24u 280u 28u "The basic install will run Jellyfin in your current user account.$\nThis is recommended for new users and those with existing Jellyfin installs older than 10.4." - Pop $hCtl_setuptype_BasicInstallLabel - - ; === BasicInstall (type: RadioButton) === - ${NSD_CreateRadioButton} 8u 7u 280u 15u "Basic Install (Recommended)" - Pop $hCtl_setuptype_BasicInstall - SendMessage $hCtl_setuptype_BasicInstall ${WM_SETFONT} $hCtl_setuptype_Font1 0 - ${NSD_Check} $hCtl_setuptype_BasicInstall - -FunctionEnd diff --git a/deployment/windows/helpers/ShowError.nsh b/deployment/windows/helpers/ShowError.nsh deleted file mode 100644 index 6e09b1e407..0000000000 --- a/deployment/windows/helpers/ShowError.nsh +++ /dev/null @@ -1,10 +0,0 @@ -; Show error -!macro ShowError TEXT RETRYLABEL - MessageBox MB_ABORTRETRYIGNORE|MB_ICONSTOP "${TEXT}" IDIGNORE +2 IDRETRY ${RETRYLABEL} - Abort -!macroend - -!macro ShowErrorFinal TEXT - MessageBox MB_OK|MB_ICONSTOP "${TEXT}" - Abort -!macroend diff --git a/deployment/windows/helpers/StrSlash.nsh b/deployment/windows/helpers/StrSlash.nsh deleted file mode 100644 index b8aa771aa6..0000000000 --- a/deployment/windows/helpers/StrSlash.nsh +++ /dev/null @@ -1,47 +0,0 @@ -; Adapted from: https://nsis.sourceforge.io/Another_String_Replace_(and_Slash/BackSlash_Converter) (2019-08-31) - -!macro _StrSlashConstructor out in - Push "${in}" - Push "\" - Call StrSlash - Pop ${out} -!macroend - -!define StrSlash '!insertmacro "_StrSlashConstructor"' - -; Push $filenamestring (e.g. 'c:\this\and\that\filename.htm') -; Push "\" -; Call StrSlash -; Pop $R0 -; ;Now $R0 contains 'c:/this/and/that/filename.htm' -Function StrSlash - Exch $R3 ; $R3 = needle ("\" or "/") - Exch - Exch $R1 ; $R1 = String to replacement in (haystack) - Push $R2 ; Replaced haystack - Push $R4 ; $R4 = not $R3 ("/" or "\") - Push $R6 - Push $R7 ; Scratch reg - StrCpy $R2 "" - StrLen $R6 $R1 - StrCpy $R4 "\" - StrCmp $R3 "/" loop - StrCpy $R4 "/" -loop: - StrCpy $R7 $R1 1 - StrCpy $R1 $R1 $R6 1 - StrCmp $R7 $R3 found - StrCpy $R2 "$R2$R7" - StrCmp $R1 "" done loop -found: - StrCpy $R2 "$R2$R4" - StrCmp $R1 "" done loop -done: - StrCpy $R3 $R2 - Pop $R7 - Pop $R6 - Pop $R4 - Pop $R2 - Pop $R1 - Exch $R3 -FunctionEnd diff --git a/deployment/windows/jellyfin.nsi b/deployment/windows/jellyfin.nsi deleted file mode 100644 index 86724b8f49..0000000000 --- a/deployment/windows/jellyfin.nsi +++ /dev/null @@ -1,575 +0,0 @@ -!verbose 3 -SetCompressor /SOLID bzip2 -ShowInstDetails show -ShowUninstDetails show -Unicode True - -;-------------------------------- -!define SF_USELECTED 0 ; used to check selected options status, rest are inherited from Sections.nsh - - !include "MUI2.nsh" - !include "Sections.nsh" - !include "LogicLib.nsh" - - !include "helpers\ShowError.nsh" - -; Global variables that we'll use - Var _JELLYFINVERSION_ - Var _JELLYFINDATADIR_ - Var _SETUPTYPE_ - Var _INSTALLSERVICE_ - Var _SERVICESTART_ - Var _SERVICEACCOUNTTYPE_ - Var _EXISTINGINSTALLATION_ - Var _EXISTINGSERVICE_ - Var _MAKESHORTCUTS_ - Var _FOLDEREXISTS_ -; -!ifdef x64 - !define ARCH "x64" - !define NAMESUFFIX "(64 bit)" - !define INSTALL_DIRECTORY "$PROGRAMFILES64\Jellyfin\Server" -!endif - -!ifdef x84 - !define ARCH "x86" - !define NAMESUFFIX "(32 bit)" - !define INSTALL_DIRECTORY "$PROGRAMFILES32\Jellyfin\Server" -!endif - -!ifndef ARCH - !error "Set the Arch with /Dx86 or /Dx64" -!endif - -;-------------------------------- - - !define REG_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\JellyfinServer" ;Registry to show up in Add/Remove Programs - !define REG_CONFIG_KEY "Software\Jellyfin\Server" ;Registry to store all configuration - - !getdllversion "$%InstallLocation%\jellyfin.dll" ver_ ;Align installer version with jellyfin.dll version - - Name "Jellyfin Server ${ver_1}.${ver_2}.${ver_3} ${NAMESUFFIX}" ; This is referred in various header text labels - OutFile "jellyfin_${ver_1}.${ver_2}.${ver_3}_windows-${ARCH}.exe" ; Naming convention jellyfin_{version}_windows-{arch].exe - BrandingText "Jellyfin Server ${ver_1}.${ver_2}.${ver_3} Installer" ; This shows in just over the buttons - -; installer attributes, these show up in details tab on installer properties - VIProductVersion "${ver_1}.${ver_2}.${ver_3}.0" ; VIProductVersion format, should be X.X.X.X - VIFileVersion "${ver_1}.${ver_2}.${ver_3}.0" ; VIFileVersion format, should be X.X.X.X - VIAddVersionKey "ProductName" "Jellyfin Server" - VIAddVersionKey "FileVersion" "${ver_1}.${ver_2}.${ver_3}.0" - VIAddVersionKey "LegalCopyright" "(c) 2019 Jellyfin Contributors. Code released under the GNU General Public License" - VIAddVersionKey "FileDescription" "Jellyfin Server: The Free Software Media System" - -;TODO, check defaults - InstallDir ${INSTALL_DIRECTORY} ;Default installation folder - InstallDirRegKey HKLM "${REG_CONFIG_KEY}" "InstallFolder" ;Read the registry for install folder, - - RequestExecutionLevel admin ; ask it upfront for service control, and installing in priv folders - - CRCCheck on ; make sure the installer wasn't corrupted while downloading - - !define MUI_ABORTWARNING ;Prompts user in case of aborting install - -; TODO: Replace with nice Jellyfin Icons -!ifdef UXPATH - !define MUI_ICON "${UXPATH}\branding\NSIS\modern-install.ico" ; Installer Icon - !define MUI_UNICON "${UXPATH}\branding\NSIS\modern-uninstall.ico" ; Uninstaller Icon - - !define MUI_HEADERIMAGE - !define MUI_HEADERIMAGE_BITMAP "${UXPATH}\branding\NSIS\installer-header.bmp" - !define MUI_WELCOMEFINISHPAGE_BITMAP "${UXPATH}\branding\NSIS\installer-right.bmp" - !define MUI_UNWELCOMEFINISHPAGE_BITMAP "${UXPATH}\branding\NSIS\installer-right.bmp" -!endif - -;-------------------------------- -;Pages - -; Welcome Page - !define MUI_WELCOMEPAGE_TEXT "The installer will ask for details to install Jellyfin Server." - !insertmacro MUI_PAGE_WELCOME -; License Page - !insertmacro MUI_PAGE_LICENSE "$%InstallLocation%\LICENSE" ; picking up generic GPL - -; Setup Type Page - Page custom ShowSetupTypePage SetupTypePage_Config - -; Components Page - !define MUI_PAGE_CUSTOMFUNCTION_PRE HideComponentsPage - !insertmacro MUI_PAGE_COMPONENTS - !define MUI_PAGE_CUSTOMFUNCTION_PRE HideInstallDirectoryPage ; Controls when to hide / show - !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Install folder" ; shows just above the folder selection dialog - !insertmacro MUI_PAGE_DIRECTORY - -; Data folder Page - !define MUI_PAGE_CUSTOMFUNCTION_PRE HideDataDirectoryPage ; Controls when to hide / show - !define MUI_PAGE_HEADER_TEXT "Choose Data Location" - !define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install the Jellyfin Server data." - !define MUI_DIRECTORYPAGE_TEXT_TOP "The installer will set the following folder for Jellyfin Server data. To install in a different folder, click Browse and select another folder. Please make sure the folder exists and is accessible. Click Next to continue." - !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Data folder" - !define MUI_DIRECTORYPAGE_VARIABLE $_JELLYFINDATADIR_ - !insertmacro MUI_PAGE_DIRECTORY - -; Custom Dialogs - !include "dialogs\setuptype.nsdinc" - !include "dialogs\service-config.nsdinc" - !include "dialogs\confirmation.nsdinc" - -; Select service account type - #!define MUI_PAGE_CUSTOMFUNCTION_PRE HideServiceConfigPage ; Controls when to hide / show (This does not work for Page, might need to go PageEx) - #!define MUI_PAGE_CUSTOMFUNCTION_SHOW fnc_service_config_Show - #!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ServiceConfigPage_Config - #!insertmacro MUI_PAGE_CUSTOM ServiceAccountType - Page custom ShowServiceConfigPage ServiceConfigPage_Config - -; Confirmation Page - Page custom ShowConfirmationPage ; just letting the user know what they chose to install - -; Actual Installion Page - !insertmacro MUI_PAGE_INSTFILES - - !insertmacro MUI_UNPAGE_CONFIRM - !insertmacro MUI_UNPAGE_INSTFILES - #!insertmacro MUI_UNPAGE_FINISH - -;-------------------------------- -;Languages; Add more languages later here if needed - !insertmacro MUI_LANGUAGE "English" - -;-------------------------------- -;Installer Sections -Section "!Jellyfin Server (required)" InstallJellyfinServer - SectionIn RO ; Mandatory section, isn't this the whole purpose to run the installer. - - StrCmp "$_EXISTINGINSTALLATION_" "Yes" RunUninstaller CarryOn ; Silently uninstall in case of previous installation - - RunUninstaller: - DetailPrint "Looking for uninstaller at $INSTDIR" - FindFirst $0 $1 "$INSTDIR\Uninstall.exe" - FindClose $0 - StrCmp $1 "" CarryOn ; the registry key was there but uninstaller was not found - - DetailPrint "Silently running the uninstaller at $INSTDIR" - ExecWait '"$INSTDIR\Uninstall.exe" /S _?=$INSTDIR' $0 - DetailPrint "Uninstall finished, $0" - - CarryOn: - ${If} $_EXISTINGSERVICE_ == 'Yes' - ExecWait '"$INSTDIR\nssm.exe" stop JellyfinServer' $0 - ${If} $0 <> 0 - MessageBox MB_OK|MB_ICONSTOP "Could not stop the Jellyfin Server service." - Abort - ${EndIf} - DetailPrint "Stopped Jellyfin Server service, $0" - ${EndIf} - - SetOutPath "$INSTDIR" - - File "/oname=icon.ico" "${UXPATH}\branding\NSIS\modern-install.ico" - File /r $%InstallLocation%\* - - -; Write the InstallFolder, DataFolder, Network Service info into the registry for later use - WriteRegExpandStr HKLM "${REG_CONFIG_KEY}" "InstallFolder" "$INSTDIR" - WriteRegExpandStr HKLM "${REG_CONFIG_KEY}" "DataFolder" "$_JELLYFINDATADIR_" - WriteRegStr HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" "$_SERVICEACCOUNTTYPE_" - - !getdllversion "$%InstallLocation%\jellyfin.dll" ver_ - StrCpy $_JELLYFINVERSION_ "${ver_1}.${ver_2}.${ver_3}" ; - -; Write the uninstall keys for Windows - WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayName" "Jellyfin Server $_JELLYFINVERSION_ ${NAMESUFFIX}" - WriteRegExpandStr HKLM "${REG_UNINST_KEY}" "UninstallString" '"$INSTDIR\Uninstall.exe"' - WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayIcon" '"$INSTDIR\Uninstall.exe",0' - WriteRegStr HKLM "${REG_UNINST_KEY}" "Publisher" "The Jellyfin Project" - WriteRegStr HKLM "${REG_UNINST_KEY}" "URLInfoAbout" "https://jellyfin.org/" - WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayVersion" "$_JELLYFINVERSION_" - WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoModify" 1 - WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoRepair" 1 - -;Create uninstaller - WriteUninstaller "$INSTDIR\Uninstall.exe" -SectionEnd - -Section "Jellyfin Server Service" InstallService -${If} $_INSTALLSERVICE_ == "Yes" ; Only run this if we're going to install the service! - ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 - DetailPrint "Jellyfin Server service statuscode, $0" - ${If} $0 == 0 - InstallRetry: - ExecWait '"$INSTDIR\nssm.exe" install JellyfinServer "$INSTDIR\jellyfin.exe" --service --datadir \"$_JELLYFINDATADIR_\"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not install the Jellyfin Server service." InstallRetry - ${EndIf} - DetailPrint "Jellyfin Server Service install, $0" - ${Else} - DetailPrint "Jellyfin Server Service exists, updating..." - - ConfigureApplicationRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Application "$INSTDIR\jellyfin.exe"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureApplicationRetry - ${EndIf} - DetailPrint "Jellyfin Server Service setting (Application), $0" - - ConfigureAppParametersRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppParameters --service --datadir \"$_JELLYFINDATADIR_\"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureAppParametersRetry - ${EndIf} - DetailPrint "Jellyfin Server Service setting (AppParameters), $0" - ${EndIf} - - - Sleep 3000 ; Give time for Windows to catchup - ConfigureStartRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Start SERVICE_DELAYED_AUTO_START' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureStartRetry - ${EndIf} - DetailPrint "Jellyfin Server Service setting (Start), $0" - - ConfigureDescriptionRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Description "Jellyfin Server: The Free Software Media System"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureDescriptionRetry - ${EndIf} - DetailPrint "Jellyfin Server Service setting (Description), $0" - ConfigureDisplayNameRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer DisplayName "Jellyfin Server"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureDisplayNameRetry - - ${EndIf} - DetailPrint "Jellyfin Server Service setting (DisplayName), $0" - - Sleep 3000 - ${If} $_SERVICEACCOUNTTYPE_ == "NetworkService" ; the default install using NSSM is Local System - ConfigureNetworkServiceRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Objectname "Network Service"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service account." ConfigureNetworkServiceRetry - ${EndIf} - DetailPrint "Jellyfin Server service account change, $0" - ${EndIf} - - Sleep 3000 - ConfigureDefaultAppExit: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppExit Default Exit' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service app exit action." ConfigureDefaultAppExit - ${EndIf} - DetailPrint "Jellyfin Server service exit action set, $0" -${EndIf} - -SectionEnd - -Section "-start service" StartService -${If} $_SERVICESTART_ == "Yes" -${AndIf} $_INSTALLSERVICE_ == "Yes" - StartRetry: - ExecWait '"$INSTDIR\nssm.exe" start JellyfinServer' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not start the Jellyfin Server service." StartRetry - ${EndIf} - DetailPrint "Jellyfin Server service start, $0" -${EndIf} -SectionEnd - -Section "Create Shortcuts" CreateWinShortcuts - ${If} $_MAKESHORTCUTS_ == "Yes" - CreateDirectory "$SMPROGRAMS\Jellyfin Server" - CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin (View Console).lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMAXIMIZED - CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin Tray App.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0 - ;CreateShortCut "$DESKTOP\Jellyfin Server.lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMINIMIZED - CreateShortCut "$DESKTOP\Jellyfin Server\Jellyfin Server.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0 - ${EndIf} -SectionEnd - -;-------------------------------- -;Descriptions - -;Language strings - LangString DESC_InstallJellyfinServer ${LANG_ENGLISH} "Install Jellyfin Server" - LangString DESC_InstallService ${LANG_ENGLISH} "Install As a Service" - -;Assign language strings to sections - !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN - !insertmacro MUI_DESCRIPTION_TEXT ${InstallJellyfinServer} $(DESC_InstallJellyfinServer) - !insertmacro MUI_DESCRIPTION_TEXT ${InstallService} $(DESC_InstallService) - !insertmacro MUI_FUNCTION_DESCRIPTION_END - -;-------------------------------- -;Uninstaller Section - -Section "Uninstall" - - ReadRegStr $INSTDIR HKLM "${REG_CONFIG_KEY}" "InstallFolder" ; read the installation folder - ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; read the data folder - ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; read the account name - - DetailPrint "Jellyfin Install location: $INSTDIR" - DetailPrint "Jellyfin Data folder: $_JELLYFINDATADIR_" - - MessageBox MB_YESNO|MB_ICONINFORMATION "Do you want to retain the Jellyfin Server data folder? The media will not be touched. $\r$\nIf unsure choose YES." /SD IDYES IDYES PreserveData - - RMDir /r /REBOOTOK "$_JELLYFINDATADIR_" - - PreserveData: - - ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 - DetailPrint "Jellyfin Server service statuscode, $0" - IntCmp $0 0 NoServiceUninstall ; service doesn't exist, may be run from desktop shortcut - - Sleep 3000 ; Give time for Windows to catchup - - UninstallStopRetry: - ExecWait '"$INSTDIR\nssm.exe" stop JellyfinServer' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not stop the Jellyfin Server service." UninstallStopRetry - ${EndIf} - DetailPrint "Stopped Jellyfin Server service, $0" - - UninstallRemoveRetry: - ExecWait '"$INSTDIR\nssm.exe" remove JellyfinServer confirm' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not remove the Jellyfin Server service." UninstallRemoveRetry - ${EndIf} - DetailPrint "Removed Jellyfin Server service, $0" - - Sleep 3000 ; Give time for Windows to catchup - - NoServiceUninstall: ; existing install was present but no service was detected. Remove shortcuts if account is set to none - ${If} $_SERVICEACCOUNTTYPE_ == "None" - RMDir /r "$SMPROGRAMS\Jellyfin Server" - Delete "$DESKTOP\Jellyfin Server.lnk" - DetailPrint "Removed old shortcuts..." - ${EndIf} - - Delete "$INSTDIR\*.*" - RMDir /r /REBOOTOK "$INSTDIR\jellyfin-web" - Delete "$INSTDIR\Uninstall.exe" - RMDir /r /REBOOTOK "$INSTDIR" - - DeleteRegKey HKLM "Software\Jellyfin" - DeleteRegKey HKLM "${REG_UNINST_KEY}" - -SectionEnd - -Function .onInit -; Setting up defaults - StrCpy $_INSTALLSERVICE_ "Yes" - StrCpy $_SERVICESTART_ "Yes" - StrCpy $_SERVICEACCOUNTTYPE_ "NetworkService" - StrCpy $_EXISTINGINSTALLATION_ "No" - StrCpy $_EXISTINGSERVICE_ "No" - StrCpy $_MAKESHORTCUTS_ "No" - - SetShellVarContext current - StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server" - - System::Call 'kernel32::CreateMutex(p 0, i 0, t "JellyfinServerMutex") p .r1 ?e' - Pop $R0 - - StrCmp $R0 0 +3 - !insertmacro ShowErrorFinal "The installer is already running." - -;Detect if Jellyfin is already installed. -; In case it is installed, let the user choose either -; 1. Exit installer -; 2. Upgrade without messing with data -; 2a. Don't ask for any details, uninstall and install afresh with old settings - -; Read Registry for previous installation - ClearErrors - ReadRegStr "$0" HKLM "${REG_CONFIG_KEY}" "InstallFolder" - IfErrors NoExisitingInstall - - DetailPrint "Existing Jellyfin Server detected at: $0" - StrCpy "$INSTDIR" "$0" ; set the location fro registry as new default - - StrCpy $_EXISTINGINSTALLATION_ "Yes" ; Set our flag to be used later - SectionSetText ${InstallJellyfinServer} "Upgrade Jellyfin Server (required)" ; Change install text to "Upgrade" - - ; check if service was run using Network Service account - ClearErrors - ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; in case of error _SERVICEACCOUNTTYPE_ will be NetworkService as default - - ClearErrors - ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; in case of error, the default holds - - ; Hide sections which will not be needed in case of previous install - ; SectionSetText ${InstallService} "" - -; check if there is a service called Jellyfin, there should be -; hack : nssm statuscode Jellyfin will return non zero return code in case it exists - ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 - DetailPrint "Jellyfin Server service statuscode, $0" - IntCmp $0 0 NoService ; service doesn't exist, may be run from desktop shortcut - - ; if service was detected, set defaults going forward. - StrCpy $_EXISTINGSERVICE_ "Yes" - StrCpy $_INSTALLSERVICE_ "Yes" - StrCpy $_SERVICESTART_ "Yes" - StrCpy $_MAKESHORTCUTS_ "No" - SectionSetText ${CreateWinShortcuts} "" - - - NoService: ; existing install was present but no service was detected - ${If} $_SERVICEACCOUNTTYPE_ == "None" - StrCpy $_SETUPTYPE_ "Basic" - StrCpy $_INSTALLSERVICE_ "No" - StrCpy $_SERVICESTART_ "No" - StrCpy $_MAKESHORTCUTS_ "Yes" - ${EndIf} - -; Let the user know that we'll upgrade and provide an option to quit. - MessageBox MB_OKCANCEL|MB_ICONINFORMATION "Existing installation of Jellyfin Server was detected, it'll be upgraded, settings will be retained. \ - $\r$\nClick OK to proceed, Cancel to exit installer." /SD IDOK IDOK ProceedWithUpgrade - Quit ; Quit if the user is not sure about upgrade - - ProceedWithUpgrade: - - NoExisitingInstall: ; by this time, the variables have been correctly set to reflect previous install details - -FunctionEnd - -Function HideInstallDirectoryPage - ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder - Abort - ${EndIf} -FunctionEnd - -Function HideDataDirectoryPage - ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder - Abort - ${EndIf} -FunctionEnd - -Function HideServiceConfigPage - ${If} $_INSTALLSERVICE_ == "No" ; Not running as a service, don't ask for service type - ${OrIf} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder - Abort - ${EndIf} -FunctionEnd - -Function HideConfirmationPage - ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder - Abort - ${EndIf} -FunctionEnd - -Function HideSetupTypePage - ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for SetupType - Abort - ${EndIf} -FunctionEnd - -Function HideComponentsPage - ${If} $_SETUPTYPE_ == "Basic" ; Basic installation chosen, don't show components choice - Abort - ${EndIf} -FunctionEnd - -; Setup Type dialog show function -Function ShowSetupTypePage - Call HideSetupTypePage - Call fnc_setuptype_Create - nsDialogs::Show -FunctionEnd - -; Service Config dialog show function -Function ShowServiceConfigPage - Call HideServiceConfigPage - Call fnc_service_config_Create - nsDialogs::Show -FunctionEnd - -; Confirmation dialog show function -Function ShowConfirmationPage - Call HideConfirmationPage - Call fnc_confirmation_Create - nsDialogs::Show -FunctionEnd - -; Declare temp variables to read the options from the custom page. -Var StartServiceAfterInstall -Var UseNetworkServiceAccount -Var UseLocalSystemAccount -Var BasicInstall - - -Function SetupTypePage_Config -${NSD_GetState} $hCtl_setuptype_BasicInstall $BasicInstall - IfFileExists "$LOCALAPPDATA\Jellyfin" folderfound foldernotfound ; if the folder exists, use this, otherwise, go with new default - folderfound: - StrCpy $_FOLDEREXISTS_ "Yes" - Goto InstallCheck - foldernotfound: - StrCpy $_FOLDEREXISTS_ "No" - Goto InstallCheck - -InstallCheck: -${If} $BasicInstall == 1 - StrCpy $_SETUPTYPE_ "Basic" - StrCpy $_INSTALLSERVICE_ "No" - StrCpy $_SERVICESTART_ "No" - StrCpy $_SERVICEACCOUNTTYPE_ "None" - StrCpy $_MAKESHORTCUTS_ "Yes" - ${If} $_FOLDEREXISTS_ == "Yes" - StrCpy $_JELLYFINDATADIR_ "$LOCALAPPDATA\Jellyfin\" - ${EndIf} -${Else} - StrCpy $_SETUPTYPE_ "Advanced" - StrCpy $_INSTALLSERVICE_ "Yes" - StrCpy $_MAKESHORTCUTS_ "No" - ${If} $_FOLDEREXISTS_ == "Yes" - MessageBox MB_OKCANCEL|MB_ICONINFORMATION "An existing data folder was detected.\ - $\r$\nBasic Setup is highly recommended.\ - $\r$\nIf you proceed, you will need to set up Jellyfin again." IDOK GoAhead IDCANCEL GoBack - GoBack: - Abort - ${EndIf} - GoAhead: - StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server" - SectionSetText ${CreateWinShortcuts} "" -${EndIf} - -FunctionEnd - -Function ServiceConfigPage_Config -${NSD_GetState} $hCtl_service_config_StartServiceAfterInstall $StartServiceAfterInstall -${If} $StartServiceAfterInstall == 1 - StrCpy $_SERVICESTART_ "Yes" -${Else} - StrCpy $_SERVICESTART_ "No" -${EndIf} -${NSD_GetState} $hCtl_service_config_UseNetworkServiceAccount $UseNetworkServiceAccount -${NSD_GetState} $hCtl_service_config_UseLocalSystemAccount $UseLocalSystemAccount - -${If} $UseNetworkServiceAccount == 1 - StrCpy $_SERVICEACCOUNTTYPE_ "NetworkService" -${ElseIf} $UseLocalSystemAccount == 1 - StrCpy $_SERVICEACCOUNTTYPE_ "LocalSystem" -${Else} - !insertmacro ShowErrorFinal "Service account type not properly configured." -${EndIf} - -FunctionEnd - -; This function handles the choices during component selection -Function .onSelChange - -; If we are not installing service, we don't need to set the NetworkService account or StartService - SectionGetFlags ${InstallService} $0 - ${If} $0 = ${SF_SELECTED} - StrCpy $_INSTALLSERVICE_ "Yes" - ${Else} - StrCpy $_INSTALLSERVICE_ "No" - StrCpy $_SERVICESTART_ "No" - StrCpy $_SERVICEACCOUNTTYPE_ "None" - ${EndIf} -FunctionEnd - -Function .onInstSuccess - #ExecShell "open" "http://localhost:8096" -FunctionEnd diff --git a/deployment/windows/legacy/install-jellyfin.ps1 b/deployment/windows/legacy/install-jellyfin.ps1 deleted file mode 100644 index e909a0468e..0000000000 --- a/deployment/windows/legacy/install-jellyfin.ps1 +++ /dev/null @@ -1,460 +0,0 @@ -[CmdletBinding()] - -param( - [Switch]$Quiet, - [Switch]$InstallAsService, - [System.Management.Automation.pscredential]$ServiceUser, - [switch]$CreateDesktopShorcut, - [switch]$LaunchJellyfin, - [switch]$MigrateEmbyLibrary, - [string]$InstallLocation, - [string]$EmbyLibraryLocation, - [string]$JellyfinLibraryLocation -) -<# This form was created using POSHGUI.com a free online gui designer for PowerShell -.NAME - Install-Jellyfin -#> - -#This doesn't need to be used by default anymore, but I am keeping it in as a function for future use. -function Elevate-Window { - # Get the ID and security principal of the current user account - $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent() - $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID) - - # Get the security principal for the Administrator role - $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator - - # Check to see if we are currently running "as Administrator" - if ($myWindowsPrincipal.IsInRole($adminRole)) - { - # We are running "as Administrator" - so change the title and background color to indicate this - $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)" - $Host.UI.RawUI.BackgroundColor = "DarkBlue" - clear-host - } - else - { - # We are not running "as Administrator" - so relaunch as administrator - - # Create a new process object that starts PowerShell - $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell"; - - # Specify the current script path and name as a parameter - $newProcess.Arguments = $myInvocation.MyCommand.Definition; - - # Indicate that the process should be elevated - $newProcess.Verb = "runas"; - - # Start the new process - [System.Diagnostics.Process]::Start($newProcess); - - # Exit from the current, unelevated, process - exit - } -} - -#FIXME The install methods should be a function that takes all the params, the quiet flag should be a paramset - -if($Quiet.IsPresent -or $Quiet -eq $true){ - if([string]::IsNullOrEmpty($JellyfinLibraryLocation)){ - $Script:JellyfinDataDir = "$env:LOCALAPPDATA\jellyfin\" - }else{ - $Script:JellyfinDataDir = $JellyfinLibraryLocation - } - if([string]::IsNullOrEmpty($InstallLocation)){ - $Script:DefaultJellyfinInstallDirectory = "$env:Appdata\jellyfin\" - }else{ - $Script:DefaultJellyfinInstallDirectory = $InstallLocation - } - - if([string]::IsNullOrEmpty($EmbyLibraryLocation)){ - $Script:defaultEmbyDataDir = "$env:Appdata\Emby-Server\data\" - }else{ - $Script:defaultEmbyDataDir = $EmbyLibraryLocation - } - - if($InstallAsService.IsPresent -or $InstallAsService -eq $true){ - $Script:InstallAsService = $true - }else{$Script:InstallAsService = $false} - if($null -eq $ServiceUser){ - $Script:InstallServiceAsUser = $false - }else{ - $Script:InstallServiceAsUser = $true - $Script:UserCredentials = $ServiceUser - $Script:JellyfinDataDir = "$env:HOMEDRIVE\Users\$($Script:UserCredentials.UserName)\Appdata\Local\jellyfin\"} - if($CreateDesktopShorcut.IsPresent -or $CreateDesktopShorcut -eq $true) {$Script:CreateShortcut = $true}else{$Script:CreateShortcut = $false} - if($MigrateEmbyLibrary.IsPresent -or $MigrateEmbyLibrary -eq $true){$Script:MigrateLibrary = $true}else{$Script:MigrateLibrary = $false} - if($LaunchJellyfin.IsPresent -or $LaunchJellyfin -eq $true){$Script:StartJellyfin = $true}else{$Script:StartJellyfin = $false} - - if(-not (Test-Path $Script:DefaultJellyfinInstallDirectory)){ - mkdir $Script:DefaultJellyfinInstallDirectory - } - Copy-Item -Path $PSScriptRoot/* -DestinationPath "$Script:DefaultJellyfinInstallDirectory/" -Force -Recurse - if($Script:InstallAsService){ - if($Script:InstallServiceAsUser){ - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" - Start-Sleep -Milliseconds 500 - &sc.exe config Jellyfin obj=".\$($Script:UserCredentials.UserName)" password="$($Script:UserCredentials.GetNetworkCredential().Password)" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START - }else{ - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" - Start-Sleep -Milliseconds 500 - #&"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin ObjectName $Script:UserCredentials.UserName $Script:UserCredentials.GetNetworkCredential().Password - #Set-Service -Name Jellyfin -Credential $Script:UserCredentials - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START - } - } - if($Script:MigrateLibrary){ - Copy-Item -Path $Script:defaultEmbyDataDir/config -Destination $Script:JellyfinDataDir -force -Recurse - Copy-Item -Path $Script:defaultEmbyDataDir/cache -Destination $Script:JellyfinDataDir -force -Recurse - Copy-Item -Path $Script:defaultEmbyDataDir/data -Destination $Script:JellyfinDataDir -force -Recurse - Copy-Item -Path $Script:defaultEmbyDataDir/metadata -Destination $Script:JellyfinDataDir -force -Recurse - Copy-Item -Path $Script:defaultEmbyDataDir/root -Destination $Script:JellyfinDataDir -force -Recurse - } - if($Script:CreateShortcut){ - $WshShell = New-Object -comObject WScript.Shell - $Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Jellyfin.lnk") - $Shortcut.TargetPath = "$Script:DefaultJellyfinInstallDirectory\jellyfin.exe" - $Shortcut.Save() - } - if($Script:StartJellyfin){ - if($Script:InstallAsService){ - Get-Service Jellyfin | Start-Service - }else{ - Start-Process -FilePath $Script:DefaultJellyfinInstallDirectory\jellyfin.exe -PassThru - } - } -}else{ - -} -Add-Type -AssemblyName System.Windows.Forms -[System.Windows.Forms.Application]::EnableVisualStyles() - -$Script:JellyFinDataDir = "$env:LOCALAPPDATA\jellyfin\" -$Script:DefaultJellyfinInstallDirectory = "$env:Appdata\jellyfin\" -$Script:defaultEmbyDataDir = "$env:Appdata\Emby-Server\" -$Script:InstallAsService = $False -$Script:InstallServiceAsUser = $false -$Script:CreateShortcut = $false -$Script:MigrateLibrary = $false -$Script:StartJellyfin = $false - -function InstallJellyfin { - Write-Host "Install as service: $Script:InstallAsService" - Write-Host "Install as serviceuser: $Script:InstallServiceAsUser" - Write-Host "Create Shortcut: $Script:CreateShortcut" - Write-Host "MigrateLibrary: $Script:MigrateLibrary" - $GUIElementsCollection | ForEach-Object { - $_.Enabled = $false - } - Write-Host "Making Jellyfin directory" - $ProgressBar.Minimum = 1 - $ProgressBar.Maximum = 100 - $ProgressBar.Value = 1 - if($Script:DefaultJellyfinInstallDirectory -ne $InstallLocationBox.Text){ - Write-Host "Custom Install Location Chosen: $($InstallLocationBox.Text)" - $Script:DefaultJellyfinInstallDirectory = $InstallLocationBox.Text - } - if($Script:JellyfinDataDir -ne $CustomLibraryBox.Text){ - Write-Host "Custom Library Location Chosen: $($CustomLibraryBox.Text)" - $Script:JellyfinDataDir = $CustomLibraryBox.Text - } - if(-not (Test-Path $Script:DefaultJellyfinInstallDirectory)){ - mkdir $Script:DefaultJellyfinInstallDirectory - } - Write-Host "Copying Jellyfin Data" - $progressbar.Value = 10 - Copy-Item -Path $PSScriptRoot/* -Destination $Script:DefaultJellyfinInstallDirectory/ -Force -Recurse - Write-Host "Finished Copying" - $ProgressBar.Value = 50 - if($Script:InstallAsService){ - if($Script:InstallServiceAsUser){ - Write-Host "Installing Service as user $($Script:UserCredentials.UserName)" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" - Start-Sleep -Milliseconds 2000 - &sc.exe config Jellyfin obj=".\$($Script:UserCredentials.UserName)" password="$($Script:UserCredentials.GetNetworkCredential().Password)" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START - }else{ - Write-Host "Installing Service as LocalSystem" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" - Start-Sleep -Milliseconds 2000 - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START - } - } - $progressbar.Value = 60 - if($Script:MigrateLibrary){ - if($Script:defaultEmbyDataDir -ne $LibraryLocationBox.Text){ - Write-Host "Custom location defined for emby library: $($LibraryLocationBox.Text)" - $Script:defaultEmbyDataDir = $LibraryLocationBox.Text - } - Write-Host "Copying emby library from $Script:defaultEmbyDataDir to $Script:JellyFinDataDir" - Write-Host "This could take a while depending on the size of your library. Please be patient" - Write-Host "Copying config" - Copy-Item -Path $Script:defaultEmbyDataDir/config -Destination $Script:JellyfinDataDir -force -Recurse - Write-Host "Copying cache" - Copy-Item -Path $Script:defaultEmbyDataDir/cache -Destination $Script:JellyfinDataDir -force -Recurse - Write-Host "Copying data" - Copy-Item -Path $Script:defaultEmbyDataDir/data -Destination $Script:JellyfinDataDir -force -Recurse - Write-Host "Copying metadata" - Copy-Item -Path $Script:defaultEmbyDataDir/metadata -Destination $Script:JellyfinDataDir -force -Recurse - Write-Host "Copying root dir" - Copy-Item -Path $Script:defaultEmbyDataDir/root -Destination $Script:JellyfinDataDir -force -Recurse - } - $progressbar.Value = 80 - if($Script:CreateShortcut){ - Write-Host "Creating Shortcut" - $WshShell = New-Object -comObject WScript.Shell - $Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Jellyfin.lnk") - $Shortcut.TargetPath = "$Script:DefaultJellyfinInstallDirectory\jellyfin.exe" - $Shortcut.Save() - } - $ProgressBar.Value = 90 - if($Script:StartJellyfin){ - if($Script:InstallAsService){ - Write-Host "Starting Jellyfin Service" - Get-Service Jellyfin | Start-Service - }else{ - Write-Host "Starting Jellyfin" - Start-Process -FilePath $Script:DefaultJellyfinInstallDirectory\jellyfin.exe -PassThru - } - } - $progressbar.Value = 100 - Write-Host Finished - $wshell = New-Object -ComObject Wscript.Shell - $wshell.Popup("Operation Completed",0,"Done",0x1) - $InstallForm.Close() -} -function ServiceBoxCheckChanged { - if($InstallAsServiceCheck.Checked){ - $Script:InstallAsService = $true - $ServiceUserLabel.Visible = $true - $ServiceUserLabel.Enabled = $true - $ServiceUserBox.Visible = $true - $ServiceUserBox.Enabled = $true - }else{ - $Script:InstallAsService = $false - $ServiceUserLabel.Visible = $false - $ServiceUserLabel.Enabled = $false - $ServiceUserBox.Visible = $false - $ServiceUserBox.Enabled = $false - } -} -function UserSelect { - if($ServiceUserBox.Text -eq 'Local System') - { - $Script:InstallServiceAsUser = $false - $Script:UserCredentials = $null - $ServiceUserBox.Items.RemoveAt(1) - $ServiceUserBox.Items.Add("Custom User") - }elseif($ServiceUserBox.Text -eq 'Custom User'){ - $Script:InstallServiceAsUser = $true - $Script:UserCredentials = Get-Credential -Message "Please enter the credentials of the user you with to run Jellyfin Service as" -UserName $env:USERNAME - $ServiceUserBox.Items[1] = "$($Script:UserCredentials.UserName)" - } -} -function CreateShortcutBoxCheckChanged { - if($CreateShortcutCheck.Checked){ - $Script:CreateShortcut = $true - }else{ - $Script:CreateShortcut = $False - } -} -function StartJellyFinBoxCheckChanged { - if($StartProgramCheck.Checked){ - $Script:StartJellyfin = $true - }else{ - $Script:StartJellyfin = $false - } -} - -function CustomLibraryCheckChanged { - if($CustomLibraryCheck.Checked){ - $Script:UseCustomLibrary = $true - $CustomLibraryBox.Enabled = $true - }else{ - $Script:UseCustomLibrary = $false - $CustomLibraryBox.Enabled = $false - } -} - -function MigrateLibraryCheckboxChanged { - - if($MigrateLibraryCheck.Checked){ - $Script:MigrateLibrary = $true - $LibraryMigrationLabel.Visible = $true - $LibraryMigrationLabel.Enabled = $true - $LibraryLocationBox.Visible = $true - $LibraryLocationBox.Enabled = $true - }else{ - $Script:MigrateLibrary = $false - $LibraryMigrationLabel.Visible = $false - $LibraryMigrationLabel.Enabled = $false - $LibraryLocationBox.Visible = $false - $LibraryLocationBox.Enabled = $false - } - -} - - -#region begin GUI{ - -$InstallForm = New-Object system.Windows.Forms.Form -$InstallForm.ClientSize = '320,240' -$InstallForm.text = "Terrible Jellyfin Installer" -$InstallForm.TopMost = $false - -$GUIElementsCollection = @() - -$InstallButton = New-Object system.Windows.Forms.Button -$InstallButton.text = "Install" -$InstallButton.width = 60 -$InstallButton.height = 30 -$InstallButton.location = New-Object System.Drawing.Point(5,5) -$InstallButton.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $InstallButton - -$ProgressBar = New-Object system.Windows.Forms.ProgressBar -$ProgressBar.width = 245 -$ProgressBar.height = 30 -$ProgressBar.location = New-Object System.Drawing.Point(70,5) - -$InstallLocationLabel = New-Object system.Windows.Forms.Label -$InstallLocationLabel.text = "Install Location" -$InstallLocationLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft -$InstallLocationLabel.AutoSize = $true -$InstallLocationLabel.width = 100 -$InstallLocationLabel.height = 20 -$InstallLocationLabel.location = New-Object System.Drawing.Point(5,50) -$InstallLocationLabel.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $InstallLocationLabel - -$InstallLocationBox = New-Object system.Windows.Forms.TextBox -$InstallLocationBox.multiline = $false -$InstallLocationBox.width = 205 -$InstallLocationBox.height = 20 -$InstallLocationBox.location = New-Object System.Drawing.Point(110,50) -$InstallLocationBox.Text = $Script:DefaultJellyfinInstallDirectory -$InstallLocationBox.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $InstallLocationBox - -$CustomLibraryCheck = New-Object system.Windows.Forms.CheckBox -$CustomLibraryCheck.text = "Custom Library Location:" -$CustomLibraryCheck.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft -$CustomLibraryCheck.AutoSize = $false -$CustomLibraryCheck.width = 180 -$CustomLibraryCheck.height = 20 -$CustomLibraryCheck.location = New-Object System.Drawing.Point(5,75) -$CustomLibraryCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $CustomLibraryCheck - -$CustomLibraryBox = New-Object system.Windows.Forms.TextBox -$CustomLibraryBox.multiline = $false -$CustomLibraryBox.width = 130 -$CustomLibraryBox.height = 20 -$CustomLibraryBox.location = New-Object System.Drawing.Point(185,75) -$CustomLibraryBox.Text = $Script:JellyFinDataDir -$CustomLibraryBox.Font = 'Microsoft Sans Serif,10' -$CustomLibraryBox.Enabled = $false -$GUIElementsCollection += $CustomLibraryBox - -$InstallAsServiceCheck = New-Object system.Windows.Forms.CheckBox -$InstallAsServiceCheck.text = "Install as Service" -$InstallAsServiceCheck.AutoSize = $false -$InstallAsServiceCheck.width = 140 -$InstallAsServiceCheck.height = 20 -$InstallAsServiceCheck.location = New-Object System.Drawing.Point(5,125) -$InstallAsServiceCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $InstallAsServiceCheck - -$ServiceUserLabel = New-Object system.Windows.Forms.Label -$ServiceUserLabel.text = "Run Service As:" -$ServiceUserLabel.AutoSize = $true -$ServiceUserLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft -$ServiceUserLabel.width = 100 -$ServiceUserLabel.height = 20 -$ServiceUserLabel.location = New-Object System.Drawing.Point(15,145) -$ServiceUserLabel.Font = 'Microsoft Sans Serif,10' -$ServiceUserLabel.Visible = $false -$ServiceUserLabel.Enabled = $false -$GUIElementsCollection += $ServiceUserLabel - -$ServiceUserBox = New-Object system.Windows.Forms.ComboBox -$ServiceUserBox.text = "Run Service As" -$ServiceUserBox.width = 195 -$ServiceUserBox.height = 20 -@('Local System','Custom User') | ForEach-Object {[void] $ServiceUserBox.Items.Add($_)} -$ServiceUserBox.location = New-Object System.Drawing.Point(120,145) -$ServiceUserBox.Font = 'Microsoft Sans Serif,10' -$ServiceUserBox.Visible = $false -$ServiceUserBox.Enabled = $false -$ServiceUserBox.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList -$GUIElementsCollection += $ServiceUserBox - -$MigrateLibraryCheck = New-Object system.Windows.Forms.CheckBox -$MigrateLibraryCheck.text = "Import Emby/Old JF Library" -$MigrateLibraryCheck.AutoSize = $false -$MigrateLibraryCheck.width = 160 -$MigrateLibraryCheck.height = 20 -$MigrateLibraryCheck.location = New-Object System.Drawing.Point(5,170) -$MigrateLibraryCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $MigrateLibraryCheck - -$LibraryMigrationLabel = New-Object system.Windows.Forms.Label -$LibraryMigrationLabel.text = "Emby/Old JF Library Path" -$LibraryMigrationLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft -$LibraryMigrationLabel.AutoSize = $false -$LibraryMigrationLabel.width = 120 -$LibraryMigrationLabel.height = 20 -$LibraryMigrationLabel.location = New-Object System.Drawing.Point(15,190) -$LibraryMigrationLabel.Font = 'Microsoft Sans Serif,10' -$LibraryMigrationLabel.Visible = $false -$LibraryMigrationLabel.Enabled = $false -$GUIElementsCollection += $LibraryMigrationLabel - -$LibraryLocationBox = New-Object system.Windows.Forms.TextBox -$LibraryLocationBox.multiline = $false -$LibraryLocationBox.width = 175 -$LibraryLocationBox.height = 20 -$LibraryLocationBox.location = New-Object System.Drawing.Point(140,190) -$LibraryLocationBox.Text = $Script:defaultEmbyDataDir -$LibraryLocationBox.Font = 'Microsoft Sans Serif,10' -$LibraryLocationBox.Visible = $false -$LibraryLocationBox.Enabled = $false -$GUIElementsCollection += $LibraryLocationBox - -$CreateShortcutCheck = New-Object system.Windows.Forms.CheckBox -$CreateShortcutCheck.text = "Desktop Shortcut" -$CreateShortcutCheck.AutoSize = $false -$CreateShortcutCheck.width = 150 -$CreateShortcutCheck.height = 20 -$CreateShortcutCheck.location = New-Object System.Drawing.Point(5,215) -$CreateShortcutCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $CreateShortcutCheck - -$StartProgramCheck = New-Object system.Windows.Forms.CheckBox -$StartProgramCheck.text = "Start Jellyfin" -$StartProgramCheck.AutoSize = $false -$StartProgramCheck.width = 160 -$StartProgramCheck.height = 20 -$StartProgramCheck.location = New-Object System.Drawing.Point(160,215) -$StartProgramCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $StartProgramCheck - -$InstallForm.controls.AddRange($GUIElementsCollection) -$InstallForm.Controls.Add($ProgressBar) - -#region gui events { -$InstallButton.Add_Click({ InstallJellyfin }) -$CustomLibraryCheck.Add_CheckedChanged({CustomLibraryCheckChanged}) -$InstallAsServiceCheck.Add_CheckedChanged({ServiceBoxCheckChanged}) -$ServiceUserBox.Add_SelectedValueChanged({ UserSelect }) -$MigrateLibraryCheck.Add_CheckedChanged({MigrateLibraryCheckboxChanged}) -$CreateShortcutCheck.Add_CheckedChanged({CreateShortcutBoxCheckChanged}) -$StartProgramCheck.Add_CheckedChanged({StartJellyFinBoxCheckChanged}) -#endregion events } - -#endregion GUI } - - -[void]$InstallForm.ShowDialog() diff --git a/deployment/windows/legacy/install.bat b/deployment/windows/legacy/install.bat deleted file mode 100644 index e21479a79a..0000000000 --- a/deployment/windows/legacy/install.bat +++ /dev/null @@ -1 +0,0 @@ -powershell.exe -executionpolicy Bypass -file install-jellyfin.ps1 diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 92b7a03fdd..45ab725eb2 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -5,6 +5,8 @@ + + @@ -26,6 +28,8 @@ + + diff --git a/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs index 84cdbe360d..e40af703f9 100644 --- a/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupOrElevatedPolicy { var fixture = new Fixture().Customize(new AutoMoqCustomization()); _configurationManagerMock = fixture.Freeze>(); - _requirements = new List {new FirstTimeSetupOrElevatedRequirement()}; + _requirements = new List { new FirstTimeSetupOrElevatedRequirement() }; _sut = fixture.Create(); } @@ -58,7 +58,7 @@ namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupOrElevatedPolicy private static ClaimsPrincipal SetupUser(string role) { - var claims = new[] {new Claim(ClaimTypes.Role, role)}; + var claims = new[] { new Claim(ClaimTypes.Role, role) }; var identity = new ClaimsIdentity(claims); return new ClaimsPrincipal(identity); } diff --git a/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs index e2beea1ad8..cd05a8328d 100644 --- a/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs @@ -23,9 +23,9 @@ namespace Jellyfin.Api.Tests.Auth.RequiresElevationPolicy [InlineData(UserRoles.Guest, false)] public async Task ShouldHandleRolesCorrectly(string role, bool shouldSucceed) { - var requirements = new List {new RequiresElevationRequirement()}; + var requirements = new List { new RequiresElevationRequirement() }; - var claims = new[] {new Claim(ClaimTypes.Role, role)}; + var claims = new[] { new Claim(ClaimTypes.Role, role) }; var identity = new ClaimsIdentity(claims); var user = new ClaimsPrincipal(identity); diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 1d7e4f7af3..b159db2bd8 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -11,11 +11,11 @@ - - + + - + diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 86bb11bd4c..81a2242e7f 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -8,10 +8,10 @@ - + - + diff --git a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs new file mode 100644 index 0000000000..929bb92aa8 --- /dev/null +++ b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Sorting; +using Xunit; + +namespace Jellyfin.Controller.Tests +{ + public class AlphanumComparatorTests + { + private readonly Random _rng = new Random(42); + + // InlineData is pre-sorted + [Theory] + [InlineData(null, "", "1", "9", "10", "a", "z")] + [InlineData("50F", "100F", "SR9", "SR100")] + [InlineData("image-1.jpg", "image-02.jpg", "image-4.jpg", "image-9.jpg", "image-10.jpg", "image-11.jpg", "image-22.jpg")] + [InlineData("Hard drive 2GB", "Hard drive 20GB")] + [InlineData("b", "e", "è", "ě", "f", "g", "k")] + [InlineData("123456789", "123456789a", "abc", "abcd")] + [InlineData("12345678912345678912345678913234567891", "123456789123456789123456789132345678912")] + [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567891")] + [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567892")] + [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891a")] + [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")] + public void AlphanumComparatorTest(params string?[] strings) + { + var copy = (string?[])strings.Clone(); + if (strings.Length == 2) + { + var tmp = copy[0]; + copy[0] = copy[1]; + copy[1] = tmp; + } + else + { + copy.Shuffle(_rng); + } + + Array.Sort(copy, new AlphanumComparator()); + Assert.True(strings.SequenceEqual(copy)); + } + } +} diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj new file mode 100644 index 0000000000..30994dee60 --- /dev/null +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + false + true + enable + + + + + + + + + + + + + + diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index b5e4a1287b..78a020ad58 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -14,10 +14,10 @@ - + - + diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 9602d9e58a..f404b3e464 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -7,10 +7,10 @@ - + - + diff --git a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs index 9abbcc7bf0..553d06681b 100644 --- a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs @@ -6,56 +6,22 @@ namespace Jellyfin.Naming.Tests.TV { public class AbsoluteEpisodeNumberTests { - [Fact] - public void TestAbsoluteEpisodeNumber1() - { - Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/12.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber2() - { - Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 12.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber3() - { - Assert.Equal(82, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 82.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber4() - { - Assert.Equal(112, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 112.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber5() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/Foo_ep_02.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber6() - { - Assert.Equal(889, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 889.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber7() - { - Assert.Equal(101, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 101.avi")); - } - - private int? GetEpisodeNumberFromFile(string path) + [Theory] + [InlineData("The Simpsons/12.avi", 12)] + [InlineData("The Simpsons/The Simpsons 12.avi", 12)] + [InlineData("The Simpsons/The Simpsons 82.avi", 82)] + [InlineData("The Simpsons/The Simpsons 112.avi", 112)] + [InlineData("The Simpsons/Foo_ep_02.avi", 2)] + [InlineData("The Simpsons/The Simpsons 889.avi", 889)] + [InlineData("The Simpsons/The Simpsons 101.avi", 101)] + public void GetEpisodeNumberFromFileTest(string path, int episodeNumber) { var options = new NamingOptions(); var result = new EpisodeResolver(options) .Resolve(path, false, null, null, true); - return result.EpisodeNumber; + Assert.Equal(episodeNumber, result.EpisodeNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs index 29daf8cc37..6ecffe80b7 100644 --- a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs @@ -6,52 +6,17 @@ namespace Jellyfin.Naming.Tests.TV { public class DailyEpisodeTests { - [Fact] - public void TestDailyEpisode1() - { - Test(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14); - } - [Fact] - public void TestDailyEpisode2() - { - Test(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14); - } - // FIXME - // [Fact] - public void TestDailyEpisode3() - { - Test(@"/server/anything_14.11.1996.mp4", "anything", 1996, 11, 14); - } - - // FIXME - // [Fact] - public void TestDailyEpisode4() - { - Test(@"/server/A Daily Show - (2015-01-15) - Episode Name - [720p].mkv", "A Daily Show", 2015, 01, 15); - } - - [Fact] - public void TestDailyEpisode5() - { - Test(@"/server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv", "james.corden", 2017, 04, 20); - } - - [Fact] - public void TestDailyEpisode6() - { - Test(@"/server/ABC News 2018_03_24_19_00_00.mkv", "ABC News", 2018, 03, 24); - } - - // FIXME - // [Fact] - public void TestDailyEpisode7() - { - Test(@"/server/Last Man Standing_KTLADT_2018_05_25_01_28_00.wtv", "Last Man Standing", 2018, 05, 25); - } - - private void Test(string path, string seriesName, int? year, int? month, int? day) + [Theory] + [InlineData(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14)] + [InlineData(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14)] + [InlineData(@"/server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv", "james.corden", 2017, 04, 20)] + [InlineData(@"/server/ABC News 2018_03_24_19_00_00.mkv", "ABC News", 2018, 03, 24)] + // TODO: [InlineData(@"/server/anything_14.11.1996.mp4", "anything", 1996, 11, 14)] + // TODO: [InlineData(@"/server/A Daily Show - (2015-01-15) - Episode Name - [720p].mkv", "A Daily Show", 2015, 01, 15)] + // TODO: [InlineData(@"/server/Last Man Standing_KTLADT_2018_05_25_01_28_00.wtv", "Last Man Standing", 2018, 05, 25)] + public void Test(string path, string seriesName, int? year, int? month, int? day) { var options = new NamingOptions(); diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs index 5017fce4d9..5e023bdb06 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs @@ -13,6 +13,65 @@ namespace Jellyfin.Naming.Tests.TV [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 22)] [InlineData("Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", 1)] [InlineData("After Life 1x06 Episodio 6 [WEB-DL NF 1080p h264 Dual DD 5.1 Sub].mkv", 6)] + [InlineData("Season 02/S02E03 blah.avi", 3)] + [InlineData("Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4", 3)] + [InlineData("Season 02/02x03 - x04 - x15 - Ep Name.mp4", 3)] + [InlineData("Season 1/01x02 blah.avi", 2)] + [InlineData("Season 1/S01x02 blah.avi", 2)] + [InlineData("Season 1/S01E02 blah.avi", 2)] + [InlineData("Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", 3)] + [InlineData("Season 1/S01xE02 blah.avi", 2)] + [InlineData("Season 1/seriesname S01E02 blah.avi", 2)] + [InlineData("Season 2/Episode - 16.avi", 16)] + [InlineData("Season 2/Episode 16.avi", 16)] + [InlineData("Season 2/Episode 16 - Some Title.avi", 16)] + [InlineData("Season 2/16 Some Title.avi", 16)] + [InlineData("Season 2/16 - 12 Some Title.avi", 16)] + [InlineData("Season 2/7 - 12 Angry Men.avi", 7)] + [InlineData("Season 1/seriesname 01x02 blah.avi", 2)] + [InlineData("Season 25/The Simpsons.S25E09.Steal this episode.mp4", 9)] + [InlineData("Season 1/seriesname S01x02 blah.avi", 2)] + [InlineData("Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", 3)] + [InlineData("Season 1/seriesname S01xE02 blah.avi", 2)] + [InlineData("Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", 3)] + [InlineData("Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", 3)] + [InlineData("Season 2/02x03-04-15 - Ep Name.mp4", 3)] + [InlineData("Season 02/02x03-E15 - Ep Name.mp4", 3)] + [InlineData("Season 02/Elementary - 02x03-E15 - Ep Name.mp4", 3)] + [InlineData("Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", 23)] + [InlineData("Season 2009/S2009E23-E24-E26 - The Woman.mp4", 23)] + [InlineData("Season 2009/2009x02 blah.avi", 2)] + [InlineData("Season 2009/S2009x02 blah.avi", 2)] + [InlineData("Season 2009/S2009E02 blah.avi", 2)] + [InlineData("Season 2009/seriesname 2009x02 blah.avi", 2)] + [InlineData("Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4", 3)] + [InlineData("Season 2009/2009x03x04x15 - Ep Name.mp4", 3)] + [InlineData("Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4", 3)] + [InlineData("Season 2009/S2009xE02 blah.avi", 2)] + [InlineData("Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4", 23)] + [InlineData("Season 2009/seriesname S2009xE02 blah.avi", 2)] + [InlineData("Season 2009/2009x03-E15 - Ep Name.mp4", 3)] + [InlineData("Season 2009/seriesname S2009E02 blah.avi", 2)] + [InlineData("Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 3)] + [InlineData("Season 2009/2009x03 - x04 - x15 - Ep Name.mp4", 3)] + [InlineData("Season 2009/seriesname S2009x02 blah.avi", 2)] + [InlineData("Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 3)] + [InlineData("Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4", 3)] + [InlineData("Season 2009/2009x03-04-15 - Ep Name.mp4", 3)] + [InlineData("Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4", 3)] + [InlineData("Season 1/02 - blah-02 a.avi", 2)] + [InlineData("Season 1/02 - blah.avi", 2)] + [InlineData("Season 2/02 - blah 14 blah.avi", 2)] + [InlineData("Season 2/02.avi", 2)] + [InlineData("Season 2/2. Infestation.avi", 2)] + [InlineData("The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", 7)] + [InlineData("Running Man/Running Man S2017E368.mkv", 368)] + // TODO: [InlineData("Season 2/16 12 Some Title.avi", 16)] + // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", 7)] + // TODO: [InlineData("Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", 3)] + // TODO: [InlineData("Season 2/7 12 Angry Men.avi", 7)] + // TODO: [InlineData("Season 02/02x03x04x15 - Ep Name.mp4", 2)] + // TODO: [InlineData("Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv", 136)] public void GetEpisodeNumberFromFileTest(string path, int? expected) { var result = new EpisodePathParser(_namingOptions) @@ -20,387 +79,5 @@ namespace Jellyfin.Naming.Tests.TV Assert.Equal(expected, result.EpisodeNumber); } - - [Fact] - public void TestEpisodeNumber1() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/S02E03 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber40() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber41() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/01x02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber42() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber43() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber44() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber45() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber46() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber47() - { - Assert.Equal(36, GetEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv")); - } - - [Fact] - public void TestEpisodeNumber52() - { - Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode - 16.avi")); - } - - [Fact] - public void TestEpisodeNumber53() - { - Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16.avi")); - } - - [Fact] - public void TestEpisodeNumber54() - { - Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16 - Some Title.avi")); - } - - [Fact] - public void TestEpisodeNumber57() - { - Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 Some Title.avi")); - } - - [Fact] - public void TestEpisodeNumber58() - { - Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 - 12 Some Title.avi")); - } - - [Fact] - public void TestEpisodeNumber59() - { - Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 - 12 Angry Men.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumber60() - { - Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 12 Some Title.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumber61() - { - Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 12 Angry Men.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumber62() - { - // This is not supported. Expected to fail, although it would be a good one to add support for. - Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.E03.avi")); - } - - [Fact] - public void TestEpisodeNumber63() - { - Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.S04E03.avi")); - } - - [Fact] - public void TestEpisodeNumber64() - { - Assert.Equal(368, GetEpisodeNumberFromFile(@"Running Man/Running Man S2017E368.mkv")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumber65() - { - // Not supported yet - Assert.Equal(7, GetEpisodeNumberFromFile(@"/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv")); - } - - [Fact] - public void TestEpisodeNumber30() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumber31() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber32() - { - Assert.Equal(9, GetEpisodeNumberFromFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4")); - } - - [Fact] - public void TestEpisodeNumber33() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber34() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber35() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber36() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber37() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber38() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber39() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber20() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber21() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber22() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber23() - { - Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestEpisodeNumber24() - { - Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestEpisodeNumber25() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber26() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber27() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumber28() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber29() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber11() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber12() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber13() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber14() - { - Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestEpisodeNumber15() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber16() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber17() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber18() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber19() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber2() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber3() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber4() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber5() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber6() - { - Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestEpisodeNumber7() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi")); - } - - [Fact] - public void TestEpisodeNumber8() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah.avi")); - } - - [Fact] - public void TestEpisodeNumber9() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi")); - } - - [Fact] - public void TestEpisodeNumber10() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02.avi")); - } - - [Fact] - public void TestEpisodeNumber48() - { - Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/2. Infestation.avi")); - } - - [Fact] - public void TestEpisodeNumber49() - { - Assert.Equal(7, GetEpisodeNumberFromFile(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi")); - } - - private int? GetEpisodeNumberFromFile(string path) - { - var result = new EpisodePathParser(_namingOptions) - .Parse(path, false); - - return result.EpisodeNumber; - } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs index 00aa9ee7c2..0c7d9520e2 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs @@ -6,122 +6,31 @@ namespace Jellyfin.Naming.Tests.TV { public class EpisodeNumberWithoutSeasonTests { - [Fact] - public void TestEpisodeNumberWithoutSeason1() - { - Assert.Equal(8, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4")); - } - [Fact] - public void TestEpisodeNumberWithoutSeason2() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 - Ep Name.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason3() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason4() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02 - Ep Name.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason5() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02-Ep Name.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason6() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.EpName.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason7() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason8() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 Ep Name.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumberWithoutSeason9() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 - Ep Name.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumberWithoutSeason10() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 Ep Name.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumberWithoutSeason11() - { - Assert.Equal(7, GetEpisodeNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi")); - Assert.Equal(8, GetSeasonNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason12() - { - Assert.Equal(7, GetEpisodeNumberFromFile(@"GJ Club (2013)/GJ Club - 07.mkv")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumberWithoutSeason13() - { - // This is not supported anymore after removing the episode number 365+ hack from EpisodePathParser - Assert.Equal(13, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 13.mkv")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason14() - { - Assert.Equal(3, GetSeasonNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv")); - Assert.Equal(17, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason15() - { - Assert.Equal(2017, GetSeasonNumberFromFile(@"Running Man/Running Man S2017E368.mkv")); - } - - private int? GetEpisodeNumberFromFile(string path) + [Theory] + [InlineData(8, @"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4")] + [InlineData(2, @"The Simpsons/The Simpsons - 02 - Ep Name.avi")] + [InlineData(2, @"The Simpsons/02.avi")] + [InlineData(2, @"The Simpsons/02 - Ep Name.avi")] + [InlineData(2, @"The Simpsons/02-Ep Name.avi")] + [InlineData(2, @"The Simpsons/02.EpName.avi")] + [InlineData(2, @"The Simpsons/The Simpsons - 02.avi")] + [InlineData(2, @"The Simpsons/The Simpsons - 02 Ep Name.avi")] + [InlineData(7, @"GJ Club (2013)/GJ Club - 07.mkv")] + [InlineData(17, @"Case Closed (1996-2007)/Case Closed - 317.mkv")] + // TODO: [InlineData(2, @"The Simpsons/The Simpsons 5 - 02 - Ep Name.avi")] + // TODO: [InlineData(2, @"The Simpsons/The Simpsons 5 - 02 Ep Name.avi")] + // TODO: [InlineData(7, @"Seinfeld/Seinfeld 0807 The Checks.avi")] + // This is not supported anymore after removing the episode number 365+ hack from EpisodePathParser + // TODO: [InlineData(13, @"Case Closed (1996-2007)/Case Closed - 13.mkv")] + public void GetEpisodeNumberFromFileTest(int episodeNumber, string path) { var options = new NamingOptions(); var result = new EpisodeResolver(options) .Resolve(path, false); - return result.EpisodeNumber; + Assert.Equal(episodeNumber, result.EpisodeNumber); } - - private int? GetSeasonNumberFromFile(string path) - { - var options = new NamingOptions(); - - var result = new EpisodeResolver(options) - .Resolve(path, false); - - return result.SeasonNumber; - } - } } diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs index da6e993100..4b56067153 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs @@ -15,7 +15,27 @@ namespace Jellyfin.Naming.Tests.TV [InlineData("D:\\media\\Foo - S04E011", "Foo", 4, 11)] [InlineData("D:\\media\\Foo\\Foo s01x01", "Foo", 1, 1)] [InlineData("D:\\media\\Foo (2019)\\Season 4\\Foo (2019).S04E03", "Foo (2019)", 4, 3)] + [InlineData("/Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 1/seriesname S01E02 blah.avi", "seriesname", 1, 2)] + [InlineData("/Running Man/Running Man S2017E368.mkv", "Running Man", 2017, 368)] + [InlineData("/Season 1/seriesname 01x02 blah.avi", "seriesname", 1, 2)] + [InlineData("/Season 25/The Simpsons.S25E09.Steal this episode.mp4", "The Simpsons", 25, 9)] + [InlineData("/Season 1/seriesname S01x02 blah.avi", "seriesname", 1, 2)] + [InlineData("/Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 1/seriesname S01xE02 blah.avi", "seriesname", 1, 2)] + [InlineData("/Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 02/Elementary - 02x03-E15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", "Elementary", 1, 23)] + [InlineData("/The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", "The Wonder Years", 4, 7)] + // TODO: [InlineData("/Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", "Castle Rock", 2, 1)] + // TODO: [InlineData("/After Life 1x06 Episodio 6 [WEB-DL NF 1080p h264 Dual DD 5.1 Sub].mkv", "After Life", 1, 6)] + // TODO: [InlineData("/Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", "Uchuu Senkan Yamoto 2199", 4, 3)] + // TODO: [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", "The Daily Show", 25, 22)] + // TODO: [InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", "Watchmen (2019)", 1, 3)] + // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", "The Legend of Condor Heroes 2017", 1, 7)] public void ParseEpisodesCorrectly(string path, string name, int season, int episode) + { NamingOptions o = new NamingOptions(); EpisodePathParser p = new EpisodePathParser(o); @@ -26,22 +46,5 @@ namespace Jellyfin.Naming.Tests.TV Assert.Equal(season, res.SeasonNumber); Assert.Equal(episode, res.EpisodeNumber); } - - [Theory] - [InlineData("/media/Foo/Foo 889", "Foo", 889)] - [InlineData("/media/Foo/[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)] - [InlineData("D:\\media\\Foo\\Foo 889", "Foo", 889)] - [InlineData("D:\\media\\Foo\\[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)] - public void ParseEpisodeWithoutSeason(string path, string name, int episode) - { - NamingOptions o = new NamingOptions(); - EpisodePathParser p = new EpisodePathParser(o); - var res = p.Parse(path, true, fillExtendedInfo: true); - - Assert.True(res.Success); - Assert.Equal(name, res.SeriesName); - Assert.Null(res.SeasonNumber); - Assert.Equal(episode, res.EpisodeNumber); - } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs index c2851ccdb1..364eb7ff85 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs @@ -6,42 +6,13 @@ namespace Jellyfin.Naming.Tests.TV { public class EpisodeWithoutSeasonTests { - // FIXME - // [Fact] - public void TestWithoutSeason1() - { - Test(@"/server/anything_ep02.mp4", "anything", null, 2); - } - - // FIXME - // [Fact] - public void TestWithoutSeason2() - { - Test(@"/server/anything_ep_02.mp4", "anything", null, 2); - } - - // FIXME - // [Fact] - public void TestWithoutSeason3() - { - Test(@"/server/anything_part.II.mp4", "anything", null, null); - } - - // FIXME - // [Fact] - public void TestWithoutSeason4() - { - Test(@"/server/anything_pt.II.mp4", "anything", null, null); - } - - // FIXME - // [Fact] - public void TestWithoutSeason5() - { - Test(@"/server/anything_pt_II.mp4", "anything", null, null); - } - - private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) + // TODO: [Theory] + // TODO: [InlineData(@"/server/anything_ep02.mp4", "anything", null, 2)] + // TODO: [InlineData(@"/server/anything_ep_02.mp4", "anything", null, 2)] + // TODO: [InlineData(@"/server/anything_part.II.mp4", "anything", null, null)] + // TODO: [InlineData(@"/server/anything_pt.II.mp4", "anything", null, null)] + // TODO: [InlineData(@"/server/anything_pt_II.mp4", "anything", null, null)] + public void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) { var options = new NamingOptions(); diff --git a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs index b15dd6b74c..3513050b64 100644 --- a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs @@ -6,100 +6,75 @@ namespace Jellyfin.Naming.Tests.TV { public class MultiEpisodeTests { - [Fact] - public void TestGetEndingEpisodeNumberFromFile() - { - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/4x01 – 20 Hours in America (1).mkv")); - - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/01x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 04 Ep Name.mp4")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/My show name 02x03 - 04 Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4")); - Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4")); - Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4")); - - - // Four Digits seasons - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); - Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); - Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); - - // Without season number - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02.avi")); - - Assert.Equal(3, GetEndingEpisodeNumberFromFile(@"Season 1/02-03 - blah.avi")); - Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04 - blah 14 blah.avi")); - Assert.Equal(5, GetEndingEpisodeNumberFromFile(@"Season 1/02-05 - blah-02 a.avi")); - Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv")); - - // With format specification that must not be detected as ending episode number - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-1080p.mkv")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720p.mkv")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720i.mkv")); - Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 1/MOONLIGHTING_s01e01-e04.mkv")); - } - - [Fact] - public void TestGetEndingEpisodeNumberFromFolder() - { - Assert.Equal(4, GetEndingEpisodeNumberFromFolder(@"Season 1/MOONLIGHTING_s01e01-e04")); - } - - private int? GetEndingEpisodeNumberFromFolder(string path) + [Theory] + [InlineData(@"Season 1/4x01 – 20 Hours in America (1).mkv", null)] + [InlineData(@"Season 1/01x02 blah.avi", null)] + [InlineData(@"Season 1/S01x02 blah.avi", null)] + [InlineData(@"Season 1/S01E02 blah.avi", null)] + [InlineData(@"Season 1/S01xE02 blah.avi", null)] + [InlineData(@"Season 1/seriesname 01x02 blah.avi", null)] + [InlineData(@"Season 1/seriesname S01x02 blah.avi", null)] + [InlineData(@"Season 1/seriesname S01E02 blah.avi", null)] + [InlineData(@"Season 1/seriesname S01xE02 blah.avi", null)] + [InlineData(@"Season 2/02x03 - 04 Ep Name.mp4", null)] + [InlineData(@"Season 2/My show name 02x03 - 04 Ep Name.mp4", null)] + [InlineData(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2/02x03-04-15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/02x03-E15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/02x03x04x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", 26)] + [InlineData(@"Season 1/S01E23-E24-E26 - The Woman.mp4", 26)] + // Four Digits seasons + [InlineData(@"Season 2009/2009x02 blah.avi", null)] + [InlineData(@"Season 2009/S2009x02 blah.avi", null)] + [InlineData(@"Season 2009/S2009E02 blah.avi", null)] + [InlineData(@"Season 2009/S2009xE02 blah.avi", null)] + [InlineData(@"Season 2009/seriesname 2009x02 blah.avi", null)] + [InlineData(@"Season 2009/seriesname S2009x02 blah.avi", null)] + [InlineData(@"Season 2009/seriesname S2009E02 blah.avi", null)] + [InlineData(@"Season 2009/seriesname S2009xE02 blah.avi", null)] + [InlineData(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03-04-15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03-E15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03x04x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4", 26)] + [InlineData(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4", 26)] + // Without season number + [InlineData(@"Season 1/02 - blah.avi", null)] + [InlineData(@"Season 2/02 - blah 14 blah.avi", null)] + [InlineData(@"Season 1/02 - blah-02 a.avi", null)] + [InlineData(@"Season 2/02.avi", null)] + [InlineData(@"Season 1/02-03 - blah.avi", 3)] + [InlineData(@"Season 2/02-04 - blah 14 blah.avi", 4)] + [InlineData(@"Season 1/02-05 - blah-02 a.avi", 5)] + [InlineData(@"Season 2/02-04.avi", 4)] + [InlineData(@"Season 2 /[HorribleSubs] Hunter X Hunter - 136[720p].mkv", null)] + // With format specification that must not be detected as ending episode number + [InlineData(@"Season 1/series-s09e14-1080p.mkv", null)] + [InlineData(@"Season 1/series-s09e14-720p.mkv", null)] + [InlineData(@"Season 1/series-s09e14-720i.mkv", null)] + [InlineData(@"Season 1/MOONLIGHTING_s01e01-e04.mkv", 4)] + [InlineData(@"Season 1/MOONLIGHTING_s01e01-e04", 4)] + public void TestGetEndingEpisodeNumberFromFile(string filename, int? endingEpisodeNumber) { var options = new NamingOptions(); var result = new EpisodePathParser(options) - .Parse(path, true); + .Parse(filename, false); - return result.EndingEpsiodeNumber; - } - - private int? GetEndingEpisodeNumberFromFile(string path) - { - var options = new NamingOptions(); - - var result = new EpisodePathParser(options) - .Parse(path, false); - - return result.EndingEpsiodeNumber; + Assert.Equal(result.EndingEpsiodeNumber, endingEpisodeNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs index df683fc342..078f940b29 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs @@ -5,107 +5,27 @@ namespace Jellyfin.Naming.Tests.TV { public class SeasonFolderTests { - [Fact] - public void TestGetSeasonNumberFromPath1() - { - Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1")); - } - - [Fact] - public void TestGetSeasonNumberFromPath2() - { - Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 2")); - } - - [Fact] - public void TestGetSeasonNumberFromPath3() - { - Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 02")); - } - - [Fact] - public void TestGetSeasonNumberFromPath4() - { - Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1")); - } - - [Fact] - public void TestGetSeasonNumberFromPath5() - { - Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/S02")); - } - - [Fact] - public void TestGetSeasonNumberFromPath6() - { - Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/2")); - } - - [Fact] - public void TestGetSeasonNumberFromPath7() - { - Assert.Equal(2009, GetSeasonNumberFromPath(@"/Drive/Season 2009")); - } - - [Fact] - public void TestGetSeasonNumberFromPath8() - { - Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season1")); - } - - [Fact] - public void TestGetSeasonNumberFromPath9() - { - Assert.Equal(4, GetSeasonNumberFromPath(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH")); - } - - [Fact] - public void TestGetSeasonNumberFromPath10() - { - Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Season 7 (2016)")); - } - - [Fact] - public void TestGetSeasonNumberFromPath11() - { - Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Staffel 7 (2016)")); - } - - [Fact] - public void TestGetSeasonNumberFromPath12() - { - Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Stagione 7 (2016)")); - } - - [Fact] - public void TestGetSeasonNumberFromPath14() - { - Assert.Null(GetSeasonNumberFromPath(@"/Drive/Season (8)")); - } - - [Fact] - public void TestGetSeasonNumberFromPath13() - { - Assert.Equal(3, GetSeasonNumberFromPath(@"/Drive/3.Staffel")); - } - - [Fact] - public void TestGetSeasonNumberFromPath15() - { - Assert.Null(GetSeasonNumberFromPath(@"/Drive/s06e05")); - } - - [Fact] - public void TestGetSeasonNumberFromPath16() - { - Assert.Null(GetSeasonNumberFromPath(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv")); - } - - private int? GetSeasonNumberFromPath(string path) + [Theory] + [InlineData(@"/Drive/Season 1", 1)] + [InlineData(@"/Drive/Season 2", 2)] + [InlineData(@"/Drive/Season 02", 2)] + [InlineData(@"/Drive/Seinfeld/S02", 2)] + [InlineData(@"/Drive/Seinfeld/2", 2)] + [InlineData(@"/Drive/Season 2009", 2009)] + [InlineData(@"/Drive/Season1", 1)] + [InlineData(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH", 4)] + [InlineData(@"/Drive/Season 7 (2016)", 7)] + [InlineData(@"/Drive/Staffel 7 (2016)", 7)] + [InlineData(@"/Drive/Stagione 7 (2016)", 7)] + [InlineData(@"/Drive/Season (8)", null)] + [InlineData(@"/Drive/3.Staffel", 3)] + [InlineData(@"/Drive/s06e05", null)] + [InlineData(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv", null)] + public void GetSeasonNumberFromPathTest(string path, int? seasonNumber) { var result = SeasonPathParser.Parse(path, true, true); - return result.SeasonNumber; + Assert.Equal(result.SeasonNumber, seasonNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs index 1df28c974c..9eaf897b9e 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs @@ -10,6 +10,50 @@ namespace Jellyfin.Naming.Tests.TV [Theory] [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 25)] + [InlineData("/Show/Season 02/S02E03 blah.avi", 2)] + [InlineData("Season 1/seriesname S01x02 blah.avi", 1)] + [InlineData("Season 1/S01x02 blah.avi", 1)] + [InlineData("Season 1/seriesname S01xE02 blah.avi", 1)] + [InlineData("Season 1/01x02 blah.avi", 1)] + [InlineData("Season 1/S01E02 blah.avi", 1)] + [InlineData("Season 1/S01xE02 blah.avi", 1)] + [InlineData("Season 1/seriesname 01x02 blah.avi", 1)] + [InlineData("Season 1/seriesname S01E02 blah.avi", 1)] + [InlineData("Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", 2)] + [InlineData("Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4", 2)] + [InlineData("Season 2/02x03-04-15 - Ep Name.mp4", 2)] + [InlineData("Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", 2)] + [InlineData("Season 02/02x03-E15 - Ep Name.mp4", 2)] + [InlineData("Season 02/Elementary - 02x03-E15 - Ep Name.mp4", 2)] + [InlineData("Season 02/02x03 - x04 - x15 - Ep Name.mp4", 2)] + [InlineData("Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", 2)] + [InlineData("Season 02/02x03x04x15 - Ep Name.mp4", 2)] + [InlineData("Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", 2)] + [InlineData("Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", 1)] + [InlineData("Season 1/S01E23-E24-E26 - The Woman.mp4", 1)] + [InlineData("Season 25/The Simpsons.S25E09.Steal this episode.mp4", 25)] + [InlineData("The Simpsons/The Simpsons.S25E09.Steal this episode.mp4", 25)] + [InlineData("2016/Season s2016e1.mp4", 2016)] + [InlineData("2016/Season 2016x1.mp4", 2016)] + [InlineData("Season 2009/2009x02 blah.avi", 2009)] + [InlineData("Season 2009/S2009x02 blah.avi", 2009)] + [InlineData("Season 2009/S2009E02 blah.avi", 2009)] + [InlineData("Season 2009/S2009xE02 blah.avi", 2009)] + [InlineData("Season 2009/seriesname 2009x02 blah.avi", 2009)] + [InlineData("Season 2009/seriesname S2009x02 blah.avi", 2009)] + [InlineData("Season 2009/seriesname S2009E02 blah.avi", 2009)] + [InlineData("Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/2009x03-04-15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/2009x03x04x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4", 2009)] + [InlineData("Season 2009/S2009E23-E24-E26 - The Woman.mp4", 2009)] + [InlineData("Series/1-12 - The Woman.mp4", 1)] + [InlineData(@"Running Man/Running Man S2017E368.mkv", 2017)] + [InlineData(@"Case Closed (1996-2007)/Case Closed - 317.mkv", 3)] + // TODO: [InlineData(@"Seinfeld/Seinfeld 0807 The Checks.avi", 8)] public void GetSeasonNumberFromEpisodeFileTest(string path, int? expected) { var result = new EpisodeResolver(_namingOptions) @@ -17,299 +61,5 @@ namespace Jellyfin.Naming.Tests.TV Assert.Equal(expected, result.SeasonNumber); } - - private int? GetSeasonNumberFromEpisodeFile(string path) - { - var result = new EpisodeResolver(_namingOptions) - .Resolve(path, false); - - return result.SeasonNumber; - } - - [Fact] - public void TestSeasonNumber1() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"/Show/Season 02/S02E03 blah.avi")); - } - - [Fact] - public void TestSeasonNumber2() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber3() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber4() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01xE02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber5() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber6() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber7() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01xE02 blah.avi")); - } - - // FIXME - // [Fact] - public void TestSeasonNumber8() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname 01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber9() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber10() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01E02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber11() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber12() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber13() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber14() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber15() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03-E15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber16() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber17() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber18() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber19() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber20() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber21() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestSeasonNumber22() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestSeasonNumber23() - { - Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4")); - } - - [Fact] - public void TestSeasonNumber24() - { - Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"The Simpsons/The Simpsons.S25E09.Steal this episode.mp4")); - } - - [Fact] - public void TestSeasonNumber25() - { - Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season s2016e1.mp4")); - } - - // FIXME - // [Fact] - public void TestSeasonNumber26() - { - // This convention is not currently supported, just adding in case we want to look at it in the future - Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season 2016x1.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber1() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber2() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009x02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber3() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber4() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009xE02 blah.avi")); - } - - // FIXME - // [Fact] - public void TestFourDigitSeasonNumber5() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname 2009x02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber6() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009x02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber7() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009E02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber8() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber9() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber10() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber11() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber12() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber13() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber14() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber15() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber16() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber17() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber18() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber19() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber20() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestNoSeriesFolder() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Series/1-12 - The Woman.mp4")); - } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs index c9323c218e..de253ce375 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs @@ -6,81 +6,25 @@ namespace Jellyfin.Naming.Tests.TV { public class SimpleEpisodeTests { - [Fact] - public void TestSimpleEpisodePath1() - { - Test(@"/server/anything_s01e02.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath2() - { - Test(@"/server/anything_s1e2.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath3() - { - Test(@"/server/anything_s01.e02.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath4() - { - Test(@"/server/anything_s01_e02.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath5() - { - Test(@"/server/anything_102.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath6() - { - Test(@"/server/anything_1x02.mp4", "anything", 1, 2); - } - - // FIXME - // [Fact] - public void TestSimpleEpisodePath7() - { - Test(@"/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1); - } - - [Fact] - public void TestSimpleEpisodePath8() - { - Test(@"/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1); - } - - - [Fact] - public void TestSimpleEpisodePath9() - { - Test(@"/server/Temp/S01E02 foo.mp4", string.Empty, 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath10() - { - Test(@"Series/4-12 - The Woman.mp4", string.Empty, 4, 12); - } - - [Fact] - public void TestSimpleEpisodePath11() - { - Test(@"Series/4x12 - The Woman.mp4", string.Empty, 4, 12); - } - - [Fact] - public void TestSimpleEpisodePath12() - { - Test(@"Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32); - } - - private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) + [Theory] + [InlineData("/server/anything_s01e02.mp4", "anything", 1, 2)] + [InlineData("/server/anything_s1e2.mp4", "anything", 1, 2)] + [InlineData("/server/anything_s01.e02.mp4", "anything", 1, 2)] + [InlineData("/server/anything_102.mp4", "anything", 1, 2)] + [InlineData("/server/anything_1x02.mp4", "anything", 1, 2)] + [InlineData("/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1)] + [InlineData("/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1)] + [InlineData("/server/Temp/S01E02 foo.mp4", "", 1, 2)] + [InlineData("Series/4-12 - The Woman.mp4", "", 4, 12)] + [InlineData("Series/4x12 - The Woman.mp4", "", 4, 12)] + [InlineData("Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32)] + [InlineData("[Baz-Bar]Foo - [1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)] + [InlineData(@"/Foo/The.Series.Name.S01E04.WEBRip.x264-Baz[Bar]/the.series.name.s01e04.webrip.x264-Baz[Bar].mkv", "The.Series.Name", 1, 4)] + [InlineData(@"Love.Death.and.Robots.S01.1080p.NF.WEB-DL.DDP5.1.x264-NTG/Love.Death.and.Robots.S01E01.Sonnies.Edge.1080p.NF.WEB-DL.DDP5.1.x264-NTG.mkv", "Love.Death.and.Robots", 1, 1)] + // TODO: [InlineData("[Baz-Bar]Foo - 01 - 12[1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)] + // TODO: [InlineData("E:\\Anime\\Yahari Ore no Seishun Love Comedy wa Machigatteiru\\Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku\\Oregairu Zoku 11 - Hayama Hayato Always Renconds to Everyone's Expectations..mkv", "Yahari Ore no Seishun Love Comedy wa Machigatteiru", null, 11)] + // TODO: [InlineData(@"/Library/Series/The Grand Tour (2016)/Season 1/S01E01 The Holy Trinity.mkv", "The Grand Tour", 1, 1)] + public void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) { var options = new NamingOptions(); 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 29733a1c47..b7865439c7 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -14,7 +14,7 @@ - +