diff --git a/.ci/azure-pipelines-compat.yml b/.ci/azure-pipelines-abi.yml
similarity index 68%
rename from .ci/azure-pipelines-compat.yml
rename to .ci/azure-pipelines-abi.yml
index 762bbdcb2e..635aa759ca 100644
--- a/.ci/azure-pipelines-compat.yml
+++ b/.ci/azure-pipelines-abi.yml
@@ -1,13 +1,13 @@
parameters:
- - name: Packages
- type: object
- default: {}
- - name: LinuxImage
- type: string
- default: "ubuntu-latest"
- - name: DotNetSdkVersion
- type: string
- default: 3.1.100
+- name: Packages
+ type: object
+ default: {}
+- name: LinuxImage
+ type: string
+ default: "ubuntu-latest"
+- name: DotNetSdkVersion
+ type: string
+ default: 3.1.100
jobs:
- job: CompatibilityCheck
@@ -23,7 +23,7 @@ jobs:
NugetPackageName: ${{ Package.value.NugetPackageName }}
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
maxParallel: 2
- dependsOn: MainBuild
+ dependsOn: Build
steps:
- checkout: none
@@ -33,6 +33,13 @@ jobs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
+ - task: DotNetCoreCLI@2
+ displayName: 'Install ABI CompatibilityChecker tool'
+ inputs:
+ command: custom
+ custom: tool
+ arguments: 'update compatibilitychecker -g'
+
- task: DownloadPipelineArtifact@2
displayName: "Download New Assembly Build Artifact"
inputs:
@@ -72,25 +79,11 @@ jobs:
overWrite: true
flattenFolders: true
- - task: DownloadGitHubRelease@0
- displayName: "Download ABI Compatibility Check Tool"
- inputs:
- connection: Jellyfin Release Download
- userRepository: EraYaN/dotnet-compatibility
- defaultVersionType: "latest"
- itemPattern: "**-ci.zip"
- downloadPath: "$(System.ArtifactsDirectory)"
-
- - task: ExtractFiles@1
- displayName: "Extract ABI Compatibility Check Tool"
- inputs:
- archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
- destinationFolder: $(System.ArtifactsDirectory)/tools
- cleanDestinationFolder: true
-
# The `--warnings-only` switch will swallow the return code and not emit any errors.
- - task: CmdLine@2
- displayName: "Execute ABI Compatibility Check Tool"
+ - task: DotNetCoreCLI@2
+ displayName: 'Execute ABI Compatibility Check Tool'
inputs:
- script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
+ command: custom
+ custom: compat
+ arguments: 'current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
workingDirectory: $(System.ArtifactsDirectory)
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
index 09901b2a6a..2a1c0e6f22 100644
--- a/.ci/azure-pipelines-main.yml
+++ b/.ci/azure-pipelines-main.yml
@@ -1,101 +1,93 @@
parameters:
- LinuxImage: "ubuntu-latest"
- RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
+ LinuxImage: 'ubuntu-latest'
+ RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
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 }}"
+ vmImage: '${{ parameters.LinuxImage }}'
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')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+ - task: DownloadPipelineArtifact@2
+ displayName: 'Download Web Branch'
+ condition: 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"
+ path: '$(Agent.TempDirectory)'
+ artifact: 'jellyfin-web-production'
+ source: 'specific'
+ project: 'jellyfin'
+ pipeline: 'Jellyfin Web'
+ runBranch: variables['Build.SourceBranch']
- - task: CmdLine@2
- displayName: "Clone Web Client (PR)"
- 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'))
+ - task: DownloadPipelineArtifact@2
+ displayName: 'Download Web Target'
+ condition: eq(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"
+ path: '$(Agent.TempDirectory)'
+ artifact: 'jellyfin-web-production'
+ source: 'specific'
+ project: 'jellyfin'
+ pipeline: 'Jellyfin Web'
+ runBranch: variables['System.PullRequest.TargetBranch']
- - 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')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+ - task: ExtractFiles@1
+ displayName: 'Extract Web Client'
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')), eq(variables['BuildConfiguration'], 'Release'), 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')), eq(variables['BuildConfiguration'], 'Release'), 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
+ archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
+ destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
+ cleanDestinationFolder: false
- task: UseDotNet@2
- displayName: "Update DotNet"
+ displayName: 'Update DotNet'
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- task: DotNetCoreCLI@2
- displayName: "Publish Server"
+ displayName: 'Publish Server'
inputs:
command: publish
publishWebProjects: false
- projects: "${{ parameters.RestoreBuildProjects }}"
- arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)"
+ projects: '${{ parameters.RestoreBuildProjects }}'
+ arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: false
- task: PublishPipelineArtifact@0
- displayName: "Publish Artifact Naming"
+ displayName: 'Publish Artifact Naming'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
- targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll"
- artifactName: "Jellyfin.Naming"
+ targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
+ artifactName: 'Jellyfin.Naming'
- task: PublishPipelineArtifact@0
- displayName: "Publish Artifact Controller"
+ displayName: 'Publish Artifact Controller'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
- targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
- artifactName: "Jellyfin.Controller"
+ targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
+ artifactName: 'Jellyfin.Controller'
- task: PublishPipelineArtifact@0
- displayName: "Publish Artifact Model"
+ displayName: 'Publish Artifact Model'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
- targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
- artifactName: "Jellyfin.Model"
+ targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
+ artifactName: 'Jellyfin.Model'
- task: PublishPipelineArtifact@0
- displayName: "Publish Artifact Common"
+ displayName: 'Publish Artifact Common'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
- targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
- artifactName: "Jellyfin.Common"
+ targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
+ artifactName: 'Jellyfin.Common'
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
new file mode 100644
index 0000000000..2721112991
--- /dev/null
+++ b/.ci/azure-pipelines-package.yml
@@ -0,0 +1,163 @@
+jobs:
+- job: BuildPackage
+ displayName: 'Build Packages'
+
+ strategy:
+ matrix:
+ CentOS.amd64:
+ BuildConfiguration: centos.amd64
+ Fedora.amd64:
+ BuildConfiguration: fedora.amd64
+ Debian.amd64:
+ BuildConfiguration: debian.amd64
+ Debian.arm64:
+ BuildConfiguration: debian.arm64
+ Debian.armhf:
+ BuildConfiguration: debian.armhf
+ Ubuntu.amd64:
+ BuildConfiguration: ubuntu.amd64
+ Ubuntu.arm64:
+ BuildConfiguration: ubuntu.arm64
+ Ubuntu.armhf:
+ BuildConfiguration: ubuntu.armhf
+ Linux.amd64:
+ BuildConfiguration: linux.amd64
+ Windows.amd64:
+ BuildConfiguration: windows.amd64
+ MacOS:
+ BuildConfiguration: macos
+ Portable:
+ BuildConfiguration: portable
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
+ displayName: 'Build Dockerfile'
+
+ - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
+ displayName: 'Run Dockerfile (unstable)'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
+
+ - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
+ displayName: 'Run Dockerfile (stable)'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
+
+ - task: PublishPipelineArtifact@1
+ displayName: 'Publish Release'
+ inputs:
+ targetPath: '$(Build.SourcesDirectory)/deployment/dist'
+ artifactName: 'jellyfin-server-$(BuildConfiguration)'
+
+ - task: SSH@0
+ displayName: 'Create target directory on repository server'
+ inputs:
+ sshEndpoint: repository
+ runOptions: 'inline'
+ inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
+
+ - task: CopyFilesOverSSH@0
+ displayName: 'Upload artifacts to repository server'
+ inputs:
+ sshEndpoint: repository
+ sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
+ contents: '**'
+ targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
+
+- job: BuildDocker
+ displayName: 'Build Docker'
+
+ strategy:
+ matrix:
+ amd64:
+ BuildConfiguration: amd64
+ arm64:
+ BuildConfiguration: arm64
+ armhf:
+ BuildConfiguration: armhf
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - task: Docker@2
+ displayName: 'Push Unstable Image'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
+ inputs:
+ repository: 'jellyfin/jellyfin-server'
+ command: buildAndPush
+ buildContext: '.'
+ Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
+ containerRegistry: Docker Hub
+ tags: |
+ unstable-$(Build.BuildNumber)-$(BuildConfiguration)
+ unstable-$(BuildConfiguration)
+
+ - task: Docker@2
+ displayName: 'Push Stable Image'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
+ inputs:
+ repository: 'jellyfin/jellyfin-server'
+ command: buildAndPush
+ buildContext: '.'
+ Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
+ containerRegistry: Docker Hub
+ tags: |
+ stable-$(Build.BuildNumber)-$(BuildConfiguration)
+ stable-$(BuildConfiguration)
+
+- job: CollectArtifacts
+ displayName: 'Collect Artifacts'
+ dependsOn:
+ - BuildPackage
+ - BuildDocker
+ condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - task: SSH@0
+ displayName: 'Update Unstable Repository'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
+ inputs:
+ sshEndpoint: repository
+ runOptions: 'inline'
+ inline: |
+ sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable
+ rm $0
+ exit
+
+ - task: SSH@0
+ displayName: 'Update Stable Repository'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
+ inputs:
+ sshEndpoint: repository
+ runOptions: 'inline'
+ inline: |
+ sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
+ rm $0
+ exit
+
+- job: PublishNuget
+ displayName: 'Publish NuGet packages'
+ dependsOn:
+ - BuildPackage
+ condition: and(succeeded('BuildPackage'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - task: NuGetCommand@2
+ inputs:
+ command: 'pack'
+ packagesToPack: Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj
+ packDestination: '$(Build.ArtifactStagingDirectory)'
+
+ - task: NuGetCommand@2
+ inputs:
+ command: 'push'
+ packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
+ includeNugetOrg: 'true'
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml
index 4455632e15..a3c7f8526c 100644
--- a/.ci/azure-pipelines-test.yml
+++ b/.ci/azure-pipelines-test.yml
@@ -1,26 +1,25 @@
parameters:
- - name: ImageNames
- type: object
- default:
- Linux: "ubuntu-latest"
- Windows: "windows-latest"
- macOS: "macos-latest"
- - name: TestProjects
- type: string
- default: "tests/**/*Tests.csproj"
- - name: DotNetSdkVersion
- type: string
- default: 3.1.100
+- name: ImageNames
+ type: object
+ default:
+ Linux: "ubuntu-latest"
+ Windows: "windows-latest"
+ macOS: "macos-latest"
+- name: TestProjects
+ type: string
+ default: "tests/**/*Tests.csproj"
+- name: DotNetSdkVersion
+ type: string
+ default: 3.1.100
jobs:
- - job: MainTest
- displayName: Main Test
+ - job: Test
+ displayName: Test
strategy:
matrix:
${{ each imageName in parameters.ImageNames }}:
${{ imageName.key }}:
ImageName: ${{ imageName.value }}
- maxParallel: 3
pool:
vmImage: "$(ImageName)"
steps:
@@ -29,14 +28,31 @@ jobs:
submodules: true
persistCredentials: false
+ # This is required for the SonarCloud analyzer
+ - task: UseDotNet@2
+ displayName: "Install .NET Core SDK 2.1"
+ condition: eq(variables['ImageName'], 'ubuntu-latest')
+ inputs:
+ packageType: sdk
+ version: '2.1.805'
+
- task: UseDotNet@2
displayName: "Update DotNet"
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
+ - task: SonarCloudPrepare@1
+ displayName: 'Prepare analysis on SonarCloud'
+ condition: eq(variables['ImageName'], 'ubuntu-latest')
+ enabled: false
+ inputs:
+ SonarCloud: 'Sonarcloud for Jellyfin'
+ organization: 'jellyfin'
+ projectKey: 'jellyfin_jellyfin'
+
- task: DotNetCoreCLI@2
- displayName: Run .NET Core CLI tests
+ displayName: 'Run CLI Tests'
inputs:
command: "test"
projects: ${{ parameters.TestProjects }}
@@ -45,9 +61,20 @@ jobs:
testRunTitle: $(Agent.JobName)
workingDirectory: "$(Build.SourcesDirectory)"
+ - task: SonarCloudAnalyze@1
+ displayName: 'Run Code Analysis'
+ condition: eq(variables['ImageName'], 'ubuntu-latest')
+ enabled: false
+
+ - task: SonarCloudPublish@1
+ displayName: 'Publish Quality Gate Result'
+ condition: eq(variables['ImageName'], 'ubuntu-latest')
+ enabled: false
+
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
- displayName: ReportGenerator (merge)
+ displayName: 'Run ReportGenerator'
+ enabled: false
inputs:
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
targetdir: "$(Agent.TempDirectory)/merged/"
@@ -56,7 +83,8 @@ jobs:
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
- task: PublishCodeCoverageResults@1
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
- displayName: Publish Code Coverage
+ displayName: 'Publish Code Coverage'
+ enabled: false
inputs:
codeCoverageTool: "cobertura"
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
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..0c86c0171c 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -1,12 +1,12 @@
name: $(Date:yyyyMMdd)$(Rev:.r)
variables:
- - name: TestProjects
- value: "tests/**/*Tests.csproj"
- - name: RestoreBuildProjects
- value: "Jellyfin.Server/Jellyfin.Server.csproj"
- - name: DotNetSdkVersion
- value: 3.1.100
+- name: TestProjects
+ value: 'tests/**/*Tests.csproj'
+- name: RestoreBuildProjects
+ value: 'Jellyfin.Server/Jellyfin.Server.csproj'
+- name: DotNetSdkVersion
+ value: 3.1.100
pr:
autoCancel: true
@@ -15,24 +15,22 @@ trigger:
batch: true
jobs:
+- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-main.yml
parameters:
- LinuxImage: "ubuntu-latest"
+ LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: $(RestoreBuildProjects)
+- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-test.yml
parameters:
ImageNames:
- Linux: "ubuntu-latest"
- Windows: "windows-latest"
- macOS: "macos-latest"
+ Linux: 'ubuntu-latest'
+ Windows: 'windows-latest'
+ macOS: 'macos-latest'
- - template: azure-pipelines-windows.yml
- parameters:
- WindowsImage: "windows-latest"
- TestProjects: $(TestProjects)
-
- - template: azure-pipelines-compat.yml
+- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
+ - template: azure-pipelines-abi.yml
parameters:
Packages:
Naming:
@@ -47,4 +45,7 @@ jobs:
Common:
NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll
- LinuxImage: "ubuntu-latest"
+ LinuxImage: 'ubuntu-latest'
+
+- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
+ - template: azure-pipelines-package.yml
diff --git a/.copr/Makefile b/.copr/Makefile
deleted file mode 100644
index ba330ada95..0000000000
--- a/.copr/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
- deployment/fedora-package-x64/pkg-src/jellyfin.spec)
-
-deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
- curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
- https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
- || curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
- https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
-
-srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
- cd deployment/fedora-package-x64; \
- SOURCE_DIR=../.. \
- WORKDIR="$${PWD}"; \
- package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
- pkg_src_dir="$${WORKDIR}/pkg-src"; \
- GNU_TAR=1; \
- tar \
- --transform "s,^\.,jellyfin-$(VERSION)," \
- --exclude='.git*' \
- --exclude='**/.git' \
- --exclude='**/.hg' \
- --exclude='**/.vs' \
- --exclude='**/.vscode' \
- --exclude='deployment' \
- --exclude='**/bin' \
- --exclude='**/obj' \
- --exclude='**/.nuget' \
- --exclude='*.deb' \
- --exclude='*.rpm' \
- -czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
- -C $${SOURCE_DIR} ./ || GNU_TAR=0; \
- if [ $${GNU_TAR} -eq 0 ]; then \
- package_temporary_dir="$$(mktemp -d)"; \
- mkdir -p "$${package_temporary_dir}/jellyfin"; \
- tar \
- --exclude='.git*' \
- --exclude='**/.git' \
- --exclude='**/.hg' \
- --exclude='**/.vs' \
- --exclude='**/.vscode' \
- --exclude='deployment' \
- --exclude='**/bin' \
- --exclude='**/obj' \
- --exclude='**/.nuget' \
- --exclude='*.deb' \
- --exclude='*.rpm' \
- -czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
- -C $${SOURCE_DIR} ./; \
- mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
- tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
- -C "$${package_temporary_dir}/jellyfin-$(VERSION); \
- rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
- tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
- -C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
- rm -rf $${package_temporary_dir}; \
- fi; \
- rpmbuild -bs pkg-src/jellyfin.spec \
- --define "_sourcedir $$PWD/pkg-src/" \
- --define "_srcrpmdir $(outdir)"
diff --git a/.copr/Makefile b/.copr/Makefile
new file mode 120000
index 0000000000..ec3c90dfd9
--- /dev/null
+++ b/.copr/Makefile
@@ -0,0 +1 @@
+../fedora/Makefile
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index dc9aaa3ed5..b84e563efa 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -13,7 +13,7 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
-max_line_length = null
+max_line_length = off
# YAML indentation
[*.{yml,yaml}]
@@ -22,6 +22,7 @@ indent_size = 2
# XML indentation
[*.{csproj,xml}]
indent_size = 2
+
###############################
# .NET Coding Conventions #
###############################
@@ -51,11 +52,12 @@ dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
-dotnet_prefer_inferred_tuple_names = true:suggestion
-dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
+
###############################
# Naming Conventions #
###############################
@@ -67,7 +69,7 @@ dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
-dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
+dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
@@ -159,6 +161,7 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
+
###############################
# C# Formatting Rules #
###############################
@@ -189,9 +192,3 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
-###############################
-# VB Coding Conventions #
-###############################
-[*.vb]
-# Modifier preferences
-visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000..e902dc7124
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,3 @@
+# Joshua must review all changes to deployment and build.sh
+deployment/* @joshuaboniface
+build.sh @joshuaboniface
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..0874cae2e3
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,9 @@
+version: 2
+updates:
+- package-ecosystem: nuget
+ directory: "/"
+ schedule:
+ interval: weekly
+ time: '12:00'
+ open-pull-requests-limit: 10
+
diff --git a/.gitignore b/.gitignore
index 42243f01a8..0df7606ce9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -244,14 +244,14 @@ pip-log.txt
#########################
# Artifacts for debian-x64
-deployment/debian-package-x64/pkg-src/.debhelper/
-deployment/debian-package-x64/pkg-src/*.debhelper
-deployment/debian-package-x64/pkg-src/debhelper-build-stamp
-deployment/debian-package-x64/pkg-src/files
-deployment/debian-package-x64/pkg-src/jellyfin.substvars
-deployment/debian-package-x64/pkg-src/jellyfin/
+debian/.debhelper/
+debian/*.debhelper
+debian/debhelper-build-stamp
+debian/files
+debian/jellyfin.substvars
+debian/jellyfin/
# Don't ignore the debian/bin folder
-!deployment/debian-package-x64/pkg-src/bin/
+!debian/bin/
deployment/**/dist/
deployment/**/pkg-dist/
@@ -271,3 +271,8 @@ dist
# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts
+
+# Ignore web artifacts from native builds
+web/
+web-src.*
+MediaBrowser.WebDashboard/jellyfin-web
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000000..59d9452fed
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,14 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+
+ // List of extensions which should be recommended for users of this workspace.
+ "recommendations": [
+ "ms-dotnettools.csharp",
+ "editorconfig.editorconfig"
+ ],
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+ "unwantedRecommendations": [
+
+ ]
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 73f347c9fe..0f698bfa4b 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,9 +1,6 @@
{
- // Use IntelliSense to find out which attributes exist for C# debugging
- // Use hover for the description of the existing attributes
- // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
- "version": "0.2.0",
- "configurations": [
+ "version": "0.2.0",
+ "configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
@@ -24,5 +21,8 @@
"request": "attach",
"processId": "${command:pickProcess}"
}
- ,]
+ ],
+ "env": {
+ "DOTNET_CLI_TELEMETRY_OPTOUT": "1"
+ }
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index ac517e10c6..7ddc49d5ce 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -10,6 +10,21 @@
"${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj"
],
"problemMatcher": "$msCompile"
+ },
+ {
+ "label": "api tests",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "test",
+ "${workspaceFolder}/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ],
+ "options": {
+ "env": {
+ "DOTNET_CLI_TELEMETRY_OPTOUT": "1"
}
- ]
-}
\ No newline at end of file
+ }
+}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index f195c125f1..c5f35c0885 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -7,6 +7,7 @@
- [anthonylavado](https://github.com/anthonylavado)
- [Artiume](https://github.com/Artiume)
- [AThomsen](https://github.com/AThomsen)
+ - [barronpm](https://github.com/barronpm)
- [bilde2910](https://github.com/bilde2910)
- [bfayers](https://github.com/bfayers)
- [BnMcG](https://github.com/BnMcG)
@@ -22,6 +23,7 @@
- [cvium](https://github.com/cvium)
- [dannymichel](https://github.com/dannymichel)
- [DaveChild](https://github.com/DaveChild)
+ - [Delgan](https://github.com/Delgan)
- [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung)
- [dinki](https://github.com/dinki)
@@ -128,6 +130,8 @@
- [xosdy](https://github.com/xosdy)
- [XVicarious](https://github.com/XVicarious)
- [YouKnowBlom](https://github.com/YouKnowBlom)
+ - [KristupasSavickas](https://github.com/KristupasSavickas)
+ - [Pusta](https://github.com/pusta)
# Emby Contributors
diff --git a/Dockerfile b/Dockerfile
index caac7500a5..d3fb138a81 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,9 +1,8 @@
ARG DOTNET_VERSION=3.1
-ARG FFMPEG_VERSION=latest
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
-RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm \
+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 \
@@ -17,7 +16,6 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
-FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
FROM debian:buster-slim
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
@@ -27,32 +25,26 @@ 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
# Install dependencies:
-# libfontconfig1: needed for Skia
-# libgomp1: needed for ffmpeg
-# libva-drm2: needed for ffmpeg
-# mesa-va-drivers: needed for VAAPI
+# mesa-va-drivers: needed for AMD VAAPI
RUN apt-get update \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
+ && wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
+ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
+ && apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
- libfontconfig1 \
- libgomp1 \
- libva-drm2 \
mesa-va-drivers \
+ jellyfin-ffmpeg \
openssl \
- ca-certificates \
- vainfo \
- i965-va-driver \
locales \
- && apt-get clean autoclean -y\
- && apt-get autoremove -y\
+ && apt-get remove gnupg wget apt-transport-https -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 \
- && ln -s /opt/ffmpeg/bin/ffmpeg /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
@@ -65,4 +57,4 @@ VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
- "--ffmpeg", "/usr/local/bin/ffmpeg"]
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
diff --git a/Dockerfile.arm b/Dockerfile.arm
index c5189d6aa3..59b8a8c982 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -38,7 +38,7 @@ COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
RUN apt-get update \
&& 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 - && \
+ curl -ks 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 && \
@@ -74,4 +74,4 @@ VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
- "--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
diff --git a/DvdLib/BigEndianBinaryReader.cs b/DvdLib/BigEndianBinaryReader.cs
index b3b2eabd5c..b3aad85cec 100644
--- a/DvdLib/BigEndianBinaryReader.cs
+++ b/DvdLib/BigEndianBinaryReader.cs
@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CS1591
+
+using System.Buffers.Binary;
using System.IO;
namespace DvdLib
@@ -12,19 +14,12 @@ namespace DvdLib
public override ushort ReadUInt16()
{
- return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
+ return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
}
public override uint ReadUInt32()
{
- return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
- }
-
- private byte[] ReadAndReverseBytes(int count)
- {
- byte[] val = base.ReadBytes(count);
- Array.Reverse(val, 0, count);
- return val;
+ return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
}
}
}
diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj
index f4df6a9f52..64d041cb05 100644
--- a/DvdLib/DvdLib.csproj
+++ b/DvdLib/DvdLib.csproj
@@ -1,17 +1,19 @@
-
-
-
+
+
+ {713F42B5-878E-499D-A878-E4C652B1D5E8}
+
-
+
netstandard2.1
false
true
+ true
diff --git a/DvdLib/Ifo/Cell.cs b/DvdLib/Ifo/Cell.cs
index 268ab897ee..ea0b50e430 100644
--- a/DvdLib/Ifo/Cell.cs
+++ b/DvdLib/Ifo/Cell.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
namespace DvdLib.Ifo
@@ -5,6 +7,7 @@ namespace DvdLib.Ifo
public class Cell
{
public CellPlaybackInfo PlaybackInfo { get; private set; }
+
public CellPositionInfo PositionInfo { get; private set; }
internal void ParsePlayback(BinaryReader br)
diff --git a/DvdLib/Ifo/CellPlaybackInfo.cs b/DvdLib/Ifo/CellPlaybackInfo.cs
index e588e51ac0..6e33a0ec5a 100644
--- a/DvdLib/Ifo/CellPlaybackInfo.cs
+++ b/DvdLib/Ifo/CellPlaybackInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
namespace DvdLib.Ifo
diff --git a/DvdLib/Ifo/CellPositionInfo.cs b/DvdLib/Ifo/CellPositionInfo.cs
index 2b973e0830..216aa0f77a 100644
--- a/DvdLib/Ifo/CellPositionInfo.cs
+++ b/DvdLib/Ifo/CellPositionInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
namespace DvdLib.Ifo
diff --git a/DvdLib/Ifo/Chapter.cs b/DvdLib/Ifo/Chapter.cs
index bd3bd97040..e786cb5536 100644
--- a/DvdLib/Ifo/Chapter.cs
+++ b/DvdLib/Ifo/Chapter.cs
@@ -1,9 +1,13 @@
+#pragma warning disable CS1591
+
namespace DvdLib.Ifo
{
public class Chapter
{
public ushort ProgramChainNumber { get; private set; }
+
public ushort ProgramNumber { get; private set; }
+
public uint ChapterNumber { get; private set; }
public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)
diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs
index c0f9cf4107..361319625c 100644
--- a/DvdLib/Ifo/Dvd.cs
+++ b/DvdLib/Ifo/Dvd.cs
@@ -1,8 +1,9 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using MediaBrowser.Model.IO;
namespace DvdLib.Ifo
{
@@ -13,13 +14,10 @@ namespace DvdLib.Ifo
private ushort _titleCount;
public readonly Dictionary VTSPaths = new Dictionary();
- private readonly IFileSystem _fileSystem;
-
- public Dvd(string path, IFileSystem fileSystem)
+ public Dvd(string path)
{
- _fileSystem = fileSystem;
Titles = new List();
- var allFiles = _fileSystem.GetFiles(path, true).ToList();
+ var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
@@ -76,7 +74,7 @@ namespace DvdLib.Ifo
}
}
- private void ReadVTS(ushort vtsNum, IEnumerable allFiles)
+ private void ReadVTS(ushort vtsNum, IReadOnlyList allFiles)
{
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
@@ -119,12 +117,19 @@ namespace DvdLib.Ifo
uint chapNum = 1;
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
- if (t == null) continue;
+ if (t == null)
+ {
+ continue;
+ }
do
{
t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
- if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) break;
+ if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1]))
+ {
+ break;
+ }
+
chapNum++;
}
while (vtsFs.Position < (baseAddr + endaddr));
@@ -149,7 +154,10 @@ namespace DvdLib.Ifo
uint vtsPgcOffset = vtsRead.ReadUInt32();
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
- if (t != null) t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
+ if (t != null)
+ {
+ t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
+ }
}
}
}
diff --git a/DvdLib/Ifo/DvdTime.cs b/DvdLib/Ifo/DvdTime.cs
index 3688089ec7..d231406106 100644
--- a/DvdLib/Ifo/DvdTime.cs
+++ b/DvdLib/Ifo/DvdTime.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace DvdLib.Ifo
@@ -13,8 +15,14 @@ namespace DvdLib.Ifo
Second = GetBCDValue(data[2]);
Frames = GetBCDValue((byte)(data[3] & 0x3F));
- if ((data[3] & 0x80) != 0) FrameRate = 30;
- else if ((data[3] & 0x40) != 0) FrameRate = 25;
+ if ((data[3] & 0x80) != 0)
+ {
+ FrameRate = 30;
+ }
+ else if ((data[3] & 0x40) != 0)
+ {
+ FrameRate = 25;
+ }
}
private static byte GetBCDValue(byte data)
diff --git a/DvdLib/Ifo/Program.cs b/DvdLib/Ifo/Program.cs
index af08afa356..3d94fa7dc1 100644
--- a/DvdLib/Ifo/Program.cs
+++ b/DvdLib/Ifo/Program.cs
@@ -1,10 +1,12 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace DvdLib.Ifo
{
public class Program
{
- public readonly List Cells;
+ public IReadOnlyList Cells { get; }
public Program(List cells)
{
diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs
index 7b003005b9..83c0051b90 100644
--- a/DvdLib/Ifo/ProgramChain.cs
+++ b/DvdLib/Ifo/ProgramChain.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -20,7 +22,9 @@ namespace DvdLib.Ifo
public readonly List Cells;
public DvdTime PlaybackTime { get; private set; }
+
public UserOperation ProhibitedUserOperations { get; private set; }
+
public byte[] AudioStreamControl { get; private set; } // 8*2 entries
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
@@ -31,9 +35,11 @@ namespace DvdLib.Ifo
private ushort _goupProgramNumber;
public ProgramPlaybackMode PlaybackMode { get; private set; }
+
public uint ProgramCount { get; private set; }
public byte StillTime { get; private set; }
+
public byte[] Palette { get; private set; } // 16*4 entries
private ushort _commandTableOffset;
@@ -69,8 +75,15 @@ namespace DvdLib.Ifo
StillTime = br.ReadByte();
byte pbMode = br.ReadByte();
- if (pbMode == 0) PlaybackMode = ProgramPlaybackMode.Sequential;
- else PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
+ if (pbMode == 0)
+ {
+ PlaybackMode = ProgramPlaybackMode.Sequential;
+ }
+ else
+ {
+ PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
+ }
+
ProgramCount = (uint)(pbMode & 0x7F);
Palette = br.ReadBytes(64);
diff --git a/DvdLib/Ifo/Title.cs b/DvdLib/Ifo/Title.cs
index 335e929928..29a0b95c72 100644
--- a/DvdLib/Ifo/Title.cs
+++ b/DvdLib/Ifo/Title.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.IO;
@@ -6,8 +8,11 @@ namespace DvdLib.Ifo
public class Title
{
public uint TitleNumber { get; private set; }
+
public uint AngleCount { get; private set; }
+
public ushort ChapterCount { get; private set; }
+
public byte VideoTitleSetNumber { get; private set; }
private ushort _parentalManagementMask;
@@ -15,6 +20,7 @@ namespace DvdLib.Ifo
private uint _vtsStartSector; // relative to start of entire disk
public ProgramChain EntryProgramChain { get; private set; }
+
public readonly List ProgramChains;
public readonly List Chapters;
@@ -53,7 +59,10 @@ namespace DvdLib.Ifo
var pgc = new ProgramChain(pgcNum);
pgc.ParseHeader(br);
ProgramChains.Add(pgc);
- if (entryPgc) EntryProgramChain = pgc;
+ if (entryPgc)
+ {
+ EntryProgramChain = pgc;
+ }
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
}
diff --git a/DvdLib/Ifo/UserOperation.cs b/DvdLib/Ifo/UserOperation.cs
index 757a5a05db..5d111ebc06 100644
--- a/DvdLib/Ifo/UserOperation.cs
+++ b/DvdLib/Ifo/UserOperation.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace DvdLib.Ifo
diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs
index 7fba2184a7..d9c1669b0f 100644
--- a/Emby.Dlna/Api/DlnaServerService.cs
+++ b/Emby.Dlna/Api/DlnaServerService.cs
@@ -11,6 +11,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
namespace Emby.Dlna.Api
{
@@ -108,7 +109,7 @@ namespace Emby.Dlna.Api
public string Filename { get; set; }
}
- public class DlnaServerService : IService, IRequiresRequest
+ public class DlnaServerService : IService
{
private const string XMLContentType = "text/xml; charset=UTF-8";
@@ -127,11 +128,13 @@ namespace Emby.Dlna.Api
public DlnaServerService(
IDlnaManager dlnaManager,
IHttpResultFactory httpResultFactory,
- IServerConfigurationManager configurationManager)
+ IServerConfigurationManager configurationManager,
+ IHttpContextAccessor httpContextAccessor)
{
_dlnaManager = dlnaManager;
_resultFactory = httpResultFactory;
_configurationManager = configurationManager;
+ Request = httpContextAccessor?.HttpContext.GetServiceStackRequest() ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
private string GetHeader(string name)
diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs
index e224d10bd3..dba9019677 100644
--- a/Emby.Dlna/ConfigurationExtension.cs
+++ b/Emby.Dlna/ConfigurationExtension.cs
@@ -1,3 +1,4 @@
+#nullable enable
#pragma warning disable CS1591
using System.Collections.Generic;
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs
index 64cd308a2a..b1ce7e8ecb 100644
--- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs
+++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs
@@ -1,13 +1,15 @@
#pragma warning disable CS1591
using System;
+using System.Linq;
using System.Threading.Tasks;
using Emby.Dlna.Service;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.TV;
@@ -31,7 +33,8 @@ namespace Emby.Dlna.ContentDirectory
private readonly IMediaEncoder _mediaEncoder;
private readonly ITVSeriesManager _tvSeriesManager;
- public ContentDirectory(IDlnaManager dlna,
+ public ContentDirectory(
+ IDlnaManager dlna,
IUserDataManager userDataManager,
IImageProcessor imageProcessor,
ILibraryManager libraryManager,
@@ -130,18 +133,13 @@ namespace Emby.Dlna.ContentDirectory
foreach (var user in _userManager.Users)
{
- if (user.Policy.IsAdministrator)
+ if (user.HasPermission(PermissionKind.IsAdministrator))
{
return user;
}
}
- foreach (var user in _userManager.Users)
- {
- return user;
- }
-
- return null;
+ return _userManager.Users.FirstOrDefault();
}
}
}
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 28888f031a..291de5245b 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -10,6 +10,7 @@ using System.Threading;
using System.Xml;
using Emby.Dlna.Didl;
using Emby.Dlna.Service;
+using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
@@ -17,7 +18,6 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
@@ -28,6 +28,12 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
+using Book = MediaBrowser.Controller.Entities.Book;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Dlna.ContentDirectory
{
@@ -460,12 +466,12 @@ namespace Emby.Dlna.ContentDirectory
}
else if (search.SearchType == SearchType.Playlist)
{
- //items = items.OfType();
+ // items = items.OfType();
isFolder = true;
}
else if (search.SearchType == SearchType.MusicAlbum)
{
- //items = items.OfType();
+ // items = items.OfType();
isFolder = true;
}
@@ -731,7 +737,7 @@ namespace Emby.Dlna.ContentDirectory
return GetGenres(item, user, query);
}
- var array = new ServerItem[]
+ var array = new[]
{
new ServerItem(item)
{
@@ -920,7 +926,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult GetMovieCollections(User user, InternalItemsQuery query)
{
query.Recursive = true;
- //query.Parent = parent;
+ // query.Parent = parent;
query.SetUser(user);
query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
@@ -1115,7 +1121,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult GetMusicPlaylists(User user, InternalItemsQuery query)
{
query.Parent = null;
- query.IncludeItemTypes = new[] { typeof(Playlist).Name };
+ query.IncludeItemTypes = new[] { nameof(Playlist) };
query.SetUser(user);
query.Recursive = true;
@@ -1132,10 +1138,9 @@ namespace Emby.Dlna.ContentDirectory
{
UserId = user.Id,
Limit = 50,
- IncludeItemTypes = new[] { typeof(Audio).Name },
- ParentId = parent == null ? Guid.Empty : parent.Id,
+ IncludeItemTypes = new[] { nameof(Audio) },
+ ParentId = parent?.Id ?? Guid.Empty,
GroupItems = true
-
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items);
@@ -1150,7 +1155,6 @@ namespace Emby.Dlna.ContentDirectory
Limit = query.Limit,
StartIndex = query.StartIndex,
UserId = query.User.Id
-
}, new[] { parent }, query.DtoOptions);
return ToResult(result);
@@ -1167,7 +1171,6 @@ namespace Emby.Dlna.ContentDirectory
IncludeItemTypes = new[] { typeof(Episode).Name },
ParentId = parent == null ? Guid.Empty : parent.Id,
GroupItems = false
-
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items);
@@ -1177,14 +1180,14 @@ namespace Emby.Dlna.ContentDirectory
{
query.OrderBy = Array.Empty<(string, SortOrder)>();
- var items = _userViewManager.GetLatestItems(new LatestItemsQuery
+ var items = _userViewManager.GetLatestItems(
+ new LatestItemsQuery
{
UserId = user.Id,
Limit = 50,
- IncludeItemTypes = new[] { typeof(Movie).Name },
- ParentId = parent == null ? Guid.Empty : parent.Id,
+ IncludeItemTypes = new[] { nameof(Movie) },
+ ParentId = parent?.Id ?? Guid.Empty,
GroupItems = true
-
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items);
@@ -1217,7 +1220,11 @@ namespace Emby.Dlna.ContentDirectory
Recursive = true,
ParentId = parentId,
GenreIds = new[] { item.Id },
- IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name },
+ IncludeItemTypes = new[]
+ {
+ nameof(Movie),
+ nameof(Series)
+ },
Limit = limit,
StartIndex = startIndex,
DtoOptions = GetDtoOptions()
@@ -1350,6 +1357,7 @@ namespace Emby.Dlna.ContentDirectory
internal class ServerItem
{
public BaseItem Item { get; set; }
+
public StubType? StubType { get; set; }
public ServerItem(BaseItem item)
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 88cc00e30a..66baa9512c 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -6,14 +6,13 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
-using Emby.Dlna.Configuration;
using Emby.Dlna.ContentDirectory;
+using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Playlists;
@@ -23,6 +22,13 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
+using Season = MediaBrowser.Controller.Entities.TV.Season;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
+using XmlAttribute = MediaBrowser.Model.Dlna.XmlAttribute;
namespace Emby.Dlna.Didl
{
@@ -92,21 +98,21 @@ namespace Emby.Dlna.Didl
{
using (var writer = XmlWriter.Create(builder, settings))
{
- //writer.WriteStartDocument();
+ // writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
- //didl.SetAttribute("xmlns:sec", NS_SEC);
+ // didl.SetAttribute("xmlns:sec", NS_SEC);
WriteXmlRootAttributes(_profile, writer);
WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
writer.WriteFullEndElement();
- //writer.WriteEndDocument();
+ // writer.WriteEndDocument();
}
return builder.ToString();
@@ -421,61 +427,102 @@ namespace Emby.Dlna.Didl
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
case StubType.Series: return _localization.GetLocalizedString("Shows");
- default: break;
}
}
- if (item is Episode episode && context is Season season)
+ return item is Episode episode
+ ? GetEpisodeDisplayName(episode, context)
+ : item.Name;
+ }
+
+ ///
+ /// Gets episode display name appropriate for the given context.
+ ///
+ ///
+ /// If context is a season, this will return a string containing just episode number and name.
+ /// Otherwise the result will include series nams and season number.
+ ///
+ /// The episode.
+ /// Current context.
+ /// Formatted name of the episode.
+ private string GetEpisodeDisplayName(Episode episode, BaseItem context)
+ {
+ string[] components;
+
+ if (context is Season season)
{
// This is a special embedded within a season
- if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
+ if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{
return string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ValueSpecialEpisodeName"),
- item.Name);
+ episode.Name);
}
- if (item.IndexNumber.HasValue)
- {
- var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+ // inside a season use simple format (ex. '12 - Episode Name')
+ var epNumberName = GetEpisodeIndexFullName(episode);
+ components = new[] { epNumberName, episode.Name };
+ }
+ else
+ {
+ // outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
+ var epNumberName = GetEpisodeNumberDisplayName(episode);
+ components = new[] { episode.SeriesName, epNumberName, episode.Name };
+ }
- if (episode.IndexNumberEnd.HasValue)
- {
- number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
- }
+ return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
+ }
- return number + " - " + item.Name;
- }
- }
- else if (item is Episode ep)
+ ///
+ /// Gets complete episode number.
+ ///
+ /// The episode.
+ /// For single episodes returns just the number. For double episodes - current and ending numbers.
+ private string GetEpisodeIndexFullName(Episode episode)
+ {
+ var name = string.Empty;
+ if (episode.IndexNumber.HasValue)
{
- var parent = ep.GetParent();
- var name = parent.Name + " - ";
+ name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
- if (ep.ParentIndexNumber.HasValue)
- {
- name += "S" + ep.ParentIndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
- }
- else if (!item.IndexNumber.HasValue)
+ if (episode.IndexNumberEnd.HasValue)
{
- return name + " - " + item.Name;
+ name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
}
+ }
- name += "E" + ep.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
- if (ep.IndexNumberEnd.HasValue)
- {
- name += "-" + ep.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
- }
+ return name;
+ }
+
+ ///
+ /// Gets episode number formatted as 'S##E##'.
+ ///
+ /// The episode.
+ /// Formatted episode number.
+ private string GetEpisodeNumberDisplayName(Episode episode)
+ {
+ var name = string.Empty;
+ var seasonNumber = episode.Season?.IndexNumber;
- name += " - " + item.Name;
- return name;
+ if (seasonNumber.HasValue)
+ {
+ name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
}
- return item.Name;
+ var indexName = GetEpisodeIndexFullName(episode);
+
+ if (!string.IsNullOrWhiteSpace(indexName))
+ {
+ name += "E" + indexName;
+ }
+
+ return name;
}
+ private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
+
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
@@ -628,7 +675,7 @@ namespace Emby.Dlna.Didl
return;
}
- MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null;
+ XmlAttribute secAttribute = null;
foreach (var attribute in _profile.XmlRootAttributes)
{
if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
@@ -658,13 +705,13 @@ namespace Emby.Dlna.Didl
}
///
- /// Adds fields used by both items and folders
+ /// Adds fields used by both items and folders.
///
private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
{
// Don't filter on dc:title because not all devices will include it in the filter
// MediaMonkey for example won't display content without a title
- //if (filter.Contains("dc:title"))
+ // if (filter.Contains("dc:title"))
{
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
}
@@ -703,7 +750,7 @@ namespace Emby.Dlna.Didl
AddValue(writer, "dc", "description", desc, NS_DC);
}
}
- //if (filter.Contains("upnp:longDescription"))
+ // if (filter.Contains("upnp:longDescription"))
//{
// if (!string.IsNullOrWhiteSpace(item.Overview))
// {
@@ -718,6 +765,7 @@ namespace Emby.Dlna.Didl
{
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
}
+
if (filter.Contains("upnp:rating"))
{
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
@@ -953,7 +1001,6 @@ namespace Emby.Dlna.Didl
}
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
-
}
private void AddImageResElement(
@@ -1006,10 +1053,12 @@ namespace Emby.Dlna.Didl
{
return GetImageInfo(item, ImageType.Primary);
}
+
if (item.HasImage(ImageType.Thumb))
{
return GetImageInfo(item, ImageType.Thumb);
}
+
if (item.HasImage(ImageType.Backdrop))
{
if (item is Channel)
@@ -1018,19 +1067,58 @@ namespace Emby.Dlna.Didl
}
}
- item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
+ // For audio tracks without art use album art if available.
+ if (item is Audio audioItem)
+ {
+ var album = audioItem.AlbumEntity;
+ return album != null && album.HasImage(ImageType.Primary)
+ ? GetImageInfo(album, ImageType.Primary)
+ : null;
+ }
+
+ // Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
+ if (item is MusicAlbum || item is Playlist)
+ {
+ return null;
+ }
- if (item != null)
+ // For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item.
+ var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
+ if (parentWithImage != null)
{
- if (item.HasImage(ImageType.Primary))
- {
- return GetImageInfo(item, ImageType.Primary);
- }
+ return GetImageInfo(parentWithImage, ImageType.Primary);
}
return null;
}
+ private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
+ {
+ if (item == null)
+ {
+ return null;
+ }
+
+ if (item.HasImage(ImageType.Primary))
+ {
+ return item;
+ }
+
+ var parent = item.GetParent();
+ if (parent is UserRootFolder)
+ {
+ return null;
+ }
+
+ // terminate in case we went past user root folder (unlikely?)
+ if (parent is Folder folder && folder.IsRoot)
+ {
+ return null;
+ }
+
+ return GetFirstParentWithImageBelowUserRoot(parent);
+ }
+
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{
var imageInfo = item.GetImageInfo(type, 0);
@@ -1050,25 +1138,24 @@ namespace Emby.Dlna.Didl
if (width == 0 || height == 0)
{
- //_imageProcessor.GetImageSize(item, imageInfo);
+ // _imageProcessor.GetImageSize(item, imageInfo);
width = null;
height = null;
}
-
else if (width == -1 || height == -1)
{
width = null;
height = null;
}
- //try
+ // try
//{
// var size = _imageProcessor.GetImageSize(imageInfo);
// width = size.Width;
// height = size.Height;
//}
- //catch
+ // catch
//{
//}
diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs
index 412259e904..b730d9db25 100644
--- a/Emby.Dlna/Didl/Filter.cs
+++ b/Emby.Dlna/Didl/Filter.cs
@@ -12,7 +12,6 @@ namespace Emby.Dlna.Didl
public Filter()
: this("*")
{
-
}
public Filter(string filter)
@@ -26,7 +25,7 @@ namespace Emby.Dlna.Didl
{
// Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back.
return true;
- //return _all || ListHelper.ContainsIgnoreCase(_fields, field);
+ // return _all || ListHelper.ContainsIgnoreCase(_fields, field);
}
}
}
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index 10f881fe76..5911a73ef1 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -31,7 +31,7 @@ namespace Emby.Dlna
private readonly IApplicationPaths _appPaths;
private readonly IXmlSerializer _xmlSerializer;
private readonly IFileSystem _fileSystem;
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
@@ -49,7 +49,7 @@ namespace Emby.Dlna
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
_appPaths = appPaths;
- _logger = loggerFactory.CreateLogger("Dlna");
+ _logger = loggerFactory.CreateLogger();
_jsonSerializer = jsonSerializer;
_appHost = appHost;
}
@@ -88,7 +88,6 @@ namespace Emby.Dlna
.Select(i => i.Item2)
.ToList();
}
-
}
public DeviceProfile GetDefaultProfile()
@@ -141,55 +140,73 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
{
if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
{
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ModelName))
{
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
{
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
+ {
return false;
+ }
}
return true;
@@ -251,7 +268,7 @@ namespace Emby.Dlna
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring:
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
- //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
+ // _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
return isMatch;
case HeaderMatchType.Regex:
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
@@ -439,6 +456,7 @@ namespace Emby.Dlna
{
throw new ArgumentException("Profile is missing Id");
}
+
if (string.IsNullOrEmpty(profile.Name))
{
throw new ArgumentException("Profile is missing Name");
@@ -464,6 +482,7 @@ namespace Emby.Dlna
{
_profiles[path] = new Tuple(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
}
+
SerializeToXml(profile, path);
}
@@ -474,7 +493,7 @@ namespace Emby.Dlna
///
/// Recreates the object using serialization, to ensure it's not a subclass.
- /// If it's a subclass it may not serlialize properly to xml (different root element tag name)
+ /// If it's a subclass it may not serlialize properly to xml (different root element tag name).
///
///
///
@@ -493,6 +512,7 @@ namespace Emby.Dlna
class InternalProfileInfo
{
internal DeviceProfileInfo Info { get; set; }
+
internal string Path { get; set; }
}
@@ -566,9 +586,9 @@ namespace Emby.Dlna
new Foobar2000Profile(),
new SharpSmartTvProfile(),
new MediaMonkeyProfile(),
- //new Windows81Profile(),
- //new WindowsMediaCenterProfile(),
- //new WindowsPhoneProfile(),
+ // new Windows81Profile(),
+ // new WindowsMediaCenterProfile(),
+ // new WindowsPhoneProfile(),
new DirectTvProfile(),
new DishHopperJoeyProfile(),
new DefaultProfile(),
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index 0cabe43d51..42a5f95c14 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -1,5 +1,10 @@
+
+
+ {805844AB-E92F-45E6-9D99-4F6D48D129A5}
+
+
diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs
index efbb53b644..7d02f5e960 100644
--- a/Emby.Dlna/Eventing/EventManager.cs
+++ b/Emby.Dlna/Eventing/EventManager.cs
@@ -31,18 +31,26 @@ namespace Emby.Dlna.Eventing
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
{
var subscription = GetSubscription(subscriptionId, false);
+ if (subscription != null)
+ {
+ subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
+ int timeoutSeconds = subscription.TimeoutSeconds;
+ subscription.SubscriptionTime = DateTime.UtcNow;
- subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
- int timeoutSeconds = subscription.TimeoutSeconds;
- subscription.SubscriptionTime = DateTime.UtcNow;
+ _logger.LogDebug(
+ "Renewing event subscription for {0} with timeout of {1} to {2}",
+ subscription.NotificationType,
+ timeoutSeconds,
+ subscription.CallbackUrl);
- _logger.LogDebug(
- "Renewing event subscription for {0} with timeout of {1} to {2}",
- subscription.NotificationType,
- timeoutSeconds,
- subscription.CallbackUrl);
+ return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
+ }
- return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
+ return new EventSubscriptionResponse
+ {
+ Content = string.Empty,
+ ContentType = "text/plain"
+ };
}
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
@@ -144,12 +152,17 @@ namespace Emby.Dlna.Eventing
builder.Append("");
foreach (var key in stateVariables.Keys)
{
- builder.Append("");
- builder.Append("<" + key + ">");
- builder.Append(stateVariables[key]);
- builder.Append("" + key + ">");
- builder.Append("");
+ builder.Append("")
+ .Append('<')
+ .Append(key)
+ .Append('>')
+ .Append(stateVariables[key])
+ .Append("")
+ .Append(key)
+ .Append('>')
+ .Append("");
}
+
builder.Append("");
var options = new HttpRequestOptions
@@ -169,7 +182,6 @@ namespace Emby.Dlna.Eventing
{
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
{
-
}
}
catch (OperationCanceledException)
diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs
index 51eaee9d77..40d73ee0e5 100644
--- a/Emby.Dlna/Eventing/EventSubscription.cs
+++ b/Emby.Dlna/Eventing/EventSubscription.cs
@@ -7,10 +7,13 @@ namespace Emby.Dlna.Eventing
public class EventSubscription
{
public string Id { get; set; }
+
public string CallbackUrl { get; set; }
+
public string NotificationType { get; set; }
public DateTime SubscriptionTime { get; set; }
+
public int TimeoutSeconds { get; set; }
public long TriggerCount { get; set; }
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index c5d60b2a05..9b9b57e973 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -33,10 +33,8 @@ namespace Emby.Dlna.Main
public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
{
private readonly IServerConfigurationManager _config;
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
private readonly IServerApplicationHost _appHost;
-
- private PlayToManager _manager;
private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient;
private readonly ILibraryManager _libraryManager;
@@ -47,14 +45,13 @@ namespace Emby.Dlna.Main
private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
-
private readonly IDeviceDiscovery _deviceDiscovery;
-
- private SsdpDevicePublisher _Publisher;
-
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
+ private readonly object _syncLock = new object();
+ private PlayToManager _manager;
+ private SsdpDevicePublisher _publisher;
private ISsdpCommunicationsServer _communicationsServer;
internal IContentDirectory ContentDirectory { get; private set; }
@@ -65,7 +62,8 @@ namespace Emby.Dlna.Main
public static DlnaEntryPoint Current;
- public DlnaEntryPoint(IServerConfigurationManager config,
+ public DlnaEntryPoint(
+ IServerConfigurationManager config,
ILoggerFactory loggerFactory,
IServerApplicationHost appHost,
ISessionManager sessionManager,
@@ -99,7 +97,7 @@ namespace Emby.Dlna.Main
_mediaEncoder = mediaEncoder;
_socketFactory = socketFactory;
_networkManager = networkManager;
- _logger = loggerFactory.CreateLogger("Dlna");
+ _logger = loggerFactory.CreateLogger();
ContentDirectory = new ContentDirectory.ContentDirectory(
dlnaManager,
@@ -133,20 +131,20 @@ namespace Emby.Dlna.Main
{
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
- ReloadComponents();
+ await ReloadComponents().ConfigureAwait(false);
- _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
+ _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
}
- void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
+ private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
{
- ReloadComponents();
+ await ReloadComponents().ConfigureAwait(false);
}
}
- private async void ReloadComponents()
+ private async Task ReloadComponents()
{
var options = _config.GetDlnaConfiguration();
@@ -180,7 +178,7 @@ namespace Emby.Dlna.Main
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
OperatingSystem.Id == OperatingSystemId.Linux;
- _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
+ _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
{
IsShared = true
};
@@ -231,20 +229,22 @@ namespace Emby.Dlna.Main
return;
}
- if (_Publisher != null)
+ if (_publisher != null)
{
return;
}
try
{
- _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
- _Publisher.LogFunction = LogMessage;
- _Publisher.SupportPnpRootDevice = false;
+ _publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost)
+ {
+ LogFunction = LogMessage,
+ SupportPnpRootDevice = false
+ };
await RegisterServerEndpoints().ConfigureAwait(false);
- _Publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
+ _publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
}
catch (Exception ex)
{
@@ -266,6 +266,12 @@ namespace Emby.Dlna.Main
continue;
}
+ // Limit to LAN addresses only
+ if (!_networkManager.IsAddressInSubnets(address, true, true))
+ {
+ continue;
+ }
+
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
@@ -275,7 +281,7 @@ namespace Emby.Dlna.Main
var device = new SsdpRootDevice
{
- CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
+ CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
Location = uri, // Must point to the URL that serves your devices UPnP description document.
Address = address,
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
@@ -287,13 +293,13 @@ namespace Emby.Dlna.Main
};
SetProperies(device, fullService);
- _Publisher.AddDevice(device);
+ _publisher.AddDevice(device);
var embeddedDevices = new[]
{
"urn:schemas-upnp-org:service:ContentDirectory:1",
"urn:schemas-upnp-org:service:ConnectionManager:1",
- //"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
+ // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
};
foreach (var subDevice in embeddedDevices)
@@ -319,12 +325,13 @@ namespace Emby.Dlna.Main
{
guid = text.GetMD5();
}
+
return guid.ToString("N", CultureInfo.InvariantCulture);
}
private void SetProperies(SsdpDevice device, string fullDeviceType)
{
- var service = fullDeviceType.Replace("urn:", string.Empty).Replace(":1", string.Empty);
+ var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase);
var serviceParts = service.Split(':');
@@ -335,7 +342,6 @@ namespace Emby.Dlna.Main
device.DeviceType = serviceParts[2];
}
- private readonly object _syncLock = new object();
private void StartPlayToManager()
{
lock (_syncLock)
@@ -347,7 +353,8 @@ namespace Emby.Dlna.Main
try
{
- _manager = new PlayToManager(_logger,
+ _manager = new PlayToManager(
+ _logger,
_sessionManager,
_libraryManager,
_userManager,
@@ -386,6 +393,7 @@ namespace Emby.Dlna.Main
{
_logger.LogError(ex, "Error disposing PlayTo manager");
}
+
_manager = null;
}
}
@@ -412,11 +420,11 @@ namespace Emby.Dlna.Main
public void DisposeDevicePublisher()
{
- if (_Publisher != null)
+ if (_publisher != null)
{
_logger.LogInformation("Disposing SsdpDevicePublisher");
- _Publisher.Dispose();
- _Publisher = null;
+ _publisher.Dispose();
+ _publisher = null;
}
}
}
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 6abc3a82c3..72834c69d1 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -4,12 +4,12 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Security;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using Emby.Dlna.Common;
-using Emby.Dlna.Server;
using Emby.Dlna.Ssdp;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -19,8 +19,6 @@ namespace Emby.Dlna.PlayTo
{
public class Device : IDisposable
{
- #region Fields & Properties
-
private Timer _timer;
public DeviceInfo Properties { get; set; }
@@ -34,9 +32,10 @@ namespace Emby.Dlna.PlayTo
{
get
{
- RefreshVolumeIfNeeded();
+ RefreshVolumeIfNeeded().GetAwaiter().GetResult();
return _volume;
}
+
set => _volume = value;
}
@@ -52,10 +51,10 @@ namespace Emby.Dlna.PlayTo
public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED;
- #endregion
-
private readonly IHttpClient _httpClient;
+
private readonly ILogger _logger;
+
private readonly IServerConfigurationManager _config;
public Action OnDeviceUnavailable { get; set; }
@@ -76,24 +75,24 @@ namespace Emby.Dlna.PlayTo
private DateTime _lastVolumeRefresh;
private bool _volumeRefreshActive;
- private void RefreshVolumeIfNeeded()
+ private Task RefreshVolumeIfNeeded()
{
- if (!_volumeRefreshActive)
- {
- return;
- }
-
- if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
+ if (_volumeRefreshActive
+ && DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
{
_lastVolumeRefresh = DateTime.UtcNow;
- RefreshVolume(CancellationToken.None);
+ return RefreshVolume();
}
+
+ return Task.CompletedTask;
}
- private async void RefreshVolume(CancellationToken cancellationToken)
+ private async Task RefreshVolume(CancellationToken cancellationToken = default)
{
if (_disposed)
+ {
return;
+ }
try
{
@@ -141,8 +140,6 @@ namespace Emby.Dlna.PlayTo
}
}
- #region Commanding
-
public Task VolumeDown(CancellationToken cancellationToken)
{
var sendVolume = Math.Max(Volume - 5, 0);
@@ -211,7 +208,9 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
if (command == null)
+ {
return false;
+ }
var service = GetServiceRenderingControl();
@@ -232,7 +231,7 @@ namespace Emby.Dlna.PlayTo
}
///
- /// Sets volume on a scale of 0-100
+ /// Sets volume on a scale of 0-100.
///
public async Task SetVolume(int value, CancellationToken cancellationToken)
{
@@ -240,7 +239,9 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
if (command == null)
+ {
return;
+ }
var service = GetServiceRenderingControl();
@@ -263,7 +264,9 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
if (command == null)
+ {
return;
+ }
var service = GetAvTransportService();
@@ -288,7 +291,9 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
if (command == null)
+ {
return;
+ }
var dictionary = new Dictionary
{
@@ -329,7 +334,7 @@ namespace Emby.Dlna.PlayTo
return string.Empty;
}
- return DescriptionXmlBuilder.Escape(value);
+ return SecurityElement.Escape(value);
}
private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken)
@@ -401,11 +406,8 @@ namespace Emby.Dlna.PlayTo
RestartTimer(true);
}
- #endregion
-
- #region Get data
-
private int _connectFailureCount;
+
private async void TimerCallback(object sender)
{
if (_disposed)
@@ -458,7 +460,9 @@ namespace Emby.Dlna.PlayTo
_connectFailureCount = 0;
if (_disposed)
+ {
return;
+ }
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
if (transportState.Value == TRANSPORTSTATE.STOPPED)
@@ -478,7 +482,9 @@ namespace Emby.Dlna.PlayTo
catch (Exception ex)
{
if (_disposed)
+ {
return;
+ }
_logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name);
@@ -494,6 +500,7 @@ namespace Emby.Dlna.PlayTo
return;
}
}
+
RestartTimerInactive();
}
}
@@ -578,7 +585,9 @@ namespace Emby.Dlna.PlayTo
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
+ {
return;
+ }
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse")
.Select(i => i.Element("CurrentMute"))
@@ -750,7 +759,7 @@ namespace Emby.Dlna.PlayTo
if (track == null)
{
- //If track is null, some vendors do this, use GetMediaInfo instead
+ // If track is null, some vendors do this, use GetMediaInfo instead
return (true, null);
}
@@ -794,7 +803,6 @@ namespace Emby.Dlna.PlayTo
}
catch (XmlException)
{
-
}
// first try to add a root node with a dlna namesapce
@@ -806,7 +814,6 @@ namespace Emby.Dlna.PlayTo
}
catch (XmlException)
{
-
}
// some devices send back invalid xml
@@ -816,7 +823,6 @@ namespace Emby.Dlna.PlayTo
}
catch (XmlException)
{
-
}
return null;
@@ -871,10 +877,6 @@ namespace Emby.Dlna.PlayTo
return new string[4];
}
- #endregion
-
- #region From XML
-
private async Task GetAVProtocolAsync(CancellationToken cancellationToken)
{
if (AvCommands != null)
@@ -1069,8 +1071,6 @@ namespace Emby.Dlna.PlayTo
return new Device(deviceProperties, httpClient, logger, config);
}
- #endregion
-
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private static DeviceIcon CreateIcon(XElement element)
{
@@ -1194,8 +1194,6 @@ namespace Emby.Dlna.PlayTo
});
}
- #region IDisposable
-
bool _disposed;
public void Dispose()
@@ -1222,8 +1220,6 @@ namespace Emby.Dlna.PlayTo
_disposed = true;
}
- #endregion
-
public override string ToString()
{
return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index 43e9830540..92a93d4349 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna.Didl;
+using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
@@ -22,6 +23,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
+using Photo = MediaBrowser.Controller.Entities.Photo;
namespace Emby.Dlna.PlayTo
{
@@ -146,11 +148,14 @@ namespace Emby.Dlna.PlayTo
{
var positionTicks = GetProgressPositionTicks(streamInfo);
- ReportPlaybackStopped(streamInfo, positionTicks);
+ await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false);
}
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
- if (streamInfo.Item == null) return;
+ if (streamInfo.Item == null)
+ {
+ return;
+ }
var newItemProgress = GetProgressInfo(streamInfo);
@@ -173,11 +178,14 @@ namespace Emby.Dlna.PlayTo
{
var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
- if (streamInfo.Item == null) return;
+ if (streamInfo.Item == null)
+ {
+ return;
+ }
var positionTicks = GetProgressPositionTicks(streamInfo);
- ReportPlaybackStopped(streamInfo, positionTicks);
+ await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false);
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
@@ -185,7 +193,7 @@ namespace Emby.Dlna.PlayTo
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
mediaSource.RunTimeTicks;
- var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0);
+ var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
if (!playedToCompletion && duration.HasValue && positionTicks.HasValue)
{
@@ -210,7 +218,7 @@ namespace Emby.Dlna.PlayTo
}
}
- private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks)
+ private async Task ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks)
{
try
{
@@ -220,7 +228,6 @@ namespace Emby.Dlna.PlayTo
SessionId = _session.Id,
PositionTicks = positionTicks,
MediaSourceId = streamInfo.MediaSourceId
-
}).ConfigureAwait(false);
}
catch (Exception ex)
@@ -418,6 +425,7 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
return;
}
+
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
}
}
@@ -441,7 +449,13 @@ namespace Emby.Dlna.PlayTo
}
}
- private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
+ private PlaylistItem CreatePlaylistItem(
+ BaseItem item,
+ User user,
+ long startPostionTicks,
+ string mediaSourceId,
+ int? audioStreamIndex,
+ int? subtitleStreamIndex)
{
var deviceInfo = _device.Properties;
@@ -700,6 +714,7 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentException("Volume argument cannot be null");
}
+
default:
return Task.CompletedTask;
}
@@ -785,12 +800,15 @@ namespace Emby.Dlna.PlayTo
public int? SubtitleStreamIndex { get; set; }
public string DeviceProfileId { get; set; }
+
public string DeviceId { get; set; }
public string MediaSourceId { get; set; }
+
public string LiveStreamId { get; set; }
public BaseItem Item { get; set; }
+
private MediaSourceInfo MediaSource;
private IMediaSourceManager _mediaSourceManager;
@@ -908,7 +926,8 @@ namespace Emby.Dlna.PlayTo
return 0;
}
- public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
+ ///
+ public Task SendMessage(string name, Guid messageId, T data, CancellationToken cancellationToken)
{
if (_disposed)
{
@@ -924,10 +943,12 @@ namespace Emby.Dlna.PlayTo
{
return SendPlayCommand(data as PlayRequest, cancellationToken);
}
+
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
{
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
}
+
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
{
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index bbedd1485c..512589e4d7 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -78,9 +78,15 @@ namespace Emby.Dlna.PlayTo
var info = e.Argument;
- if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
+ if (!info.Headers.TryGetValue("USN", out string usn))
+ {
+ usn = string.Empty;
+ }
- if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
+ if (!info.Headers.TryGetValue("NT", out string nt))
+ {
+ nt = string.Empty;
+ }
string location = info.Location.ToString();
@@ -88,7 +94,7 @@ namespace Emby.Dlna.PlayTo
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1)
{
- //_logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
+ // _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
return;
}
@@ -112,7 +118,6 @@ namespace Emby.Dlna.PlayTo
}
catch (OperationCanceledException)
{
-
}
catch (Exception ex)
{
@@ -133,6 +138,7 @@ namespace Emby.Dlna.PlayTo
usn = usn.Substring(index);
found = true;
}
+
index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
@@ -184,7 +190,8 @@ namespace Emby.Dlna.PlayTo
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
}
- controller = new PlayToController(sessionInfo,
+ controller = new PlayToController(
+ sessionInfo,
_sessionManager,
_libraryManager,
_logger,
@@ -242,7 +249,6 @@ namespace Emby.Dlna.PlayTo
}
catch
{
-
}
_sessionLock.Dispose();
diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
index 3b169e5993..fa42b80e8b 100644
--- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
+++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
@@ -12,6 +12,7 @@ namespace Emby.Dlna.PlayTo
public class MediaChangedEventArgs : EventArgs
{
public uBaseObject OldMediaInfo { get; set; }
+
public uBaseObject NewMediaInfo { get; set; }
}
}
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index 8c13620075..ab262bebfc 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -91,7 +91,6 @@ namespace Emby.Dlna.PlayTo
using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
{
-
}
}
diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs
index a8ed5692c9..05c19299f1 100644
--- a/Emby.Dlna/PlayTo/uBaseObject.cs
+++ b/Emby.Dlna/PlayTo/uBaseObject.cs
@@ -44,10 +44,12 @@ namespace Emby.Dlna.PlayTo
{
return MediaBrowser.Model.Entities.MediaType.Audio;
}
+
if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1)
{
return MediaBrowser.Model.Entities.MediaType.Video;
}
+
if (classType.IndexOf("image", StringComparison.Ordinal) != -1)
{
return MediaBrowser.Model.Entities.MediaType.Photo;
diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs
index d10804b228..90a23a4a22 100644
--- a/Emby.Dlna/Profiles/DefaultProfile.cs
+++ b/Emby.Dlna/Profiles/DefaultProfile.cs
@@ -12,7 +12,7 @@ namespace Emby.Dlna.Profiles
{
Name = "Generic Device";
- ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*";
+ ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";
Manufacturer = "Jellyfin";
ModelDescription = "UPnP/AV 1.0 Compliant Media Server";
@@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
public void AddXmlRootAttribute(string name, string value)
{
- var atts = XmlRootAttributes ?? new XmlAttribute[] { };
+ var atts = XmlRootAttributes ?? System.Array.Empty();
var list = atts.ToList();
list.Add(new XmlAttribute
diff --git a/Emby.Dlna/Profiles/DenonAvrProfile.cs b/Emby.Dlna/Profiles/DenonAvrProfile.cs
index 73a87c499e..a5ba0f36cc 100644
--- a/Emby.Dlna/Profiles/DenonAvrProfile.cs
+++ b/Emby.Dlna/Profiles/DenonAvrProfile.cs
@@ -28,7 +28,7 @@ namespace Emby.Dlna.Profiles
},
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty();
}
}
}
diff --git a/Emby.Dlna/Profiles/DirectTvProfile.cs b/Emby.Dlna/Profiles/DirectTvProfile.cs
index 5ca388167b..f6f98b07d8 100644
--- a/Emby.Dlna/Profiles/DirectTvProfile.cs
+++ b/Emby.Dlna/Profiles/DirectTvProfile.cs
@@ -123,7 +123,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty();
}
}
}
diff --git a/Emby.Dlna/Profiles/Foobar2000Profile.cs b/Emby.Dlna/Profiles/Foobar2000Profile.cs
index ea3de686a6..68e959770a 100644
--- a/Emby.Dlna/Profiles/Foobar2000Profile.cs
+++ b/Emby.Dlna/Profiles/Foobar2000Profile.cs
@@ -72,7 +72,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty();
}
}
}
diff --git a/Emby.Dlna/Profiles/MarantzProfile.cs b/Emby.Dlna/Profiles/MarantzProfile.cs
index 6cfcc3b824..24078ab29c 100644
--- a/Emby.Dlna/Profiles/MarantzProfile.cs
+++ b/Emby.Dlna/Profiles/MarantzProfile.cs
@@ -37,7 +37,7 @@ namespace Emby.Dlna.Profiles
},
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty();
}
}
}
diff --git a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
index 7161af7386..28d639b6db 100644
--- a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
+++ b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -37,7 +38,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
index 42b066d52b..238fe9f6bc 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
index fbdf2c18e8..812a481513 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
index ce32179a1b..6bfff322ef 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
index aa1721d398..ec2529574c 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty();
}
}
}
diff --git a/Emby.Dlna/Profiles/Xml/Default.xml b/Emby.Dlna/Profiles/Xml/Default.xml
index daac4135a2..9460f9d5a1 100644
--- a/Emby.Dlna/Profiles/Xml/Default.xml
+++ b/Emby.Dlna/Profiles/Xml/Default.xml
@@ -21,7 +21,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Denon AVR.xml b/Emby.Dlna/Profiles/Xml/Denon AVR.xml
index c76cd9a898..571786906c 100644
--- a/Emby.Dlna/Profiles/Xml/Denon AVR.xml
+++ b/Emby.Dlna/Profiles/Xml/Denon AVR.xml
@@ -26,7 +26,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
index f2ce68ab5d..eea0febfdc 100644
--- a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
+++ b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
10
true
true
diff --git a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml
index a0f0e0ee8a..20f5ba79bf 100644
--- a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml
+++ b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
10
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml
index 55910c77f2..d01e3a145e 100644
--- a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml
+++ b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml
@@ -25,7 +25,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Marantz.xml b/Emby.Dlna/Profiles/Xml/Marantz.xml
index a6345ab3f3..0cc9c86e87 100644
--- a/Emby.Dlna/Profiles/Xml/Marantz.xml
+++ b/Emby.Dlna/Profiles/Xml/Marantz.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml
index 2c2c3a1de4..9d5ddc3d1a 100644
--- a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml
+++ b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml
index 934f0550d3..8f766853bb 100644
--- a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml
+++ b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml
@@ -28,7 +28,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
10
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml
index eab220fae9..aa881d0147 100644
--- a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml
+++ b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml
@@ -21,7 +21,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml
index 3e6f56e5bb..7160a9c2eb 100644
--- a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml
+++ b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml
index 74240b8435..c9b907e580 100644
--- a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml
+++ b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
true
true
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
index 49819ccfdb..e516ff512d 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
index aaad7b342c..88bd1c2f53 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
index 8e2e8dbaa4..3ca9893cdc 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
index 17a6135e1f..8804a75dfa 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
index df385135cd..bafa44b828 100644
--- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
+++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
index 20f50f6b63..eb8e645b31 100644
--- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
+++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/WDTV Live.xml b/Emby.Dlna/Profiles/Xml/WDTV Live.xml
index 05380e33a6..ccb74ee646 100644
--- a/Emby.Dlna/Profiles/Xml/WDTV Live.xml
+++ b/Emby.Dlna/Profiles/Xml/WDTV Live.xml
@@ -28,7 +28,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
5
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Xbox One.xml b/Emby.Dlna/Profiles/Xml/Xbox One.xml
index 5f5cf1af31..26a65bbd44 100644
--- a/Emby.Dlna/Profiles/Xml/Xbox One.xml
+++ b/Emby.Dlna/Profiles/Xml/Xbox One.xml
@@ -28,7 +28,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
40
false
false
diff --git a/Emby.Dlna/Profiles/Xml/foobar2000.xml b/Emby.Dlna/Profiles/Xml/foobar2000.xml
index f3eedb35c6..5ce75ace55 100644
--- a/Emby.Dlna/Profiles/Xml/foobar2000.xml
+++ b/Emby.Dlna/Profiles/Xml/foobar2000.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
index 5ecc81a2f1..bca9e81cd0 100644
--- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs
+++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Security;
using System.Text;
using Emby.Dlna.Common;
using MediaBrowser.Model.Dlna;
@@ -64,10 +65,10 @@ namespace Emby.Dlna.Server
foreach (var att in attributes)
{
- builder.AppendFormat(" {0}=\"{1}\"", att.Name, att.Value);
+ builder.AppendFormat(CultureInfo.InvariantCulture, " {0}=\"{1}\"", att.Name, att.Value);
}
- builder.Append(">");
+ builder.Append('>');
builder.Append("");
builder.Append("1");
@@ -76,7 +77,9 @@ namespace Emby.Dlna.Server
if (!EnableAbsoluteUrls)
{
- builder.Append("" + Escape(_serverAddress) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_serverAddress))
+ .Append("");
}
AppendDeviceInfo(builder);
@@ -93,86 +96,14 @@ namespace Emby.Dlna.Server
AppendIconList(builder);
- builder.Append("" + Escape(_serverAddress) + "/web/index.html");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_serverAddress))
+ .Append("/web/index.html");
AppendServiceList(builder);
builder.Append("");
}
- private static readonly char[] s_escapeChars = new char[]
- {
- '<',
- '>',
- '"',
- '\'',
- '&'
- };
-
- private static readonly string[] s_escapeStringPairs = new[]
- {
- "<",
- "<",
- ">",
- ">",
- "\"",
- """,
- "'",
- "'",
- "&",
- "&"
- };
-
- private static string GetEscapeSequence(char c)
- {
- int num = s_escapeStringPairs.Length;
- for (int i = 0; i < num; i += 2)
- {
- string text = s_escapeStringPairs[i];
- string result = s_escapeStringPairs[i + 1];
- if (text[0] == c)
- {
- return result;
- }
- }
- return c.ToString(CultureInfo.InvariantCulture);
- }
-
- /// Replaces invalid XML characters in a string with their valid XML equivalent.
- /// The input string with invalid characters replaced.
- /// The string within which to escape invalid characters.
- public static string Escape(string str)
- {
- if (str == null)
- {
- return null;
- }
-
- StringBuilder stringBuilder = null;
- int length = str.Length;
- int num = 0;
- while (true)
- {
- int num2 = str.IndexOfAny(s_escapeChars, num);
- if (num2 == -1)
- {
- break;
- }
- if (stringBuilder == null)
- {
- stringBuilder = new StringBuilder();
- }
- stringBuilder.Append(str, num, num2 - num);
- stringBuilder.Append(GetEscapeSequence(str[num2]));
- num = num2 + 1;
- }
- if (stringBuilder == null)
- {
- return str;
- }
- stringBuilder.Append(str, num, length - num);
- return stringBuilder.ToString();
- }
-
private void AppendDeviceProperties(StringBuilder builder)
{
builder.Append("");
@@ -182,32 +113,54 @@ namespace Emby.Dlna.Server
builder.Append("urn:schemas-upnp-org:device:MediaServer:1");
- builder.Append("" + Escape(GetFriendlyName()) + "");
- builder.Append("" + Escape(_profile.Manufacturer ?? string.Empty) + "");
- builder.Append("" + Escape(_profile.ManufacturerUrl ?? string.Empty) + "");
-
- builder.Append("" + Escape(_profile.ModelDescription ?? string.Empty) + "");
- builder.Append("" + Escape(_profile.ModelName ?? string.Empty) + "");
-
- builder.Append("" + Escape(_profile.ModelNumber ?? string.Empty) + "");
- builder.Append("" + Escape(_profile.ModelUrl ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(GetFriendlyName()))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.Manufacturer ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ManufacturerUrl ?? string.Empty))
+ .Append("");
+
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ModelDescription ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ModelName ?? string.Empty))
+ .Append("");
+
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ModelNumber ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ModelUrl ?? string.Empty))
+ .Append("");
if (string.IsNullOrEmpty(_profile.SerialNumber))
{
- builder.Append("" + Escape(_serverId) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_serverId))
+ .Append("");
}
else
{
- builder.Append("" + Escape(_profile.SerialNumber) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.SerialNumber))
+ .Append("");
}
builder.Append("");
- builder.Append("uuid:" + Escape(_serverUdn) + "");
+ builder.Append("uuid:")
+ .Append(SecurityElement.Escape(_serverUdn))
+ .Append("");
if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags))
{
- builder.Append("" + Escape(_profile.SonyAggregationFlags) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.SonyAggregationFlags))
+ .Append("");
}
}
@@ -245,11 +198,21 @@ namespace Emby.Dlna.Server
{
builder.Append("");
- builder.Append("" + Escape(icon.MimeType ?? string.Empty) + "");
- builder.Append("" + Escape(icon.Width.ToString(_usCulture)) + "");
- builder.Append("" + Escape(icon.Height.ToString(_usCulture)) + "");
- builder.Append("" + Escape(icon.Depth ?? string.Empty) + "");
- builder.Append("" + BuildUrl(icon.Url) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(icon.Width.ToString(_usCulture)))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(icon.Height.ToString(_usCulture)))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(BuildUrl(icon.Url))
+ .Append("");
builder.Append("");
}
@@ -265,11 +228,21 @@ namespace Emby.Dlna.Server
{
builder.Append("");
- builder.Append("" + Escape(service.ServiceType ?? string.Empty) + "");
- builder.Append("" + Escape(service.ServiceId ?? string.Empty) + "");
- builder.Append("" + BuildUrl(service.ScpdUrl) + "");
- builder.Append("" + BuildUrl(service.ControlUrl) + "");
- builder.Append("" + BuildUrl(service.EventSubUrl) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(BuildUrl(service.ScpdUrl))
+ .Append("");
+ builder.Append("")
+ .Append(BuildUrl(service.ControlUrl))
+ .Append("");
+ builder.Append("")
+ .Append(BuildUrl(service.EventSubUrl))
+ .Append("");
builder.Append("");
}
@@ -293,7 +266,7 @@ namespace Emby.Dlna.Server
url = _serverAddress.TrimEnd('/') + url;
}
- return Escape(url);
+ return SecurityElement.Escape(url);
}
private IEnumerable GetIcons()
diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs
index 161a3434c5..699d325eac 100644
--- a/Emby.Dlna/Service/BaseControlHandler.cs
+++ b/Emby.Dlna/Service/BaseControlHandler.cs
@@ -18,6 +18,7 @@ namespace Emby.Dlna.Service
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
protected IServerConfigurationManager Config { get; }
+
protected ILogger Logger { get; }
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
@@ -135,6 +136,7 @@ namespace Emby.Dlna.Service
break;
}
+
default:
{
await reader.SkipAsync().ConfigureAwait(false);
@@ -211,7 +213,9 @@ namespace Emby.Dlna.Service
private class ControlRequestInfo
{
public string LocalName { get; set; }
+
public string NamespaceURI { get; set; }
+
public Dictionary Headers { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs
index 3704bedcd5..8794ec26a8 100644
--- a/Emby.Dlna/Service/BaseService.cs
+++ b/Emby.Dlna/Service/BaseService.cs
@@ -17,7 +17,7 @@ namespace Emby.Dlna.Service
Logger = logger;
HttpClient = httpClient;
- EventManager = new EventManager(Logger, HttpClient);
+ EventManager = new EventManager(logger, HttpClient);
}
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs
index 62ffd9e42a..6c7d6f8462 100644
--- a/Emby.Dlna/Service/ServiceXmlBuilder.cs
+++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs
@@ -1,9 +1,9 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.Security;
using System.Text;
using Emby.Dlna.Common;
-using Emby.Dlna.Server;
namespace Emby.Dlna.Service
{
@@ -37,7 +37,9 @@ namespace Emby.Dlna.Service
{
builder.Append("");
- builder.Append("" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(item.Name ?? string.Empty))
+ .Append("");
builder.Append("");
@@ -45,9 +47,15 @@ namespace Emby.Dlna.Service
{
builder.Append("");
- builder.Append("" + DescriptionXmlBuilder.Escape(argument.Name ?? string.Empty) + "");
- builder.Append("" + DescriptionXmlBuilder.Escape(argument.Direction ?? string.Empty) + "");
- builder.Append("" + DescriptionXmlBuilder.Escape(argument.RelatedStateVariable ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(argument.Name ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
+ .Append("");
builder.Append("");
}
@@ -68,18 +76,27 @@ namespace Emby.Dlna.Service
{
var sendEvents = item.SendsEvents ? "yes" : "no";
- builder.Append("");
+ builder.Append("");
- builder.Append("" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "");
- builder.Append("" + DescriptionXmlBuilder.Escape(item.DataType ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(item.Name ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(item.DataType ?? string.Empty))
+ .Append("");
if (item.AllowedValues.Length > 0)
{
builder.Append("");
foreach (var allowedValue in item.AllowedValues)
{
- builder.Append("" + DescriptionXmlBuilder.Escape(allowedValue) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(allowedValue))
+ .Append("");
}
+
builder.Append("");
}
diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
index f95b8ce7de..7daac96d1d 100644
--- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
@@ -77,7 +77,7 @@ namespace Emby.Dlna.Ssdp
// (Optional) Set the filter so we only see notifications for devices we care about
// (can be any search target value i.e device type, uuid value etc - any value that appears in the
// DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
- //_DeviceLocator.NotificationFilter = "upnp:rootdevice";
+ // _DeviceLocator.NotificationFilter = "upnp:rootdevice";
// Connect our event handler so we process devices as they are found
_deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;
@@ -100,15 +100,13 @@ namespace Emby.Dlna.Ssdp
var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
- var args = new GenericEventArgs
- {
- Argument = new UpnpDeviceInfo
+ var args = new GenericEventArgs(
+ new UpnpDeviceInfo
{
Location = e.DiscoveredDevice.DescriptionLocation,
Headers = headers,
LocalIpAddress = e.LocalIpAddress
- }
- };
+ });
DeviceDiscoveredInternal?.Invoke(this, args);
}
@@ -121,14 +119,12 @@ namespace Emby.Dlna.Ssdp
var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
- var args = new GenericEventArgs
- {
- Argument = new UpnpDeviceInfo
+ var args = new GenericEventArgs(
+ new UpnpDeviceInfo
{
Location = e.DiscoveredDevice.DescriptionLocation,
Headers = headers
- }
- };
+ });
DeviceLeft?.Invoke(this, args);
}
diff --git a/Emby.Dlna/Ssdp/Extensions.cs b/Emby.Dlna/Ssdp/Extensions.cs
index 10c1f321be..613d332b2d 100644
--- a/Emby.Dlna/Ssdp/Extensions.cs
+++ b/Emby.Dlna/Ssdp/Extensions.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System.Linq;
using System.Xml.Linq;
namespace Emby.Dlna.Ssdp
@@ -10,24 +11,17 @@ namespace Emby.Dlna.Ssdp
{
var node = container.Element(name);
- return node == null ? null : node.Value;
+ return node?.Value;
}
public static string GetAttributeValue(this XElement container, XName name)
{
var node = container.Attribute(name);
- return node == null ? null : node.Value;
+ return node?.Value;
}
public static string GetDescendantValue(this XElement container, XName name)
- {
- foreach (var node in container.Descendants(name))
- {
- return node.Value;
- }
-
- return null;
- }
+ => container.Descendants(name).FirstOrDefault()?.Value;
}
}
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj
index b7090b2629..092f8580a6 100644
--- a/Emby.Drawing/Emby.Drawing.csproj
+++ b/Emby.Drawing/Emby.Drawing.csproj
@@ -1,10 +1,16 @@
+
+
+ {08FFF49B-F175-4807-A2B5-73B0EBD9F716}
+
+
netstandard2.1
false
true
true
+ enable
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index eca4b56eb9..8696cb2805 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -4,17 +4,18 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
+using Photo = MediaBrowser.Controller.Entities.Photo;
namespace Emby.Drawing
{
@@ -29,12 +30,11 @@ namespace Emby.Drawing
private static readonly HashSet _transparentImageTypes
= new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
private readonly IImageEncoder _imageEncoder;
- private readonly Func _libraryManager;
- private readonly Func _mediaEncoder;
+ private readonly IMediaEncoder _mediaEncoder;
private bool _disposed = false;
@@ -45,20 +45,17 @@ namespace Emby.Drawing
/// The server application paths.
/// The filesystem.
/// The image encoder.
- /// The library manager.
/// The media encoder.
public ImageProcessor(
ILogger logger,
IServerApplicationPaths appPaths,
IFileSystem fileSystem,
IImageEncoder imageEncoder,
- Func libraryManager,
- Func mediaEncoder)
+ IMediaEncoder mediaEncoder)
{
_logger = logger;
_fileSystem = fileSystem;
_imageEncoder = imageEncoder;
- _libraryManager = libraryManager;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
}
@@ -119,28 +116,11 @@ namespace Emby.Drawing
=> _transparentImageTypes.Contains(Path.GetExtension(path));
///
- public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
+ public async Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
{
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
-
- var libraryManager = _libraryManager();
-
ItemImageInfo originalImage = options.Image;
BaseItem item = options.Item;
- if (!originalImage.IsLocalFile)
- {
- if (item == null)
- {
- item = libraryManager.GetItemById(options.ItemId);
- }
-
- originalImage = await libraryManager.ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false);
- }
-
string originalImagePath = originalImage.Path;
DateTime dateModified = originalImage.DateModified;
ImageDimensions? originalImageSize = null;
@@ -252,7 +232,7 @@ namespace Emby.Drawing
return ImageFormat.Jpg;
}
- private string GetMimeType(ImageFormat format, string path)
+ private string? GetMimeType(ImageFormat format, string path)
=> format switch
{
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
@@ -312,10 +292,6 @@ namespace Emby.Drawing
///
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
- => GetImageDimensions(item, info, true);
-
- ///
- public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
{
int width = info.Width;
int height = info.Height;
@@ -326,17 +302,12 @@ namespace Emby.Drawing
}
string path = info.Path;
- _logger.LogInformation("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
+ _logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
ImageDimensions size = GetImageDimensions(path);
info.Width = size.Width;
info.Height = size.Height;
- if (updateItem)
- {
- _libraryManager().UpdateImages(item);
- }
-
return size;
}
@@ -344,6 +315,27 @@ namespace Emby.Drawing
public ImageDimensions GetImageDimensions(string path)
=> _imageEncoder.GetImageSize(path);
+ ///
+ public string GetImageBlurHash(string path)
+ {
+ var size = GetImageDimensions(path);
+ if (size.Width <= 0 || size.Height <= 0)
+ {
+ return string.Empty;
+ }
+
+ // We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
+ // One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
+ // See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
+ float xCompF = MathF.Sqrt(16.0f * size.Width / size.Height);
+ float yCompF = xCompF * size.Height / size.Width;
+
+ int xComp = Math.Min((int)xCompF + 1, 9);
+ int yComp = Math.Min((int)yCompF + 1, 9);
+
+ return _imageEncoder.GetImageBlurHash(xComp, yComp, path);
+ }
+
///
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
@@ -351,19 +343,19 @@ namespace Emby.Drawing
///
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
{
- try
+ return GetImageCacheTag(item, new ItemImageInfo
{
- return GetImageCacheTag(item, new ItemImageInfo
- {
- Path = chapter.ImagePath,
- Type = ImageType.Chapter,
- DateModified = chapter.ImageDateModified
- });
- }
- catch
- {
- return null;
- }
+ Path = chapter.ImagePath,
+ Type = ImageType.Chapter,
+ DateModified = chapter.ImageDateModified
+ });
+ }
+
+ ///
+ public string GetImageCacheTag(User user)
+ {
+ return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5()
+ .ToString("N", CultureInfo.InvariantCulture);
}
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
@@ -384,13 +376,13 @@ namespace Emby.Drawing
{
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
- string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
+ string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
var file = _fileSystem.GetFileInfo(outputPath);
if (!file.Exists)
{
- await _mediaEncoder().ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
+ await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
}
else
diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs
index 5af7f16225..bbb5c17162 100644
--- a/Emby.Drawing/NullImageEncoder.cs
+++ b/Emby.Drawing/NullImageEncoder.cs
@@ -42,5 +42,11 @@ namespace Emby.Drawing
{
throw new NotImplementedException();
}
+
+ ///
+ public string GetImageBlurHash(int xComp, int yComp, string path)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs
index 33f4468d9f..b63be3a647 100644
--- a/Emby.Naming/Audio/AlbumParser.cs
+++ b/Emby.Naming/Audio/AlbumParser.cs
@@ -1,9 +1,9 @@
+#nullable enable
#pragma warning disable CS1591
using System;
using System.Globalization;
using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
@@ -21,8 +21,7 @@ namespace Emby.Naming.Audio
public bool IsMultiPart(string path)
{
var filename = Path.GetFileName(path);
-
- if (string.IsNullOrEmpty(filename))
+ if (filename.Length == 0)
{
return false;
}
@@ -39,18 +38,22 @@ namespace Emby.Naming.Audio
filename = filename.Replace(')', ' ');
filename = Regex.Replace(filename, @"\s+", " ");
- filename = filename.TrimStart();
+ ReadOnlySpan trimmedFilename = filename.TrimStart();
foreach (var prefix in _options.AlbumStackingPrefixes)
{
- if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0)
+ if (!trimmedFilename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
continue;
}
- var tmp = filename.Substring(prefix.Length);
+ var tmp = trimmedFilename.Slice(prefix.Length).Trim();
- tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
+ int index = tmp.IndexOf(' ');
+ if (index != -1)
+ {
+ tmp = tmp.Slice(0, index);
+ }
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
{
diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs
index 25d5f8735e..6b2f4be93e 100644
--- a/Emby.Naming/Audio/AudioFileParser.cs
+++ b/Emby.Naming/Audio/AudioFileParser.cs
@@ -1,3 +1,4 @@
+#nullable enable
#pragma warning disable CS1591
using System;
@@ -11,7 +12,7 @@ namespace Emby.Naming.Audio
{
public static bool IsAudioFile(string path, NamingOptions options)
{
- var extension = Path.GetExtension(path) ?? string.Empty;
+ var extension = Path.GetExtension(path);
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
index 5494df9d63..3c874c62ca 100644
--- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
+++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
@@ -64,6 +64,7 @@ namespace Emby.Naming.AudioBook
{
result.ChapterNumber = int.Parse(matches[0].Groups[0].Value);
}
+
if (matches.Count > 1)
{
result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value);
diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs
index 07de728514..ed6ba8881c 100644
--- a/Emby.Naming/Common/EpisodeExpression.cs
+++ b/Emby.Naming/Common/EpisodeExpression.cs
@@ -23,11 +23,6 @@ namespace Emby.Naming.Common
{
}
- public EpisodeExpression()
- : this(null)
- {
- }
-
public string Expression
{
get => _expression;
@@ -48,6 +43,6 @@ namespace Emby.Naming.Common
public string[] DateTimeFormats { get; set; }
- public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
+ public Regex Regex => _regex ??= new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
}
diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs
index cc18ce4cdd..148833765f 100644
--- a/Emby.Naming/Common/MediaType.cs
+++ b/Emby.Naming/Common/MediaType.cs
@@ -5,17 +5,17 @@ namespace Emby.Naming.Common
public enum MediaType
{
///
- /// The audio
+ /// The audio.
///
Audio = 0,
///
- /// The photo
+ /// The photo.
///
Photo = 1,
///
- /// The video
+ /// The video.
///
Video = 2
}
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 793847f84c..d1e17f4169 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -136,12 +136,13 @@ namespace Emby.Naming.Common
CleanDateTimes = new[]
{
- @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
+ @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
+ @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
};
CleanStrings = new[]
{
- @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
+ @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
@"(\[.*\])"
};
@@ -276,7 +277,7 @@ namespace Emby.Naming.Common
// This isn't a Kodi naming rule, but the expression below causes false positives,
// so we make sure this one gets tested first.
// "Foo Bar 889"
- new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?[\w\s]+?)\s(?\d{1,3})(-(?\d{2,3}))*[^\\\/x]*$")
+ new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?[\w\s]+?)\s(?[0-9]{1,3})(-(?[0-9]{2,3}))*[^\\\/x]*$")
{
IsNamed = true
},
@@ -299,32 +300,32 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming
// [bar] Foo - 1 [baz]
- new EpisodeExpression(@".*?(\[.*?\])+.*?(?[\w\s]+?)[-\s_]+(?\d+).*$")
+ new EpisodeExpression(@".*?(\[.*?\])+.*?(?[\w\s]+?)[-\s_]+(?[0-9]+).*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)[sS]?(?\d+)[xX](?\d+)[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)[sS]?(?[0-9]+)[xX](?[0-9]+)[^\\\/]*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)[sS](?\d+)[x,X]?[eE](?\d+)[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)[sS](?[0-9]+)[x,X]?[eE](?[0-9]+)[^\\\/]*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d+))[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]+))[^\\\/]*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d+)[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)(?[^\\\/]*)[sS](?[0-9]{1,4})[xX\.]?[eE](?[0-9]+)[^\\\/]*$")
{
IsNamed = true
},
// "01.avi"
- new EpisodeExpression(@".*[\\\/](?\d+)(-(?\d+))*\.\w+$")
+ new EpisodeExpression(@".*[\\\/](?[0-9]+)(-(?[0-9]+))*\.\w+$")
{
IsOptimistic = true,
IsNamed = true
@@ -334,34 +335,34 @@ namespace Emby.Naming.Common
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
// "01 - blah.avi", "01-blah.avi"
- new EpisodeExpression(@".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\s?-\s?[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)(?[0-9]{1,3})(-(?[0-9]{2,3}))*\s?-\s?[^\\\/]*$")
{
IsOptimistic = true,
IsNamed = true
},
// "01.blah.avi"
- new EpisodeExpression(@".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\.[^\\\/]+$")
+ new EpisodeExpression(@".*(\\|\/)(?[0-9]{1,3})(-(?[0-9]{2,3}))*\.[^\\\/]+$")
{
IsOptimistic = true,
IsNamed = true
},
// "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah"
- new EpisodeExpression(@".*[\\\/][^\\\/]* - (?\d{1,3})(-(?\d{2,3}))*[^\\\/]*$")
+ new EpisodeExpression(@".*[\\\/][^\\\/]* - (?[0-9]{1,3})(-(?[0-9]{2,3}))*[^\\\/]*$")
{
IsOptimistic = true,
IsNamed = true
},
// "01 episode title.avi"
- new EpisodeExpression(@"[Ss]eason[\._ ](?[0-9]+)[\\\/](?\d{1,3})([^\\\/]*)$")
+ new EpisodeExpression(@"[Ss]eason[\._ ](?[0-9]+)[\\\/](?[0-9]{1,3})([^\\\/]*)$")
{
IsOptimistic = true,
IsNamed = true
},
// "Episode 16", "Episode 16 - Title"
- new EpisodeExpression(@".*[\\\/][^\\\/]* (?\d{1,3})(-(?\d{2,3}))*[^\\\/]*$")
+ new EpisodeExpression(@".*[\\\/][^\\\/]* (?[0-9]{1,3})(-(?[0-9]{2,3}))*[^\\\/]*$")
{
IsOptimistic = true,
IsNamed = true
@@ -505,7 +506,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[]
@@ -568,17 +625,17 @@ namespace Emby.Naming.Common
AudioBookPartsExpressions = new[]
{
// Detect specified chapters, like CH 01
- @"ch(?:apter)?[\s_-]?(?\d+)",
+ @"ch(?:apter)?[\s_-]?(?[0-9]+)",
// Detect specified parts, like Part 02
- @"p(?:ar)?t[\s_-]?(?\d+)",
+ @"p(?:ar)?t[\s_-]?(?[0-9]+)",
// Chapter is often beginning of filename
- @"^(?\d+)",
+ "^(?[0-9]+)",
// Part if often ending of filename
- @"(?\d+)$",
+ "(?[0-9]+)$",
// Sometimes named as 0001_005 (chapter_part)
- @"(?\d+)_(?\d+)",
+ "(?[0-9]+)_(?[0-9]+)",
// Some audiobooks are ripped from cd's, and will be named by disk number.
- @"dis(?:c|k)[\s_-]?(?\d+)"
+ @"dis(?:c|k)[\s_-]?(?[0-9]+)"
};
var extensions = VideoFileExtensions.ToList();
@@ -618,16 +675,16 @@ namespace Emby.Naming.Common
MultipleEpisodeExpressions = new string[]
{
- @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )\d{1,4}[eExX](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )\d{1,4}[xX][eE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})(-[xE]?[eE]?(?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )\d{1,4}[xXeE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )\d{1,4}[xX][eE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$"
+ @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )?[xXeE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})(-[xE]?[eE]?(?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))((-| - )[0-9]{1,4}[xXeE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))((-| - )[0-9]{1,4}[xX][eE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))((-| - )?[xXeE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))(-[xX]?[eE]?(?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?[^\\\/]*)[sS](?[0-9]{1,4})[xX\.]?[eE](?[0-9]{1,3})((-| - )?[xXeE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?[^\\\/]*)[sS](?[0-9]{1,4})[xX\.]?[eE](?[0-9]{1,3})(-[xX]?[eE]?(?[0-9]{1,3}))+[^\\\/]*$"
}.Select(i => new EpisodeExpression(i)
{
IsNamed = true
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index 4e08170a47..c017e76c74 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -1,5 +1,10 @@
+
+
+ {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}
+
+
netstandard2.1
false
diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs
index 88ec3e2d60..24e59f90a3 100644
--- a/Emby.Naming/Subtitles/SubtitleParser.cs
+++ b/Emby.Naming/Subtitles/SubtitleParser.cs
@@ -1,3 +1,4 @@
+#nullable enable
#pragma warning disable CS1591
using System;
@@ -16,11 +17,11 @@ namespace Emby.Naming.Subtitles
_options = options;
}
- public SubtitleInfo ParseFile(string path)
+ public SubtitleInfo? ParseFile(string path)
{
- if (string.IsNullOrEmpty(path))
+ if (path.Length == 0)
{
- throw new ArgumentNullException(nameof(path));
+ throw new ArgumentException("File path can't be empty.", nameof(path));
}
var extension = Path.GetExtension(path);
@@ -52,11 +53,6 @@ namespace Emby.Naming.Subtitles
private string[] GetFlags(string path)
{
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
// Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
var file = Path.GetFileName(path);
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index 42a5c88b31..fc0424faab 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -80,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/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs
index cb58a39347..7c9702e244 100644
--- a/Emby.Naming/Video/ExtraRule.cs
+++ b/Emby.Naming/Video/ExtraRule.cs
@@ -5,30 +5,29 @@ 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 b021a04a31..e89876f4ae 100644
--- a/Emby.Naming/Video/ExtraRuleType.cs
+++ b/Emby.Naming/Video/ExtraRuleType.cs
@@ -5,18 +5,23 @@ 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/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 7f755fd25e..948fe037b5 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -227,7 +227,7 @@ namespace Emby.Naming.Video
}
return remainingFiles
- .Where(i => i.ExtraType == null)
+ .Where(i => i.ExtraType != null)
.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 0b75a8cce9..b4aee614b0 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -89,14 +89,14 @@ namespace Emby.Naming.Video
if (parseName)
{
var cleanDateTimeResult = CleanDateTime(name);
+ name = cleanDateTimeResult.Name;
+ year = cleanDateTimeResult.Year;
if (extraResult.ExtraType == null
- && TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan newName))
+ && TryCleanString(name, out ReadOnlySpan newName))
{
name = newName.ToString();
}
-
- year = cleanDateTimeResult.Year;
}
return new VideoFileInfo
diff --git a/Emby.Notifications/Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs
index 788750796d..1ff8a50267 100644
--- a/Emby.Notifications/Api/NotificationsService.cs
+++ b/Emby.Notifications/Api/NotificationsService.cs
@@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Notifications;
@@ -149,9 +150,7 @@ namespace Emby.Notifications.Api
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotificationsSummary request)
{
- return new NotificationsSummary
- {
- };
+ return new NotificationsSummary();
}
public Task Post(AddAdminNotification request)
@@ -164,7 +163,10 @@ namespace Emby.Notifications.Api
Level = request.Level,
Name = request.Name,
Url = request.Url,
- UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray()
+ UserIds = _userManager.Users
+ .Where(user => user.HasPermission(PermissionKind.IsAdministrator))
+ .Select(user => user.Id)
+ .ToArray()
};
return _notificationManager.SendNotification(notification, CancellationToken.None);
diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj
index e6bf785bff..1d430a5e58 100644
--- a/Emby.Notifications/Emby.Notifications.csproj
+++ b/Emby.Notifications/Emby.Notifications.csproj
@@ -1,5 +1,10 @@
+
+
+ {2E030C33-6923-4530-9E54-FA29FA6AD1A9}
+
+
netstandard2.1
false
diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs
index befecc570b..b923fd26ce 100644
--- a/Emby.Notifications/NotificationEntryPoint.cs
+++ b/Emby.Notifications/NotificationEntryPoint.cs
@@ -25,7 +25,7 @@ namespace Emby.Notifications
///
public class NotificationEntryPoint : IServerEntryPoint
{
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
private readonly IActivityManager _activityManager;
private readonly ILocalizationManager _localization;
private readonly INotificationManager _notificationManager;
@@ -143,7 +143,7 @@ namespace Emby.Notifications
var notification = new NotificationRequest
{
- Description = "Please see jellyfin.media for details.",
+ Description = "Please see jellyfin.org for details.",
NotificationType = type,
Name = _localization.GetLocalizedString("NewVersionIsAvailable")
};
diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs
index 639a5e1aad..8b281e487f 100644
--- a/Emby.Notifications/NotificationManager.cs
+++ b/Emby.Notifications/NotificationManager.cs
@@ -4,6 +4,8 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
@@ -21,7 +23,7 @@ namespace Emby.Notifications
///
public class NotificationManager : INotificationManager
{
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
private readonly IUserManager _userManager;
private readonly IServerConfigurationManager _config;
@@ -101,7 +103,7 @@ namespace Emby.Notifications
switch (request.SendToUserMode.Value)
{
case SendToUserType.Admins:
- return _userManager.Users.Where(i => i.Policy.IsAdministrator)
+ return _userManager.Users.Where(i => i.HasPermission(PermissionKind.IsAdministrator))
.Select(i => i.Id);
case SendToUserType.All:
return _userManager.UsersIds;
@@ -117,7 +119,7 @@ namespace Emby.Notifications
var config = GetConfiguration();
return _userManager.Users
- .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i.Policy))
+ .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i))
.Select(i => i.Id);
}
@@ -142,7 +144,7 @@ namespace Emby.Notifications
User = user
};
- _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Name);
+ _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Username);
try
{
diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index cc3fbb43f9..dbe01257f4 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -1,4 +1,10 @@
+
+
+
+ {89AB4548-770D-41FD-A891-8DAFF44F452C}
+
+
diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs
index 63631e512b..4071e4e547 100644
--- a/Emby.Photos/PhotoProvider.cs
+++ b/Emby.Photos/PhotoProvider.cs
@@ -22,7 +22,7 @@ namespace Emby.Photos
///
public class PhotoProvider : ICustomMetadataProvider, IForcedProvider, IHasItemChangeMonitor
{
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
private readonly IImageProcessor _imageProcessor;
// These are causing taglib to hang
@@ -104,7 +104,7 @@ namespace Emby.Photos
item.Overview = image.ImageTag.Comment;
if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
- && !item.LockedFields.Contains(MetadataFields.Name))
+ && !item.LockedFields.Contains(MetadataField.Name))
{
item.Name = image.ImageTag.Title;
}
@@ -160,7 +160,7 @@ namespace Emby.Photos
try
{
- var size = _imageProcessor.GetImageDimensions(item, img, false);
+ var size = _imageProcessor.GetImageDimensions(item, img);
if (size.Width > 0 && size.Height > 0)
{
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index d900520b2a..84bec92014 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -1,16 +1,13 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
@@ -27,9 +24,12 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
+ ///
+ /// Entry point for the activity logger.
+ ///
public sealed class ActivityLogEntryPoint : IServerEntryPoint
{
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
private readonly IInstallationManager _installationManager;
private readonly ISessionManager _sessionManager;
private readonly ITaskManager _taskManager;
@@ -37,25 +37,21 @@ namespace Emby.Server.Implementations.Activity
private readonly ILocalizationManager _localization;
private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager;
- private readonly IDeviceManager _deviceManager;
///
/// Initializes a new instance of the class.
///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
+ /// The logger.
+ /// The session manager.
+ /// The task manager.
+ /// The activity manager.
+ /// The localization manager.
+ /// The installation manager.
+ /// The subtitle manager.
+ /// The user manager.
public ActivityLogEntryPoint(
ILogger logger,
ISessionManager sessionManager,
- IDeviceManager deviceManager,
ITaskManager taskManager,
IActivityManager activityManager,
ILocalizationManager localization,
@@ -65,7 +61,6 @@ namespace Emby.Server.Implementations.Activity
{
_logger = logger;
_sessionManager = sessionManager;
- _deviceManager = deviceManager;
_taskManager = taskManager;
_activityManager = activityManager;
_localization = localization;
@@ -74,6 +69,7 @@ namespace Emby.Server.Implementations.Activity
_userManager = userManager;
}
+ ///
public Task RunAsync()
{
_taskManager.TaskCompleted += OnTaskCompleted;
@@ -92,58 +88,45 @@ namespace Emby.Server.Implementations.Activity
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
- _userManager.UserCreated += OnUserCreated;
- _userManager.UserPasswordChanged += OnUserPasswordChanged;
- _userManager.UserDeleted += OnUserDeleted;
- _userManager.UserPolicyUpdated += OnUserPolicyUpdated;
- _userManager.UserLockedOut += OnUserLockedOut;
-
- _deviceManager.CameraImageUploaded += OnCameraImageUploaded;
+ _userManager.OnUserCreated += OnUserCreated;
+ _userManager.OnUserPasswordChanged += OnUserPasswordChanged;
+ _userManager.OnUserDeleted += OnUserDeleted;
+ _userManager.OnUserLockedOut += OnUserLockedOut;
return Task.CompletedTask;
}
- private void OnCameraImageUploaded(object sender, GenericEventArgs e)
- {
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("CameraImageUploadedFrom"),
- e.Argument.Device.Name),
- Type = NotificationType.CameraImageUploaded.ToString()
- });
- }
-
- private void OnUserLockedOut(object sender, GenericEventArgs e)
+ private async void OnUserLockedOut(object sender, GenericEventArgs e)
{
- CreateLogEntry(new ActivityLogEntry
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("UserLockedOutWithName"),
+ e.Argument.Username),
+ NotificationType.UserLockedOut.ToString(),
+ e.Argument.Id)
{
- Name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("UserLockedOutWithName"),
- e.Argument.Name),
- Type = NotificationType.UserLockedOut.ToString(),
- UserId = e.Argument.Id
- });
+ LogSeverity = LogLevel.Error
+ }).ConfigureAwait(false);
}
- private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
+ private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
e.Provider,
- Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
- Type = "SubtitleDownloadFailure",
+ Notifications.NotificationEntryPoint.GetItemName(e.Item)),
+ "SubtitleDownloadFailure",
+ Guid.Empty)
+ {
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
ShortOverview = e.Exception.Message
- });
+ }).ConfigureAwait(false);
}
- private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
+ private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
{
var item = e.MediaInfo;
@@ -166,15 +149,19 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users[0];
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
- Type = GetPlaybackStoppedNotificationType(item.MediaType),
- UserId = user.Id
- });
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
+ user.Username,
+ GetItemName(item),
+ e.DeviceName),
+ GetPlaybackStoppedNotificationType(item.MediaType),
+ user.Id))
+ .ConfigureAwait(false);
}
- private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
+ private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
{
var item = e.MediaInfo;
@@ -197,17 +184,16 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users.First();
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
- user.Name,
+ user.Username,
GetItemName(item),
e.DeviceName),
- Type = GetPlaybackNotificationType(item.MediaType),
- UserId = user.Id
- });
+ GetPlaybackNotificationType(item.MediaType),
+ user.Id))
+ .ConfigureAwait(false);
}
private static string GetItemName(BaseItemDto item)
@@ -257,236 +243,203 @@ namespace Emby.Server.Implementations.Activity
return null;
}
- private void OnSessionEnded(object sender, SessionEventArgs e)
+ private async void OnSessionEnded(object sender, SessionEventArgs e)
{
- string name;
var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName))
{
- name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("DeviceOfflineWithName"),
- session.DeviceName);
-
- // Causing too much spam for now
return;
}
- else
- {
- name = string.Format(
+
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOfflineFromDevice"),
session.UserName,
- session.DeviceName);
- }
-
- CreateLogEntry(new ActivityLogEntry
+ session.DeviceName),
+ "SessionEnded",
+ session.UserId)
{
- Name = name,
- Type = "SessionEnded",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
session.RemoteEndPoint),
- UserId = session.UserId
- });
+ }).ConfigureAwait(false);
}
- private void OnAuthenticationSucceeded(object sender, GenericEventArgs e)
+ private async void OnAuthenticationSucceeded(object sender, GenericEventArgs e)
{
var user = e.Argument.User;
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
user.Name),
- Type = "AuthenticationSucceeded",
+ "AuthenticationSucceeded",
+ user.Id)
+ {
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.SessionInfo.RemoteEndPoint),
- UserId = user.Id
- });
+ }).ConfigureAwait(false);
}
- private void OnAuthenticationFailed(object sender, GenericEventArgs e)
+ private async void OnAuthenticationFailed(object sender, GenericEventArgs e)
{
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
e.Argument.Username),
- Type = "AuthenticationFailed",
+ "AuthenticationFailed",
+ Guid.Empty)
+ {
+ LogSeverity = LogLevel.Error,
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.RemoteEndPoint),
- Severity = LogLevel.Error
- });
+ }).ConfigureAwait(false);
}
- private void OnUserPolicyUpdated(object sender, GenericEventArgs e)
+ private async void OnUserDeleted(object sender, GenericEventArgs e)
{
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("UserPolicyUpdatedWithName"),
- e.Argument.Name),
- Type = "UserPolicyUpdated",
- UserId = e.Argument.Id
- });
- }
-
- private void OnUserDeleted(object sender, GenericEventArgs e)
- {
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserDeletedWithName"),
- e.Argument.Name),
- Type = "UserDeleted"
- });
+ e.Argument.Username),
+ "UserDeleted",
+ Guid.Empty))
+ .ConfigureAwait(false);
}
- private void OnUserPasswordChanged(object sender, GenericEventArgs e)
+ private async void OnUserPasswordChanged(object sender, GenericEventArgs e)
{
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPasswordChangedWithName"),
- e.Argument.Name),
- Type = "UserPasswordChanged",
- UserId = e.Argument.Id
- });
+ e.Argument.Username),
+ "UserPasswordChanged",
+ e.Argument.Id))
+ .ConfigureAwait(false);
}
- private void OnUserCreated(object sender, GenericEventArgs e)
+ private async void OnUserCreated(object sender, GenericEventArgs e)
{
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserCreatedWithName"),
- e.Argument.Name),
- Type = "UserCreated",
- UserId = e.Argument.Id
- });
+ e.Argument.Username),
+ "UserCreated",
+ e.Argument.Id))
+ .ConfigureAwait(false);
}
- private void OnSessionStarted(object sender, SessionEventArgs e)
+ private async void OnSessionStarted(object sender, SessionEventArgs e)
{
- string name;
var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName))
{
- name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("DeviceOnlineWithName"),
- session.DeviceName);
-
- // Causing too much spam for now
return;
}
- else
- {
- name = string.Format(
+
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOnlineFromDevice"),
session.UserName,
- session.DeviceName);
- }
-
- CreateLogEntry(new ActivityLogEntry
+ session.DeviceName),
+ "SessionStarted",
+ session.UserId)
{
- Name = name,
- Type = "SessionStarted",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
- session.RemoteEndPoint),
- UserId = session.UserId
- });
+ session.RemoteEndPoint)
+ }).ConfigureAwait(false);
}
- private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
+ private async void OnPluginUpdated(object sender, InstallationInfo e)
{
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUpdatedWithName"),
- e.Argument.Item1.Name),
- Type = NotificationType.PluginUpdateInstalled.ToString(),
+ e.Name),
+ NotificationType.PluginUpdateInstalled.ToString(),
+ Guid.Empty)
+ {
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
- e.Argument.Item2.versionStr),
- Overview = e.Argument.Item2.description
- });
+ e.Version),
+ Overview = e.Changelog
+ }).ConfigureAwait(false);
}
- private void OnPluginUninstalled(object sender, GenericEventArgs e)
+ private async void OnPluginUninstalled(object sender, IPlugin e)
{
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUninstalledWithName"),
- e.Argument.Name),
- Type = NotificationType.PluginUninstalled.ToString()
- });
+ e.Name),
+ NotificationType.PluginUninstalled.ToString(),
+ Guid.Empty))
+ .ConfigureAwait(false);
}
- private void OnPluginInstalled(object sender, GenericEventArgs e)
+ private async void OnPluginInstalled(object sender, InstallationInfo e)
{
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginInstalledWithName"),
- e.Argument.name),
- Type = NotificationType.PluginInstalled.ToString(),
+ e.Name),
+ NotificationType.PluginInstalled.ToString(),
+ Guid.Empty)
+ {
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
- e.Argument.versionStr)
- });
+ e.Version)
+ }).ConfigureAwait(false);
}
- private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
+ private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
{
var installationInfo = e.InstallationInfo;
- CreateLogEntry(new ActivityLogEntry
- {
- Name = string.Format(
+ await CreateLogEntry(new ActivityLog(
+ string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("NameInstallFailed"),
installationInfo.Name),
- Type = NotificationType.InstallationFailed.ToString(),
+ NotificationType.InstallationFailed.ToString(),
+ Guid.Empty)
+ {
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
installationInfo.Version),
Overview = e.Exception.Message
- });
+ }).ConfigureAwait(false);
}
- private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
+ private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
{
var result = e.Result;
var task = e.Task;
- var activityTask = task.ScheduledTask as IConfigurableScheduledTask;
- if (activityTask != null && !activityTask.IsLogged)
+ if (task.ScheduledTask is IConfigurableScheduledTask activityTask
+ && !activityTask.IsLogged)
{
return;
}
@@ -511,22 +464,20 @@ namespace Emby.Server.Implementations.Activity
vals.Add(e.Result.LongErrorMessage);
}
- CreateLogEntry(new ActivityLogEntry
+ await CreateLogEntry(new ActivityLog(
+ string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
+ NotificationType.TaskFailed.ToString(),
+ Guid.Empty)
{
- Name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("ScheduledTaskFailedWithName"),
- task.Name),
- Type = NotificationType.TaskFailed.ToString(),
+ LogSeverity = LogLevel.Error,
Overview = string.Join(Environment.NewLine, vals),
- ShortOverview = runningTime,
- Severity = LogLevel.Error
- });
+ ShortOverview = runningTime
+ }).ConfigureAwait(false);
}
}
- private void CreateLogEntry(ActivityLogEntry entry)
- => _activityManager.Create(entry);
+ private async Task CreateLogEntry(ActivityLog entry)
+ => await _activityManager.CreateAsync(entry).ConfigureAwait(false);
///
public void Dispose()
@@ -548,19 +499,16 @@ namespace Emby.Server.Implementations.Activity
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
- _userManager.UserCreated -= OnUserCreated;
- _userManager.UserPasswordChanged -= OnUserPasswordChanged;
- _userManager.UserDeleted -= OnUserDeleted;
- _userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
- _userManager.UserLockedOut -= OnUserLockedOut;
-
- _deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
+ _userManager.OnUserCreated -= OnUserCreated;
+ _userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
+ _userManager.OnUserDeleted -= OnUserDeleted;
+ _userManager.OnUserLockedOut -= OnUserLockedOut;
}
///
/// Constructs a user-friendly string for this TimeSpan instance.
///
- public static string ToUserFriendlyString(TimeSpan span)
+ private static string ToUserFriendlyString(TimeSpan span)
{
const int DaysInYear = 365;
const int DaysInMonth = 30;
@@ -574,7 +522,7 @@ namespace Emby.Server.Implementations.Activity
{
int years = days / DaysInYear;
values.Add(CreateValueString(years, "year"));
- days = days % DaysInYear;
+ days %= DaysInYear;
}
// Number of months
diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs
deleted file mode 100644
index ee10845cfa..0000000000
--- a/Emby.Server.Implementations/Activity/ActivityManager.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Activity;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Querying;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.Activity
-{
- public class ActivityManager : IActivityManager
- {
- public event EventHandler> EntryCreated;
-
- private readonly IActivityRepository _repo;
- private readonly ILogger _logger;
- private readonly IUserManager _userManager;
-
- public ActivityManager(
- ILoggerFactory loggerFactory,
- IActivityRepository repo,
- IUserManager userManager)
- {
- _logger = loggerFactory.CreateLogger(nameof(ActivityManager));
- _repo = repo;
- _userManager = userManager;
- }
-
- public void Create(ActivityLogEntry entry)
- {
- entry.Date = DateTime.UtcNow;
-
- _repo.Create(entry);
-
- EntryCreated?.Invoke(this, new GenericEventArgs(entry));
- }
-
- public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
- {
- var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
-
- foreach (var item in result.Items)
- {
- if (item.UserId == Guid.Empty)
- {
- continue;
- }
-
- var user = _userManager.GetUserById(item.UserId);
-
- if (user != null)
- {
- var dto = _userManager.GetUserDto(user);
- item.UserPrimaryImageTag = dto.PrimaryImageTag;
- }
- }
-
- return result;
- }
-
- public QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
- {
- return GetActivityLogEntries(minDate, null, startIndex, limit);
- }
- }
-}
diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs
deleted file mode 100644
index 7be72319ea..0000000000
--- a/Emby.Server.Implementations/Activity/ActivityRepository.cs
+++ /dev/null
@@ -1,313 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using Emby.Server.Implementations.Data;
-using MediaBrowser.Controller;
-using MediaBrowser.Model.Activity;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Querying;
-using Microsoft.Extensions.Logging;
-using SQLitePCL.pretty;
-
-namespace Emby.Server.Implementations.Activity
-{
- public class ActivityRepository : BaseSqliteRepository, IActivityRepository
- {
- private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
- private readonly IFileSystem _fileSystem;
-
- public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
- : base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
- {
- DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
- _fileSystem = fileSystem;
- }
-
- public void Initialize()
- {
- try
- {
- InitializeInternal();
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error loading database file. Will reset and retry.");
-
- _fileSystem.DeleteFile(DbFilePath);
-
- InitializeInternal();
- }
- }
-
- private void InitializeInternal()
- {
- using (var connection = GetConnection())
- {
- connection.RunQueries(new[]
- {
- "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
- "drop index if exists idx_ActivityLogEntries"
- });
-
- TryMigrate(connection);
- }
- }
-
- private void TryMigrate(ManagedConnection connection)
- {
- try
- {
- if (TableExists(connection, "ActivityLogEntries"))
- {
- connection.RunQueries(new[]
- {
- "INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries",
- "drop table if exists ActivityLogEntries"
- });
- }
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error migrating activity log database");
- }
- }
-
- private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
-
- public void Create(ActivityLogEntry entry)
- {
- if (entry == null)
- {
- throw new ArgumentNullException(nameof(entry));
- }
-
- using (var connection = GetConnection())
- {
- connection.RunInTransaction(db =>
- {
- using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
- {
- statement.TryBind("@Name", entry.Name);
-
- statement.TryBind("@Overview", entry.Overview);
- statement.TryBind("@ShortOverview", entry.ShortOverview);
- statement.TryBind("@Type", entry.Type);
- statement.TryBind("@ItemId", entry.ItemId);
-
- if (entry.UserId.Equals(Guid.Empty))
- {
- statement.TryBindNull("@UserId");
- }
- else
- {
- statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
- }
-
- statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
- statement.TryBind("@LogSeverity", entry.Severity.ToString());
-
- statement.MoveNext();
- }
- }, TransactionMode);
- }
- }
-
- public void Update(ActivityLogEntry entry)
- {
- if (entry == null)
- {
- throw new ArgumentNullException(nameof(entry));
- }
-
- using (var connection = GetConnection())
- {
- connection.RunInTransaction(db =>
- {
- using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
- {
- statement.TryBind("@Id", entry.Id);
-
- statement.TryBind("@Name", entry.Name);
- statement.TryBind("@Overview", entry.Overview);
- statement.TryBind("@ShortOverview", entry.ShortOverview);
- statement.TryBind("@Type", entry.Type);
- statement.TryBind("@ItemId", entry.ItemId);
-
- if (entry.UserId.Equals(Guid.Empty))
- {
- statement.TryBindNull("@UserId");
- }
- else
- {
- statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
- }
-
- statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
- statement.TryBind("@LogSeverity", entry.Severity.ToString());
-
- statement.MoveNext();
- }
- }, TransactionMode);
- }
- }
-
- public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
- {
- var commandText = BaseActivitySelectText;
- var whereClauses = new List();
-
- if (minDate.HasValue)
- {
- whereClauses.Add("DateCreated>=@DateCreated");
- }
- if (hasUserId.HasValue)
- {
- if (hasUserId.Value)
- {
- whereClauses.Add("UserId not null");
- }
- else
- {
- whereClauses.Add("UserId is null");
- }
- }
-
- var whereTextWithoutPaging = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
-
- if (startIndex.HasValue && startIndex.Value > 0)
- {
- var pagingWhereText = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
-
- whereClauses.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
- pagingWhereText,
- startIndex.Value));
- }
-
- var whereText = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
-
- commandText += whereText;
-
- commandText += " ORDER BY DateCreated DESC";
-
- if (limit.HasValue)
- {
- commandText += " LIMIT " + limit.Value.ToString(_usCulture);
- }
-
- var statementTexts = new[]
- {
- commandText,
- "select count (Id) from ActivityLog" + whereTextWithoutPaging
- };
-
- var list = new List();
- var result = new QueryResult();
-
- using (var connection = GetConnection(true))
- {
- connection.RunInTransaction(
- db =>
- {
- var statements = PrepareAll(db, statementTexts).ToList();
-
- using (var statement = statements[0])
- {
- if (minDate.HasValue)
- {
- statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
- }
-
- foreach (var row in statement.ExecuteQuery())
- {
- list.Add(GetEntry(row));
- }
- }
-
- using (var statement = statements[1])
- {
- if (minDate.HasValue)
- {
- statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
- }
-
- result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
- }
- },
- ReadTransactionMode);
- }
-
- result.Items = list;
- return result;
- }
-
- private static ActivityLogEntry GetEntry(IReadOnlyList reader)
- {
- var index = 0;
-
- var info = new ActivityLogEntry
- {
- Id = reader[index].ToInt64()
- };
-
- index++;
- if (reader[index].SQLiteType != SQLiteType.Null)
- {
- info.Name = reader[index].ToString();
- }
-
- index++;
- if (reader[index].SQLiteType != SQLiteType.Null)
- {
- info.Overview = reader[index].ToString();
- }
-
- index++;
- if (reader[index].SQLiteType != SQLiteType.Null)
- {
- info.ShortOverview = reader[index].ToString();
- }
-
- index++;
- if (reader[index].SQLiteType != SQLiteType.Null)
- {
- info.Type = reader[index].ToString();
- }
-
- index++;
- if (reader[index].SQLiteType != SQLiteType.Null)
- {
- info.ItemId = reader[index].ToString();
- }
-
- index++;
- if (reader[index].SQLiteType != SQLiteType.Null)
- {
- info.UserId = new Guid(reader[index].ToString());
- }
-
- index++;
- info.Date = reader[index].ReadDateTime();
-
- index++;
- if (reader[index].SQLiteType != SQLiteType.Null)
- {
- info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
- }
-
- return info;
- }
- }
-}
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
index bc47817438..2adc1d6c34 100644
--- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
+++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
@@ -15,6 +15,11 @@ namespace Emby.Server.Implementations.AppBase
///
/// Initializes a new instance of the class.
///
+ /// The program data path.
+ /// The log directory path.
+ /// The configuration directory path.
+ /// The cache directory path.
+ /// The web directory path.
protected BaseApplicationPaths(
string programDataPath,
string logDirectoryPath,
diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
index 080cfbbd1a..d4a8268b97 100644
--- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
+++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase
CommonApplicationPaths = applicationPaths;
XmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
- Logger = loggerFactory.CreateLogger(GetType().Name);
+ Logger = loggerFactory.CreateLogger();
UpdateCachePath();
}
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.AppBase
/// Gets the logger.
///
/// The logger.
- protected ILogger Logger { get; private set; }
+ protected ILogger Logger { get; private set; }
///
/// Gets the XML serializer.
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
index 854d7b4cbf..0b681fddfc 100644
--- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -36,24 +36,22 @@ namespace Emby.Server.Implementations.AppBase
configuration = Activator.CreateInstance(type);
}
- using (var stream = new MemoryStream())
- {
- xmlSerializer.SerializeToStream(configuration, stream);
-
- // Take the object we just got and serialize it back to bytes
- var newBytes = stream.ToArray();
+ using var stream = new MemoryStream();
+ xmlSerializer.SerializeToStream(configuration, stream);
- // If the file didn't exist before, or if something has changed, re-save
- if (buffer == null || !buffer.SequenceEqual(newBytes))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(path));
+ // Take the object we just got and serialize it back to bytes
+ var newBytes = stream.ToArray();
- // Save it after load in case we got new items
- File.WriteAllBytes(path, newBytes);
- }
+ // If the file didn't exist before, or if something has changed, re-save
+ if (buffer == null || !buffer.SequenceEqual(newBytes))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
- return configuration;
+ // Save it after load in case we got new items
+ File.WriteAllBytes(path, newBytes);
}
+
+ return configuration;
}
}
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index cb32b8c01b..45d1bb01c5 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -22,7 +22,6 @@ using Emby.Dlna.Ssdp;
using Emby.Drawing;
using Emby.Notifications;
using Emby.Photos;
-using Emby.Server.Implementations.Activity;
using Emby.Server.Implementations.Archiving;
using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections;
@@ -30,7 +29,6 @@ using Emby.Server.Implementations.Configuration;
using Emby.Server.Implementations.Cryptography;
using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Devices;
-using Emby.Server.Implementations.Diagnostics;
using Emby.Server.Implementations.Dto;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.HttpServer.Security;
@@ -45,7 +43,7 @@ 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.SyncPlay;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
using MediaBrowser.Api;
@@ -80,15 +78,13 @@ using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Subtitles;
+using MediaBrowser.Controller.SyncPlay;
using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
-using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
@@ -97,7 +93,6 @@ using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Tasks;
-using MediaBrowser.Model.Updates;
using MediaBrowser.Providers.Chapters;
using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Plugins.TheTvdb;
@@ -105,10 +100,9 @@ using MediaBrowser.Providers.Subtitles;
using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Extensions;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Prometheus.DotNetRuntime;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Server.Implementations
@@ -123,14 +117,20 @@ namespace Emby.Server.Implementations
///
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
- private SqliteUserRepository _userRepository;
- private SqliteDisplayPreferencesRepository _displayPreferencesRepository;
+ private readonly IFileSystem _fileSystemManager;
+ private readonly INetworkManager _networkManager;
+ private readonly IXmlSerializer _xmlSerializer;
+ private readonly IStartupOptions _startupOptions;
+
+ private IMediaEncoder _mediaEncoder;
+ private ISessionManager _sessionManager;
+ private IHttpServer _httpServer;
+ private IHttpClient _httpClient;
///
/// Gets a value indicating whether this instance can self restart.
///
- /// true if this instance can self restart; otherwise, false.
- public abstract bool CanSelfRestart { get; }
+ public bool CanSelfRestart => _startupOptions.RestartPath != null;
public virtual bool CanLaunchWebBrowser
{
@@ -141,7 +141,7 @@ namespace Emby.Server.Implementations
return false;
}
- if (StartupOptions.IsService)
+ if (_startupOptions.IsService)
{
return false;
}
@@ -173,7 +173,7 @@ namespace Emby.Server.Implementations
///
/// Gets the logger.
///
- protected ILogger Logger { get; }
+ protected ILogger Logger { get; }
private IPlugin[] _plugins;
@@ -192,7 +192,7 @@ namespace Emby.Server.Implementations
/// Gets or sets the application paths.
///
/// The application paths.
- protected ServerApplicationPaths ApplicationPaths { get; set; }
+ protected IServerApplicationPaths ApplicationPaths { get; set; }
///
/// Gets or sets all concrete types.
@@ -211,21 +211,6 @@ namespace Emby.Server.Implementations
/// The configuration manager.
protected IConfigurationManager ConfigurationManager { get; set; }
- public IFileSystem FileSystemManager { get; set; }
-
- ///
- public PackageVersionClass SystemUpdateLevel
- {
- get
- {
-#if BETA
- return PackageVersionClass.Beta;
-#else
- return PackageVersionClass.Release;
-#endif
- }
- }
-
///
/// Gets or sets the service provider.
///
@@ -247,143 +232,47 @@ namespace Emby.Server.Implementations
/// The server configuration manager.
public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager;
- ///
- /// Gets or sets the user manager.
- ///
- /// The user manager.
- public IUserManager UserManager { get; set; }
-
- ///
- /// Gets or sets the library manager.
- ///
- /// The library manager.
- internal ILibraryManager LibraryManager { get; set; }
-
- ///
- /// Gets or sets the directory watchers.
- ///
- /// The directory watchers.
- private ILibraryMonitor LibraryMonitor { get; set; }
-
- ///
- /// Gets or sets the provider manager.
- ///
- /// The provider manager.
- private IProviderManager ProviderManager { get; set; }
-
- ///
- /// Gets or sets the HTTP server.
- ///
- /// The HTTP server.
- private IHttpServer HttpServer { get; set; }
-
- private IDtoService DtoService { get; set; }
-
- public IImageProcessor ImageProcessor { get; set; }
-
- ///
- /// Gets or sets the media encoder.
- ///
- /// The media encoder.
- private IMediaEncoder MediaEncoder { get; set; }
-
- private ISubtitleEncoder SubtitleEncoder { get; set; }
-
- private ISessionManager SessionManager { get; set; }
-
- private ILiveTvManager LiveTvManager { get; set; }
-
- public LocalizationManager LocalizationManager { get; set; }
-
- private IEncodingManager EncodingManager { get; set; }
-
- private IChannelManager ChannelManager { get; set; }
-
- ///
- /// Gets or sets the user data repository.
- ///
- /// The user data repository.
- private IUserDataManager UserDataManager { get; set; }
-
- internal SqliteItemRepository ItemRepository { get; set; }
-
- private INotificationManager NotificationManager { get; set; }
-
- private ISubtitleManager SubtitleManager { get; set; }
-
- private IChapterManager ChapterManager { get; set; }
-
- private IDeviceManager DeviceManager { get; set; }
-
- internal IUserViewManager UserViewManager { get; set; }
-
- private IAuthenticationRepository AuthenticationRepository { get; set; }
-
- private ITVSeriesManager TVSeriesManager { get; set; }
-
- private ICollectionManager CollectionManager { get; set; }
-
- private IMediaSourceManager MediaSourceManager { get; set; }
-
- ///
- /// Gets the installation manager.
- ///
- /// The installation manager.
- protected IInstallationManager InstallationManager { get; private set; }
-
- protected IAuthService AuthService { get; private set; }
-
- public IStartupOptions StartupOptions { get; }
-
- internal IImageEncoder ImageEncoder { get; private set; }
-
- protected IProcessFactory ProcessFactory { get; private set; }
-
- protected readonly IXmlSerializer XmlSerializer;
-
- protected ISocketFactory SocketFactory { get; private set; }
-
- protected ITaskManager TaskManager { get; private set; }
-
- public IHttpClient HttpClient { get; private set; }
-
- protected INetworkManager NetworkManager { get; set; }
-
- public IJsonSerializer JsonSerializer { get; private set; }
-
- protected IIsoManager IsoManager { get; private set; }
-
///
/// Initializes a new instance of the class.
///
public ApplicationHost(
- ServerApplicationPaths applicationPaths,
+ IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
IStartupOptions options,
IFileSystem fileSystem,
- IImageEncoder imageEncoder,
INetworkManager networkManager)
{
- XmlSerializer = new MyXmlSerializer();
+ _xmlSerializer = new MyXmlSerializer();
- NetworkManager = networkManager;
+ _networkManager = networkManager;
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
ApplicationPaths = applicationPaths;
LoggerFactory = loggerFactory;
- FileSystemManager = fileSystem;
+ _fileSystemManager = fileSystem;
- ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager);
+ ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
- Logger = LoggerFactory.CreateLogger("App");
+ Logger = LoggerFactory.CreateLogger();
- StartupOptions = options;
+ _startupOptions = options;
- ImageEncoder = imageEncoder;
+ // Initialize runtime stat collection
+ if (ServerConfigurationManager.Configuration.EnableMetrics)
+ {
+ DotNetRuntimeStatsBuilder.Default().StartCollecting();
+ }
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
- NetworkManager.NetworkChanged += OnNetworkChanged;
+ _networkManager.NetworkChanged += OnNetworkChanged;
+
+ CertificateInfo = new CertificateInfo
+ {
+ Path = ServerConfigurationManager.Configuration.CertificatePath,
+ Password = ServerConfigurationManager.Configuration.CertificatePassword
+ };
+ Certificate = GetCertificate(CertificateInfo);
}
public string ExpandVirtualPath(string path)
@@ -451,10 +340,7 @@ namespace Emby.Server.Implementations
}
}
- ///
- /// Gets the name.
- ///
- /// The name.
+ ///
public string Name => ApplicationProductName;
///
@@ -544,7 +430,7 @@ namespace Emby.Server.Implementations
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
- MediaEncoder.SetFFmpegPath();
+ _mediaEncoder.SetFFmpegPath();
Logger.LogInformation("ServerId: {0}", SystemId);
@@ -556,7 +442,7 @@ namespace Emby.Server.Implementations
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
Logger.LogInformation("Core startup complete");
- HttpServer.GlobalResponse = null;
+ _httpServer.GlobalResponse = null;
stopWatch.Restart();
await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
@@ -580,7 +466,7 @@ namespace Emby.Server.Implementations
}
///
- public async Task InitAsync(IServiceCollection serviceCollection, IConfiguration startupConfig)
+ public void Init(IServiceCollection serviceCollection)
{
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@@ -592,20 +478,16 @@ namespace Emby.Server.Implementations
HttpsPort = ServerConfiguration.DefaultHttpsPort;
}
- JsonSerializer = new JsonSerializer();
-
if (Plugins != null)
{
var pluginBuilder = new StringBuilder();
foreach (var plugin in Plugins)
{
- pluginBuilder.AppendLine(
- string.Format(
- CultureInfo.InvariantCulture,
- "{0} {1}",
- plugin.Name,
- plugin.Version));
+ pluginBuilder.Append(plugin.Name)
+ .Append(' ')
+ .Append(plugin.Version)
+ .AppendLine();
}
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
@@ -613,41 +495,19 @@ namespace Emby.Server.Implementations
DiscoverTypes();
- await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false);
- }
-
- public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next)
- {
- if (!context.WebSockets.IsWebSocketRequest)
- {
- await next().ConfigureAwait(false);
- return;
- }
-
- await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
+ RegisterServices(serviceCollection);
}
- public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next)
- {
- if (context.WebSockets.IsWebSocketRequest)
- {
- await next().ConfigureAwait(false);
- return;
- }
-
- var request = context.Request;
- var response = context.Response;
- var localPath = context.Request.Path.ToString();
-
- var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger());
- await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
- }
+ public Task ExecuteHttpHandlerAsync(HttpContext context, Func next)
+ => _httpServer.RequestHandler(context);
///
/// Registers services/resources with the service collection that will be available via DI.
///
- protected async Task RegisterServices(IServiceCollection serviceCollection, IConfiguration startupConfig)
+ protected virtual void RegisterServices(IServiceCollection serviceCollection)
{
+ serviceCollection.AddSingleton(_startupOptions);
+
serviceCollection.AddMemoryCache();
serviceCollection.AddSingleton(ConfigurationManager);
@@ -655,236 +515,150 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(ApplicationPaths);
- serviceCollection.AddSingleton(JsonSerializer);
-
- // TODO: Support for injecting ILogger should be deprecated in favour of ILogger and this removed
- serviceCollection.AddSingleton(Logger);
+ serviceCollection.AddSingleton();
- serviceCollection.AddSingleton(FileSystemManager);
+ serviceCollection.AddSingleton(_fileSystemManager);
serviceCollection.AddSingleton();
- HttpClient = new HttpClientManager.HttpClientManager(
- ApplicationPaths,
- LoggerFactory.CreateLogger | | | |