Merge remote-tracking branch 'upstream/master' into external-id-type
This commit is contained in:
commit
0e9164351b
|
@ -33,6 +33,13 @@ jobs:
|
||||||
packageType: sdk
|
packageType: sdk
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
|
- task: DotNetCoreCLI@2
|
||||||
|
displayName: 'Install ABI CompatibilityChecker tool'
|
||||||
|
inputs:
|
||||||
|
command: custom
|
||||||
|
custom: tool
|
||||||
|
arguments: 'update compatibilitychecker -g'
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Download New Assembly Build Artifact"
|
displayName: "Download New Assembly Build Artifact"
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -72,25 +79,11 @@ jobs:
|
||||||
overWrite: true
|
overWrite: true
|
||||||
flattenFolders: 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.
|
# The `--warnings-only` switch will swallow the return code and not emit any errors.
|
||||||
- task: CmdLine@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: "Execute ABI Compatibility Check Tool"
|
displayName: 'Execute ABI Compatibility Check Tool'
|
||||||
inputs:
|
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)
|
workingDirectory: $(System.ArtifactsDirectory)
|
|
@ -1,6 +1,6 @@
|
||||||
parameters:
|
parameters:
|
||||||
LinuxImage: "ubuntu-latest"
|
LinuxImage: 'ubuntu-latest'
|
||||||
RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
|
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||||
DotNetSdkVersion: 3.1.100
|
DotNetSdkVersion: 3.1.100
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -13,7 +13,7 @@ jobs:
|
||||||
Debug:
|
Debug:
|
||||||
BuildConfiguration: Debug
|
BuildConfiguration: Debug
|
||||||
pool:
|
pool:
|
||||||
vmImage: "${{ parameters.LinuxImage }}"
|
vmImage: '${{ parameters.LinuxImage }}'
|
||||||
steps:
|
steps:
|
||||||
- checkout: self
|
- checkout: self
|
||||||
clean: true
|
clean: true
|
||||||
|
@ -21,7 +21,7 @@ jobs:
|
||||||
persistCredentials: true
|
persistCredentials: true
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Download Web Branch"
|
displayName: 'Download Web Branch'
|
||||||
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
|
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
|
||||||
inputs:
|
inputs:
|
||||||
path: '$(Agent.TempDirectory)'
|
path: '$(Agent.TempDirectory)'
|
||||||
|
@ -32,7 +32,7 @@ jobs:
|
||||||
runBranch: variables['Build.SourceBranch']
|
runBranch: variables['Build.SourceBranch']
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Download Web Target"
|
displayName: 'Download Web Target'
|
||||||
condition: eq(variables['Build.Reason'], 'PullRequest')
|
condition: eq(variables['Build.Reason'], 'PullRequest')
|
||||||
inputs:
|
inputs:
|
||||||
path: '$(Agent.TempDirectory)'
|
path: '$(Agent.TempDirectory)'
|
||||||
|
@ -43,51 +43,51 @@ jobs:
|
||||||
runBranch: variables['System.PullRequest.TargetBranch']
|
runBranch: variables['System.PullRequest.TargetBranch']
|
||||||
|
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
displayName: "Extract Web Client"
|
displayName: 'Extract Web Client'
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
|
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
|
||||||
destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
|
destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
|
||||||
cleanDestinationFolder: false
|
cleanDestinationFolder: false
|
||||||
|
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: "Update DotNet"
|
displayName: 'Update DotNet'
|
||||||
inputs:
|
inputs:
|
||||||
packageType: sdk
|
packageType: sdk
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: "Publish Server"
|
displayName: 'Publish Server'
|
||||||
inputs:
|
inputs:
|
||||||
command: publish
|
command: publish
|
||||||
publishWebProjects: false
|
publishWebProjects: false
|
||||||
projects: "${{ parameters.RestoreBuildProjects }}"
|
projects: '${{ parameters.RestoreBuildProjects }}'
|
||||||
arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)"
|
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
|
||||||
zipAfterPublish: false
|
zipAfterPublish: false
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@0
|
||||||
displayName: "Publish Artifact Naming"
|
displayName: 'Publish Artifact Naming'
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll"
|
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
|
||||||
artifactName: "Jellyfin.Naming"
|
artifactName: 'Jellyfin.Naming'
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@0
|
||||||
displayName: "Publish Artifact Controller"
|
displayName: 'Publish Artifact Controller'
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
|
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
|
||||||
artifactName: "Jellyfin.Controller"
|
artifactName: 'Jellyfin.Controller'
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@0
|
||||||
displayName: "Publish Artifact Model"
|
displayName: 'Publish Artifact Model'
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
|
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
|
||||||
artifactName: "Jellyfin.Model"
|
artifactName: 'Jellyfin.Model'
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@0
|
||||||
displayName: "Publish Artifact Common"
|
displayName: 'Publish Artifact Common'
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
|
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
|
||||||
artifactName: "Jellyfin.Common"
|
artifactName: 'Jellyfin.Common'
|
||||||
|
|
131
.ci/azure-pipelines-package.yml
Normal file
131
.ci/azure-pipelines-package.yml
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
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'
|
||||||
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), 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="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'
|
||||||
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
|
||||||
|
artifactName: 'jellyfin-server-$(BuildConfiguration)'
|
||||||
|
|
||||||
|
- task: CopyFilesOverSSH@0
|
||||||
|
displayName: 'Upload artifacts to repository server'
|
||||||
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||||
|
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'
|
||||||
|
|
||||||
|
- 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)'
|
|
@ -45,6 +45,7 @@ jobs:
|
||||||
- task: SonarCloudPrepare@1
|
- task: SonarCloudPrepare@1
|
||||||
displayName: 'Prepare analysis on SonarCloud'
|
displayName: 'Prepare analysis on SonarCloud'
|
||||||
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
enabled: false
|
||||||
inputs:
|
inputs:
|
||||||
SonarCloud: 'Sonarcloud for Jellyfin'
|
SonarCloud: 'Sonarcloud for Jellyfin'
|
||||||
organization: 'jellyfin'
|
organization: 'jellyfin'
|
||||||
|
@ -63,14 +64,17 @@ jobs:
|
||||||
- task: SonarCloudAnalyze@1
|
- task: SonarCloudAnalyze@1
|
||||||
displayName: 'Run Code Analysis'
|
displayName: 'Run Code Analysis'
|
||||||
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
enabled: false
|
||||||
|
|
||||||
- task: SonarCloudPublish@1
|
- task: SonarCloudPublish@1
|
||||||
displayName: 'Publish Quality Gate Result'
|
displayName: 'Publish Quality Gate Result'
|
||||||
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
enabled: false
|
||||||
|
|
||||||
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
- 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
|
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||||
displayName: 'Run ReportGenerator'
|
displayName: 'Run ReportGenerator'
|
||||||
|
enabled: false
|
||||||
inputs:
|
inputs:
|
||||||
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
||||||
targetdir: "$(Agent.TempDirectory)/merged/"
|
targetdir: "$(Agent.TempDirectory)/merged/"
|
||||||
|
@ -80,10 +84,10 @@ jobs:
|
||||||
- task: PublishCodeCoverageResults@1
|
- task: PublishCodeCoverageResults@1
|
||||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
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:
|
inputs:
|
||||||
codeCoverageTool: "cobertura"
|
codeCoverageTool: "cobertura"
|
||||||
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
||||||
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
||||||
pathToSources: $(Build.SourcesDirectory)
|
pathToSources: $(Build.SourcesDirectory)
|
||||||
failIfCoverageEmpty: true
|
failIfCoverageEmpty: true
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@ name: $(Date:yyyyMMdd)$(Rev:.r)
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
- name: TestProjects
|
- name: TestProjects
|
||||||
value: "tests/**/*Tests.csproj"
|
value: 'tests/**/*Tests.csproj'
|
||||||
- name: RestoreBuildProjects
|
- name: RestoreBuildProjects
|
||||||
value: "Jellyfin.Server/Jellyfin.Server.csproj"
|
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||||
- name: DotNetSdkVersion
|
- name: DotNetSdkVersion
|
||||||
value: 3.1.100
|
value: 3.1.100
|
||||||
|
|
||||||
|
@ -17,17 +17,17 @@ trigger:
|
||||||
jobs:
|
jobs:
|
||||||
- template: azure-pipelines-main.yml
|
- template: azure-pipelines-main.yml
|
||||||
parameters:
|
parameters:
|
||||||
LinuxImage: "ubuntu-latest"
|
LinuxImage: 'ubuntu-latest'
|
||||||
RestoreBuildProjects: $(RestoreBuildProjects)
|
RestoreBuildProjects: $(RestoreBuildProjects)
|
||||||
|
|
||||||
- template: azure-pipelines-test.yml
|
- template: azure-pipelines-test.yml
|
||||||
parameters:
|
parameters:
|
||||||
ImageNames:
|
ImageNames:
|
||||||
Linux: "ubuntu-latest"
|
Linux: 'ubuntu-latest'
|
||||||
Windows: "windows-latest"
|
Windows: 'windows-latest'
|
||||||
macOS: "macos-latest"
|
macOS: 'macos-latest'
|
||||||
|
|
||||||
- template: azure-pipelines-compat.yml
|
- template: azure-pipelines-abi.yml
|
||||||
parameters:
|
parameters:
|
||||||
Packages:
|
Packages:
|
||||||
Naming:
|
Naming:
|
||||||
|
@ -42,4 +42,6 @@ jobs:
|
||||||
Common:
|
Common:
|
||||||
NugetPackageName: Jellyfin.Common
|
NugetPackageName: Jellyfin.Common
|
||||||
AssemblyFileName: MediaBrowser.Common.dll
|
AssemblyFileName: MediaBrowser.Common.dll
|
||||||
LinuxImage: "ubuntu-latest"
|
LinuxImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
- template: azure-pipelines-package.yml
|
||||||
|
|
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: nuget
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
time: '12:00'
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -39,7 +39,6 @@ ProgramData*/
|
||||||
CorePlugins*/
|
CorePlugins*/
|
||||||
ProgramData-Server*/
|
ProgramData-Server*/
|
||||||
ProgramData-UI*/
|
ProgramData-UI*/
|
||||||
MediaBrowser.WebDashboard/jellyfin-web/**
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
## Visual Studio
|
## Visual Studio
|
||||||
|
@ -276,4 +275,4 @@ BenchmarkDotNet.Artifacts
|
||||||
# Ignore web artifacts from native builds
|
# Ignore web artifacts from native builds
|
||||||
web/
|
web/
|
||||||
web-src.*
|
web-src.*
|
||||||
MediaBrowser.WebDashboard/jellyfin-web/
|
MediaBrowser.WebDashboard/jellyfin-web
|
||||||
|
|
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
|
@ -1,9 +1,6 @@
|
||||||
{
|
{
|
||||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
"version": "0.2.0",
|
||||||
// Use hover for the description of the existing attributes
|
"configurations": [
|
||||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
{
|
||||||
"name": ".NET Core Launch (console)",
|
"name": ".NET Core Launch (console)",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
|
@ -24,5 +21,8 @@
|
||||||
"request": "attach",
|
"request": "attach",
|
||||||
"processId": "${command:pickProcess}"
|
"processId": "${command:pickProcess}"
|
||||||
}
|
}
|
||||||
,]
|
],
|
||||||
|
"env": {
|
||||||
|
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
19
.vscode/tasks.json
vendored
19
.vscode/tasks.json
vendored
|
@ -10,6 +10,21 @@
|
||||||
"${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj"
|
"${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj"
|
||||||
],
|
],
|
||||||
"problemMatcher": "$msCompile"
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
- [anthonylavado](https://github.com/anthonylavado)
|
- [anthonylavado](https://github.com/anthonylavado)
|
||||||
- [Artiume](https://github.com/Artiume)
|
- [Artiume](https://github.com/Artiume)
|
||||||
- [AThomsen](https://github.com/AThomsen)
|
- [AThomsen](https://github.com/AThomsen)
|
||||||
|
- [barronpm](https://github.com/barronpm)
|
||||||
- [bilde2910](https://github.com/bilde2910)
|
- [bilde2910](https://github.com/bilde2910)
|
||||||
- [bfayers](https://github.com/bfayers)
|
- [bfayers](https://github.com/bfayers)
|
||||||
- [BnMcG](https://github.com/BnMcG)
|
- [BnMcG](https://github.com/BnMcG)
|
||||||
|
@ -130,6 +131,7 @@
|
||||||
- [XVicarious](https://github.com/XVicarious)
|
- [XVicarious](https://github.com/XVicarious)
|
||||||
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
||||||
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
||||||
|
- [Pusta](https://github.com/pusta)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ ARG DOTNET_VERSION=3.1
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=master
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm \
|
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 - \
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& cd jellyfin-web-* \
|
&& cd jellyfin-web-* \
|
||||||
&& yarn install \
|
&& yarn install \
|
||||||
|
|
|
@ -38,7 +38,7 @@ COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
|
&& 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 -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 [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 && \
|
echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace DvdLib.Ifo
|
||||||
public class Cell
|
public class Cell
|
||||||
{
|
{
|
||||||
public CellPlaybackInfo PlaybackInfo { get; private set; }
|
public CellPlaybackInfo PlaybackInfo { get; private set; }
|
||||||
|
|
||||||
public CellPositionInfo PositionInfo { get; private set; }
|
public CellPositionInfo PositionInfo { get; private set; }
|
||||||
|
|
||||||
internal void ParsePlayback(BinaryReader br)
|
internal void ParsePlayback(BinaryReader br)
|
||||||
|
|
|
@ -5,7 +5,9 @@ namespace DvdLib.Ifo
|
||||||
public class Chapter
|
public class Chapter
|
||||||
{
|
{
|
||||||
public ushort ProgramChainNumber { get; private set; }
|
public ushort ProgramChainNumber { get; private set; }
|
||||||
|
|
||||||
public ushort ProgramNumber { get; private set; }
|
public ushort ProgramNumber { get; private set; }
|
||||||
|
|
||||||
public uint ChapterNumber { get; private set; }
|
public uint ChapterNumber { get; private set; }
|
||||||
|
|
||||||
public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)
|
public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)
|
||||||
|
|
|
@ -117,12 +117,19 @@ namespace DvdLib.Ifo
|
||||||
uint chapNum = 1;
|
uint chapNum = 1;
|
||||||
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
|
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
|
||||||
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
|
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
|
||||||
if (t == null) continue;
|
if (t == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
|
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++;
|
chapNum++;
|
||||||
}
|
}
|
||||||
while (vtsFs.Position < (baseAddr + endaddr));
|
while (vtsFs.Position < (baseAddr + endaddr));
|
||||||
|
@ -147,7 +154,10 @@ namespace DvdLib.Ifo
|
||||||
uint vtsPgcOffset = vtsRead.ReadUInt32();
|
uint vtsPgcOffset = vtsRead.ReadUInt32();
|
||||||
|
|
||||||
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,14 @@ namespace DvdLib.Ifo
|
||||||
Second = GetBCDValue(data[2]);
|
Second = GetBCDValue(data[2]);
|
||||||
Frames = GetBCDValue((byte)(data[3] & 0x3F));
|
Frames = GetBCDValue((byte)(data[3] & 0x3F));
|
||||||
|
|
||||||
if ((data[3] & 0x80) != 0) FrameRate = 30;
|
if ((data[3] & 0x80) != 0)
|
||||||
else if ((data[3] & 0x40) != 0) FrameRate = 25;
|
{
|
||||||
|
FrameRate = 30;
|
||||||
|
}
|
||||||
|
else if ((data[3] & 0x40) != 0)
|
||||||
|
{
|
||||||
|
FrameRate = 25;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte GetBCDValue(byte data)
|
private static byte GetBCDValue(byte data)
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace DvdLib.Ifo
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
public readonly List<Cell> Cells;
|
public IReadOnlyList<Cell> Cells { get; }
|
||||||
|
|
||||||
public Program(List<Cell> cells)
|
public Program(List<Cell> cells)
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,9 @@ namespace DvdLib.Ifo
|
||||||
public readonly List<Cell> Cells;
|
public readonly List<Cell> Cells;
|
||||||
|
|
||||||
public DvdTime PlaybackTime { get; private set; }
|
public DvdTime PlaybackTime { get; private set; }
|
||||||
|
|
||||||
public UserOperation ProhibitedUserOperations { get; private set; }
|
public UserOperation ProhibitedUserOperations { get; private set; }
|
||||||
|
|
||||||
public byte[] AudioStreamControl { get; private set; } // 8*2 entries
|
public byte[] AudioStreamControl { get; private set; } // 8*2 entries
|
||||||
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
|
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
|
||||||
|
|
||||||
|
@ -33,9 +35,11 @@ namespace DvdLib.Ifo
|
||||||
private ushort _goupProgramNumber;
|
private ushort _goupProgramNumber;
|
||||||
|
|
||||||
public ProgramPlaybackMode PlaybackMode { get; private set; }
|
public ProgramPlaybackMode PlaybackMode { get; private set; }
|
||||||
|
|
||||||
public uint ProgramCount { get; private set; }
|
public uint ProgramCount { get; private set; }
|
||||||
|
|
||||||
public byte StillTime { get; private set; }
|
public byte StillTime { get; private set; }
|
||||||
|
|
||||||
public byte[] Palette { get; private set; } // 16*4 entries
|
public byte[] Palette { get; private set; } // 16*4 entries
|
||||||
|
|
||||||
private ushort _commandTableOffset;
|
private ushort _commandTableOffset;
|
||||||
|
@ -71,8 +75,15 @@ namespace DvdLib.Ifo
|
||||||
|
|
||||||
StillTime = br.ReadByte();
|
StillTime = br.ReadByte();
|
||||||
byte pbMode = br.ReadByte();
|
byte pbMode = br.ReadByte();
|
||||||
if (pbMode == 0) PlaybackMode = ProgramPlaybackMode.Sequential;
|
if (pbMode == 0)
|
||||||
else PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
|
{
|
||||||
|
PlaybackMode = ProgramPlaybackMode.Sequential;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
|
||||||
|
}
|
||||||
|
|
||||||
ProgramCount = (uint)(pbMode & 0x7F);
|
ProgramCount = (uint)(pbMode & 0x7F);
|
||||||
|
|
||||||
Palette = br.ReadBytes(64);
|
Palette = br.ReadBytes(64);
|
||||||
|
|
|
@ -8,8 +8,11 @@ namespace DvdLib.Ifo
|
||||||
public class Title
|
public class Title
|
||||||
{
|
{
|
||||||
public uint TitleNumber { get; private set; }
|
public uint TitleNumber { get; private set; }
|
||||||
|
|
||||||
public uint AngleCount { get; private set; }
|
public uint AngleCount { get; private set; }
|
||||||
|
|
||||||
public ushort ChapterCount { get; private set; }
|
public ushort ChapterCount { get; private set; }
|
||||||
|
|
||||||
public byte VideoTitleSetNumber { get; private set; }
|
public byte VideoTitleSetNumber { get; private set; }
|
||||||
|
|
||||||
private ushort _parentalManagementMask;
|
private ushort _parentalManagementMask;
|
||||||
|
@ -17,6 +20,7 @@ namespace DvdLib.Ifo
|
||||||
private uint _vtsStartSector; // relative to start of entire disk
|
private uint _vtsStartSector; // relative to start of entire disk
|
||||||
|
|
||||||
public ProgramChain EntryProgramChain { get; private set; }
|
public ProgramChain EntryProgramChain { get; private set; }
|
||||||
|
|
||||||
public readonly List<ProgramChain> ProgramChains;
|
public readonly List<ProgramChain> ProgramChains;
|
||||||
|
|
||||||
public readonly List<Chapter> Chapters;
|
public readonly List<Chapter> Chapters;
|
||||||
|
@ -55,7 +59,10 @@ namespace DvdLib.Ifo
|
||||||
var pgc = new ProgramChain(pgcNum);
|
var pgc = new ProgramChain(pgcNum);
|
||||||
pgc.ParseHeader(br);
|
pgc.ParseHeader(br);
|
||||||
ProgramChains.Add(pgc);
|
ProgramChains.Add(pgc);
|
||||||
if (entryPgc) EntryProgramChain = pgc;
|
if (entryPgc)
|
||||||
|
{
|
||||||
|
EntryProgramChain = pgc;
|
||||||
|
}
|
||||||
|
|
||||||
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
|
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.TV;
|
using MediaBrowser.Controller.TV;
|
||||||
|
@ -31,7 +33,8 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly ITVSeriesManager _tvSeriesManager;
|
private readonly ITVSeriesManager _tvSeriesManager;
|
||||||
|
|
||||||
public ContentDirectory(IDlnaManager dlna,
|
public ContentDirectory(
|
||||||
|
IDlnaManager dlna,
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
IImageProcessor imageProcessor,
|
IImageProcessor imageProcessor,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
|
@ -130,18 +133,13 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
foreach (var user in _userManager.Users)
|
foreach (var user in _userManager.Users)
|
||||||
{
|
{
|
||||||
if (user.Policy.IsAdministrator)
|
if (user.HasPermission(PermissionKind.IsAdministrator))
|
||||||
{
|
{
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var user in _userManager.Users)
|
return _userManager.Users.FirstOrDefault();
|
||||||
{
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ using System.Threading;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
@ -17,7 +18,6 @@ using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
@ -28,6 +28,12 @@ using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using Microsoft.Extensions.Logging;
|
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
|
namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
|
@ -460,12 +466,12 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
}
|
}
|
||||||
else if (search.SearchType == SearchType.Playlist)
|
else if (search.SearchType == SearchType.Playlist)
|
||||||
{
|
{
|
||||||
//items = items.OfType<Playlist>();
|
// items = items.OfType<Playlist>();
|
||||||
isFolder = true;
|
isFolder = true;
|
||||||
}
|
}
|
||||||
else if (search.SearchType == SearchType.MusicAlbum)
|
else if (search.SearchType == SearchType.MusicAlbum)
|
||||||
{
|
{
|
||||||
//items = items.OfType<MusicAlbum>();
|
// items = items.OfType<MusicAlbum>();
|
||||||
isFolder = true;
|
isFolder = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -731,7 +737,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
return GetGenres(item, user, query);
|
return GetGenres(item, user, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var array = new ServerItem[]
|
var array = new[]
|
||||||
{
|
{
|
||||||
new ServerItem(item)
|
new ServerItem(item)
|
||||||
{
|
{
|
||||||
|
@ -920,7 +926,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.Recursive = true;
|
query.Recursive = true;
|
||||||
//query.Parent = parent;
|
// query.Parent = parent;
|
||||||
query.SetUser(user);
|
query.SetUser(user);
|
||||||
|
|
||||||
query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
|
query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
|
||||||
|
@ -1115,7 +1121,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.Parent = null;
|
query.Parent = null;
|
||||||
query.IncludeItemTypes = new[] { typeof(Playlist).Name };
|
query.IncludeItemTypes = new[] { nameof(Playlist) };
|
||||||
query.SetUser(user);
|
query.SetUser(user);
|
||||||
query.Recursive = true;
|
query.Recursive = true;
|
||||||
|
|
||||||
|
@ -1132,10 +1138,9 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
Limit = 50,
|
Limit = 50,
|
||||||
IncludeItemTypes = new[] { typeof(Audio).Name },
|
IncludeItemTypes = new[] { nameof(Audio) },
|
||||||
ParentId = parent == null ? Guid.Empty : parent.Id,
|
ParentId = parent?.Id ?? Guid.Empty,
|
||||||
GroupItems = true
|
GroupItems = true
|
||||||
|
|
||||||
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
|
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
|
||||||
|
|
||||||
return ToResult(items);
|
return ToResult(items);
|
||||||
|
@ -1150,7 +1155,6 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
Limit = query.Limit,
|
Limit = query.Limit,
|
||||||
StartIndex = query.StartIndex,
|
StartIndex = query.StartIndex,
|
||||||
UserId = query.User.Id
|
UserId = query.User.Id
|
||||||
|
|
||||||
}, new[] { parent }, query.DtoOptions);
|
}, new[] { parent }, query.DtoOptions);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(result);
|
||||||
|
@ -1167,7 +1171,6 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||||
ParentId = parent == null ? Guid.Empty : parent.Id,
|
ParentId = parent == null ? Guid.Empty : parent.Id,
|
||||||
GroupItems = false
|
GroupItems = false
|
||||||
|
|
||||||
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
|
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
|
||||||
|
|
||||||
return ToResult(items);
|
return ToResult(items);
|
||||||
|
@ -1177,14 +1180,14 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||||
|
|
||||||
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
|
var items = _userViewManager.GetLatestItems(
|
||||||
|
new LatestItemsQuery
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
Limit = 50,
|
Limit = 50,
|
||||||
IncludeItemTypes = new[] { typeof(Movie).Name },
|
IncludeItemTypes = new[] { nameof(Movie) },
|
||||||
ParentId = parent == null ? Guid.Empty : parent.Id,
|
ParentId = parent?.Id ?? Guid.Empty,
|
||||||
GroupItems = true
|
GroupItems = true
|
||||||
|
|
||||||
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
|
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
|
||||||
|
|
||||||
return ToResult(items);
|
return ToResult(items);
|
||||||
|
@ -1217,7 +1220,11 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
ParentId = parentId,
|
ParentId = parentId,
|
||||||
GenreIds = new[] { item.Id },
|
GenreIds = new[] { item.Id },
|
||||||
IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name },
|
IncludeItemTypes = new[]
|
||||||
|
{
|
||||||
|
nameof(Movie),
|
||||||
|
nameof(Series)
|
||||||
|
},
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
DtoOptions = GetDtoOptions()
|
DtoOptions = GetDtoOptions()
|
||||||
|
@ -1350,6 +1357,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
internal class ServerItem
|
internal class ServerItem
|
||||||
{
|
{
|
||||||
public BaseItem Item { get; set; }
|
public BaseItem Item { get; set; }
|
||||||
|
|
||||||
public StubType? StubType { get; set; }
|
public StubType? StubType { get; set; }
|
||||||
|
|
||||||
public ServerItem(BaseItem item)
|
public ServerItem(BaseItem item)
|
||||||
|
|
|
@ -6,14 +6,13 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Emby.Dlna.Configuration;
|
|
||||||
using Emby.Dlna.ContentDirectory;
|
using Emby.Dlna.ContentDirectory;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Playlists;
|
using MediaBrowser.Controller.Playlists;
|
||||||
|
@ -23,6 +22,13 @@ using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using Microsoft.Extensions.Logging;
|
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
|
namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
|
@ -92,21 +98,21 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
using (var writer = XmlWriter.Create(builder, settings))
|
using (var writer = XmlWriter.Create(builder, settings))
|
||||||
{
|
{
|
||||||
//writer.WriteStartDocument();
|
// writer.WriteStartDocument();
|
||||||
|
|
||||||
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
||||||
|
|
||||||
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
||||||
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
||||||
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
||||||
//didl.SetAttribute("xmlns:sec", NS_SEC);
|
// didl.SetAttribute("xmlns:sec", NS_SEC);
|
||||||
|
|
||||||
WriteXmlRootAttributes(_profile, writer);
|
WriteXmlRootAttributes(_profile, writer);
|
||||||
|
|
||||||
WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
|
WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
//writer.WriteEndDocument();
|
// writer.WriteEndDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
|
@ -421,7 +427,6 @@ namespace Emby.Dlna.Didl
|
||||||
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
|
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
|
||||||
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
|
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
|
||||||
case StubType.Series: return _localization.GetLocalizedString("Shows");
|
case StubType.Series: return _localization.GetLocalizedString("Shows");
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -670,7 +675,7 @@ namespace Emby.Dlna.Didl
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null;
|
XmlAttribute secAttribute = null;
|
||||||
foreach (var attribute in _profile.XmlRootAttributes)
|
foreach (var attribute in _profile.XmlRootAttributes)
|
||||||
{
|
{
|
||||||
if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -700,13 +705,13 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds fields used by both items and folders
|
/// Adds fields used by both items and folders.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
|
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
|
// 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
|
// 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);
|
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
|
||||||
}
|
}
|
||||||
|
@ -745,7 +750,7 @@ namespace Emby.Dlna.Didl
|
||||||
AddValue(writer, "dc", "description", desc, NS_DC);
|
AddValue(writer, "dc", "description", desc, NS_DC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if (filter.Contains("upnp:longDescription"))
|
// if (filter.Contains("upnp:longDescription"))
|
||||||
//{
|
//{
|
||||||
// if (!string.IsNullOrWhiteSpace(item.Overview))
|
// if (!string.IsNullOrWhiteSpace(item.Overview))
|
||||||
// {
|
// {
|
||||||
|
@ -760,6 +765,7 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
|
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.Contains("upnp:rating"))
|
if (filter.Contains("upnp:rating"))
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
|
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
|
||||||
|
@ -995,7 +1001,6 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
|
|
||||||
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
|
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddImageResElement(
|
private void AddImageResElement(
|
||||||
|
@ -1048,10 +1053,12 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
return GetImageInfo(item, ImageType.Primary);
|
return GetImageInfo(item, ImageType.Primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.HasImage(ImageType.Thumb))
|
if (item.HasImage(ImageType.Thumb))
|
||||||
{
|
{
|
||||||
return GetImageInfo(item, ImageType.Thumb);
|
return GetImageInfo(item, ImageType.Thumb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.HasImage(ImageType.Backdrop))
|
if (item.HasImage(ImageType.Backdrop))
|
||||||
{
|
{
|
||||||
if (item is Channel)
|
if (item is Channel)
|
||||||
|
@ -1131,25 +1138,24 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
if (width == 0 || height == 0)
|
if (width == 0 || height == 0)
|
||||||
{
|
{
|
||||||
//_imageProcessor.GetImageSize(item, imageInfo);
|
// _imageProcessor.GetImageSize(item, imageInfo);
|
||||||
width = null;
|
width = null;
|
||||||
height = null;
|
height = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width == -1 || height == -1)
|
else if (width == -1 || height == -1)
|
||||||
{
|
{
|
||||||
width = null;
|
width = null;
|
||||||
height = null;
|
height = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//try
|
// try
|
||||||
//{
|
//{
|
||||||
// var size = _imageProcessor.GetImageSize(imageInfo);
|
// var size = _imageProcessor.GetImageSize(imageInfo);
|
||||||
|
|
||||||
// width = size.Width;
|
// width = size.Width;
|
||||||
// height = size.Height;
|
// height = size.Height;
|
||||||
//}
|
//}
|
||||||
//catch
|
// catch
|
||||||
//{
|
//{
|
||||||
|
|
||||||
//}
|
//}
|
||||||
|
|
|
@ -12,7 +12,6 @@ namespace Emby.Dlna.Didl
|
||||||
public Filter()
|
public Filter()
|
||||||
: this("*")
|
: this("*")
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Filter(string filter)
|
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.
|
// 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 true;
|
||||||
//return _all || ListHelper.ContainsIgnoreCase(_fields, field);
|
// return _all || ListHelper.ContainsIgnoreCase(_fields, field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace Emby.Dlna
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private readonly IXmlSerializer _xmlSerializer;
|
private readonly IXmlSerializer _xmlSerializer;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<DlnaManager> _logger;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
|
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
|
||||||
|
@ -49,7 +49,7 @@ namespace Emby.Dlna
|
||||||
_xmlSerializer = xmlSerializer;
|
_xmlSerializer = xmlSerializer;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_logger = loggerFactory.CreateLogger("Dlna");
|
_logger = loggerFactory.CreateLogger<DlnaManager>();
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,6 @@ namespace Emby.Dlna
|
||||||
.Select(i => i.Item2)
|
.Select(i => i.Item2)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceProfile GetDefaultProfile()
|
public DeviceProfile GetDefaultProfile()
|
||||||
|
@ -141,55 +140,73 @@ namespace Emby.Dlna
|
||||||
if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
|
if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
|
||||||
{
|
{
|
||||||
if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
|
if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
|
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
|
||||||
{
|
{
|
||||||
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
|
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
|
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
|
||||||
{
|
{
|
||||||
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
|
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
|
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
|
||||||
{
|
{
|
||||||
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
|
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
|
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
|
||||||
{
|
{
|
||||||
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
|
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ModelName))
|
if (!string.IsNullOrEmpty(profileInfo.ModelName))
|
||||||
{
|
{
|
||||||
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
|
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
|
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
|
||||||
{
|
{
|
||||||
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
|
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
|
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
|
||||||
{
|
{
|
||||||
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
|
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
|
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
|
||||||
{
|
{
|
||||||
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
|
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -251,7 +268,7 @@ namespace Emby.Dlna
|
||||||
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
|
||||||
case HeaderMatchType.Substring:
|
case HeaderMatchType.Substring:
|
||||||
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
|
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;
|
return isMatch;
|
||||||
case HeaderMatchType.Regex:
|
case HeaderMatchType.Regex:
|
||||||
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
|
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
|
||||||
|
@ -439,6 +456,7 @@ namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Profile is missing Id");
|
throw new ArgumentException("Profile is missing Id");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(profile.Name))
|
if (string.IsNullOrEmpty(profile.Name))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Profile is missing Name");
|
throw new ArgumentException("Profile is missing Name");
|
||||||
|
@ -464,6 +482,7 @@ namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
SerializeToXml(profile, path);
|
SerializeToXml(profile, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,7 +493,7 @@ namespace Emby.Dlna
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recreates the object using serialization, to ensure it's not a subclass.
|
/// 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).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="profile"></param>
|
/// <param name="profile"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
@ -493,6 +512,7 @@ namespace Emby.Dlna
|
||||||
class InternalProfileInfo
|
class InternalProfileInfo
|
||||||
{
|
{
|
||||||
internal DeviceProfileInfo Info { get; set; }
|
internal DeviceProfileInfo Info { get; set; }
|
||||||
|
|
||||||
internal string Path { get; set; }
|
internal string Path { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,9 +586,9 @@ namespace Emby.Dlna
|
||||||
new Foobar2000Profile(),
|
new Foobar2000Profile(),
|
||||||
new SharpSmartTvProfile(),
|
new SharpSmartTvProfile(),
|
||||||
new MediaMonkeyProfile(),
|
new MediaMonkeyProfile(),
|
||||||
//new Windows81Profile(),
|
// new Windows81Profile(),
|
||||||
//new WindowsMediaCenterProfile(),
|
// new WindowsMediaCenterProfile(),
|
||||||
//new WindowsPhoneProfile(),
|
// new WindowsPhoneProfile(),
|
||||||
new DirectTvProfile(),
|
new DirectTvProfile(),
|
||||||
new DishHopperJoeyProfile(),
|
new DishHopperJoeyProfile(),
|
||||||
new DefaultProfile(),
|
new DefaultProfile(),
|
||||||
|
|
|
@ -31,18 +31,26 @@ namespace Emby.Dlna.Eventing
|
||||||
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
|
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
|
||||||
{
|
{
|
||||||
var subscription = GetSubscription(subscriptionId, false);
|
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;
|
_logger.LogDebug(
|
||||||
int timeoutSeconds = subscription.TimeoutSeconds;
|
"Renewing event subscription for {0} with timeout of {1} to {2}",
|
||||||
subscription.SubscriptionTime = DateTime.UtcNow;
|
subscription.NotificationType,
|
||||||
|
timeoutSeconds,
|
||||||
|
subscription.CallbackUrl);
|
||||||
|
|
||||||
_logger.LogDebug(
|
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
|
||||||
"Renewing event subscription for {0} with timeout of {1} to {2}",
|
}
|
||||||
subscription.NotificationType,
|
|
||||||
timeoutSeconds,
|
|
||||||
subscription.CallbackUrl);
|
|
||||||
|
|
||||||
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
|
return new EventSubscriptionResponse
|
||||||
|
{
|
||||||
|
Content = string.Empty,
|
||||||
|
ContentType = "text/plain"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
||||||
|
@ -150,6 +158,7 @@ namespace Emby.Dlna.Eventing
|
||||||
builder.Append("</" + key + ">");
|
builder.Append("</" + key + ">");
|
||||||
builder.Append("</e:property>");
|
builder.Append("</e:property>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("</e:propertyset>");
|
builder.Append("</e:propertyset>");
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
var options = new HttpRequestOptions
|
||||||
|
@ -169,7 +178,6 @@ namespace Emby.Dlna.Eventing
|
||||||
{
|
{
|
||||||
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
|
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
|
|
|
@ -7,10 +7,13 @@ namespace Emby.Dlna.Eventing
|
||||||
public class EventSubscription
|
public class EventSubscription
|
||||||
{
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
public string CallbackUrl { get; set; }
|
public string CallbackUrl { get; set; }
|
||||||
|
|
||||||
public string NotificationType { get; set; }
|
public string NotificationType { get; set; }
|
||||||
|
|
||||||
public DateTime SubscriptionTime { get; set; }
|
public DateTime SubscriptionTime { get; set; }
|
||||||
|
|
||||||
public int TimeoutSeconds { get; set; }
|
public int TimeoutSeconds { get; set; }
|
||||||
|
|
||||||
public long TriggerCount { get; set; }
|
public long TriggerCount { get; set; }
|
||||||
|
|
|
@ -33,10 +33,8 @@ namespace Emby.Dlna.Main
|
||||||
public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
|
public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
|
||||||
{
|
{
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<DlnaEntryPoint> _logger;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
|
||||||
private PlayToManager _manager;
|
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
@ -47,14 +45,13 @@ namespace Emby.Dlna.Main
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
|
||||||
private SsdpDevicePublisher _Publisher;
|
|
||||||
|
|
||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
|
private readonly object _syncLock = new object();
|
||||||
|
|
||||||
|
private PlayToManager _manager;
|
||||||
|
private SsdpDevicePublisher _publisher;
|
||||||
private ISsdpCommunicationsServer _communicationsServer;
|
private ISsdpCommunicationsServer _communicationsServer;
|
||||||
|
|
||||||
internal IContentDirectory ContentDirectory { get; private set; }
|
internal IContentDirectory ContentDirectory { get; private set; }
|
||||||
|
@ -65,7 +62,8 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
public static DlnaEntryPoint Current;
|
public static DlnaEntryPoint Current;
|
||||||
|
|
||||||
public DlnaEntryPoint(IServerConfigurationManager config,
|
public DlnaEntryPoint(
|
||||||
|
IServerConfigurationManager config,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
|
@ -99,7 +97,7 @@ namespace Emby.Dlna.Main
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_socketFactory = socketFactory;
|
_socketFactory = socketFactory;
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
_logger = loggerFactory.CreateLogger("Dlna");
|
_logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
|
||||||
|
|
||||||
ContentDirectory = new ContentDirectory.ContentDirectory(
|
ContentDirectory = new ContentDirectory.ContentDirectory(
|
||||||
dlnaManager,
|
dlnaManager,
|
||||||
|
@ -133,20 +131,20 @@ namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
|
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))
|
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();
|
var options = _config.GetDlnaConfiguration();
|
||||||
|
|
||||||
|
@ -180,7 +178,7 @@ namespace Emby.Dlna.Main
|
||||||
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
|
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
|
||||||
OperatingSystem.Id == OperatingSystemId.Linux;
|
OperatingSystem.Id == OperatingSystemId.Linux;
|
||||||
|
|
||||||
_communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
||||||
{
|
{
|
||||||
IsShared = true
|
IsShared = true
|
||||||
};
|
};
|
||||||
|
@ -231,20 +229,22 @@ namespace Emby.Dlna.Main
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_Publisher != null)
|
if (_publisher != null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
|
_publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost)
|
||||||
_Publisher.LogFunction = LogMessage;
|
{
|
||||||
_Publisher.SupportPnpRootDevice = false;
|
LogFunction = LogMessage,
|
||||||
|
SupportPnpRootDevice = false
|
||||||
|
};
|
||||||
|
|
||||||
await RegisterServerEndpoints().ConfigureAwait(false);
|
await RegisterServerEndpoints().ConfigureAwait(false);
|
||||||
|
|
||||||
_Publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
|
_publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -266,6 +266,12 @@ namespace Emby.Dlna.Main
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Limit to LAN addresses only
|
||||||
|
if (!_networkManager.IsAddressInSubnets(address, true, true))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||||
|
|
||||||
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
|
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
|
||||||
|
@ -275,7 +281,7 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
var device = new SsdpRootDevice
|
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.
|
Location = uri, // Must point to the URL that serves your devices UPnP description document.
|
||||||
Address = address,
|
Address = address,
|
||||||
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
|
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
|
||||||
|
@ -287,13 +293,13 @@ namespace Emby.Dlna.Main
|
||||||
};
|
};
|
||||||
|
|
||||||
SetProperies(device, fullService);
|
SetProperies(device, fullService);
|
||||||
_Publisher.AddDevice(device);
|
_publisher.AddDevice(device);
|
||||||
|
|
||||||
var embeddedDevices = new[]
|
var embeddedDevices = new[]
|
||||||
{
|
{
|
||||||
"urn:schemas-upnp-org:service:ContentDirectory:1",
|
"urn:schemas-upnp-org:service:ContentDirectory:1",
|
||||||
"urn:schemas-upnp-org:service:ConnectionManager: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)
|
foreach (var subDevice in embeddedDevices)
|
||||||
|
@ -319,12 +325,13 @@ namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
guid = text.GetMD5();
|
guid = text.GetMD5();
|
||||||
}
|
}
|
||||||
|
|
||||||
return guid.ToString("N", CultureInfo.InvariantCulture);
|
return guid.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
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(':');
|
var serviceParts = service.Split(':');
|
||||||
|
|
||||||
|
@ -335,7 +342,6 @@ namespace Emby.Dlna.Main
|
||||||
device.DeviceType = serviceParts[2];
|
device.DeviceType = serviceParts[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object _syncLock = new object();
|
|
||||||
private void StartPlayToManager()
|
private void StartPlayToManager()
|
||||||
{
|
{
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
|
@ -347,7 +353,8 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_manager = new PlayToManager(_logger,
|
_manager = new PlayToManager(
|
||||||
|
_logger,
|
||||||
_sessionManager,
|
_sessionManager,
|
||||||
_libraryManager,
|
_libraryManager,
|
||||||
_userManager,
|
_userManager,
|
||||||
|
@ -386,6 +393,7 @@ namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error disposing PlayTo manager");
|
_logger.LogError(ex, "Error disposing PlayTo manager");
|
||||||
}
|
}
|
||||||
|
|
||||||
_manager = null;
|
_manager = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,11 +420,11 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
public void DisposeDevicePublisher()
|
public void DisposeDevicePublisher()
|
||||||
{
|
{
|
||||||
if (_Publisher != null)
|
if (_publisher != null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Disposing SsdpDevicePublisher");
|
_logger.LogInformation("Disposing SsdpDevicePublisher");
|
||||||
_Publisher.Dispose();
|
_publisher.Dispose();
|
||||||
_Publisher = null;
|
_publisher = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class Device : IDisposable
|
public class Device : IDisposable
|
||||||
{
|
{
|
||||||
#region Fields & Properties
|
|
||||||
|
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
|
|
||||||
public DeviceInfo Properties { get; set; }
|
public DeviceInfo Properties { get; set; }
|
||||||
|
@ -34,9 +32,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
RefreshVolumeIfNeeded();
|
RefreshVolumeIfNeeded().GetAwaiter().GetResult();
|
||||||
return _volume;
|
return _volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
set => _volume = value;
|
set => _volume = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,10 +51,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED;
|
public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED;
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
public Action OnDeviceUnavailable { get; set; }
|
public Action OnDeviceUnavailable { get; set; }
|
||||||
|
@ -76,24 +75,24 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
private DateTime _lastVolumeRefresh;
|
private DateTime _lastVolumeRefresh;
|
||||||
private bool _volumeRefreshActive;
|
private bool _volumeRefreshActive;
|
||||||
private void RefreshVolumeIfNeeded()
|
private Task RefreshVolumeIfNeeded()
|
||||||
{
|
{
|
||||||
if (!_volumeRefreshActive)
|
if (_volumeRefreshActive
|
||||||
{
|
&& DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
|
|
||||||
{
|
{
|
||||||
_lastVolumeRefresh = DateTime.UtcNow;
|
_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)
|
if (_disposed)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -141,8 +140,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Commanding
|
|
||||||
|
|
||||||
public Task VolumeDown(CancellationToken cancellationToken)
|
public Task VolumeDown(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var sendVolume = Math.Max(Volume - 5, 0);
|
var sendVolume = Math.Max(Volume - 5, 0);
|
||||||
|
@ -211,7 +208,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
|
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var service = GetServiceRenderingControl();
|
var service = GetServiceRenderingControl();
|
||||||
|
|
||||||
|
@ -232,7 +231,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets volume on a scale of 0-100
|
/// Sets volume on a scale of 0-100.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task SetVolume(int value, CancellationToken cancellationToken)
|
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");
|
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var service = GetServiceRenderingControl();
|
var service = GetServiceRenderingControl();
|
||||||
|
|
||||||
|
@ -263,7 +264,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
|
@ -288,7 +291,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var dictionary = new Dictionary<string, string>
|
var dictionary = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
|
@ -401,11 +406,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Get data
|
|
||||||
|
|
||||||
private int _connectFailureCount;
|
private int _connectFailureCount;
|
||||||
|
|
||||||
private async void TimerCallback(object sender)
|
private async void TimerCallback(object sender)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
@ -458,7 +460,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
_connectFailureCount = 0;
|
_connectFailureCount = 0;
|
||||||
|
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
|
// 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)
|
if (transportState.Value == TRANSPORTSTATE.STOPPED)
|
||||||
|
@ -478,7 +482,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name);
|
_logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name);
|
||||||
|
|
||||||
|
@ -494,6 +500,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RestartTimerInactive();
|
RestartTimerInactive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -578,7 +585,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse")
|
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse")
|
||||||
.Select(i => i.Element("CurrentMute"))
|
.Select(i => i.Element("CurrentMute"))
|
||||||
|
@ -750,7 +759,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (track == null)
|
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);
|
return (true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -794,7 +803,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
catch (XmlException)
|
catch (XmlException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// first try to add a root node with a dlna namesapce
|
// first try to add a root node with a dlna namesapce
|
||||||
|
@ -806,7 +814,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
catch (XmlException)
|
catch (XmlException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// some devices send back invalid xml
|
// some devices send back invalid xml
|
||||||
|
@ -816,7 +823,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
catch (XmlException)
|
catch (XmlException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -871,10 +877,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
return new string[4];
|
return new string[4];
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region From XML
|
|
||||||
|
|
||||||
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
|
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (AvCommands != null)
|
if (AvCommands != null)
|
||||||
|
@ -1069,8 +1071,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
return new Device(deviceProperties, httpClient, logger, config);
|
return new Device(deviceProperties, httpClient, logger, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||||
private static DeviceIcon CreateIcon(XElement element)
|
private static DeviceIcon CreateIcon(XElement element)
|
||||||
{
|
{
|
||||||
|
@ -1194,8 +1194,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
bool _disposed;
|
bool _disposed;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -1222,8 +1220,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
|
return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
@ -22,6 +23,7 @@ using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Photo = MediaBrowser.Controller.Entities.Photo;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
|
@ -146,11 +148,14 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
var positionTicks = GetProgressPositionTicks(streamInfo);
|
var positionTicks = GetProgressPositionTicks(streamInfo);
|
||||||
|
|
||||||
ReportPlaybackStopped(streamInfo, positionTicks);
|
await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
|
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
|
||||||
if (streamInfo.Item == null) return;
|
if (streamInfo.Item == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var newItemProgress = GetProgressInfo(streamInfo);
|
var newItemProgress = GetProgressInfo(streamInfo);
|
||||||
|
|
||||||
|
@ -173,11 +178,14 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
|
var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
|
||||||
|
|
||||||
if (streamInfo.Item == null) return;
|
if (streamInfo.Item == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var positionTicks = GetProgressPositionTicks(streamInfo);
|
var positionTicks = GetProgressPositionTicks(streamInfo);
|
||||||
|
|
||||||
ReportPlaybackStopped(streamInfo, positionTicks);
|
await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false);
|
||||||
|
|
||||||
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).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) :
|
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
|
||||||
mediaSource.RunTimeTicks;
|
mediaSource.RunTimeTicks;
|
||||||
|
|
||||||
var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0);
|
var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
|
||||||
|
|
||||||
if (!playedToCompletion && duration.HasValue && positionTicks.HasValue)
|
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
|
try
|
||||||
{
|
{
|
||||||
|
@ -220,7 +228,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
SessionId = _session.Id,
|
SessionId = _session.Id,
|
||||||
PositionTicks = positionTicks,
|
PositionTicks = positionTicks,
|
||||||
MediaSourceId = streamInfo.MediaSourceId
|
MediaSourceId = streamInfo.MediaSourceId
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -418,6 +425,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
|
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;
|
var deviceInfo = _device.Properties;
|
||||||
|
|
||||||
|
@ -700,6 +714,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
throw new ArgumentException("Volume argument cannot be null");
|
throw new ArgumentException("Volume argument cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
@ -785,12 +800,15 @@ namespace Emby.Dlna.PlayTo
|
||||||
public int? SubtitleStreamIndex { get; set; }
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
|
|
||||||
public string DeviceProfileId { get; set; }
|
public string DeviceProfileId { get; set; }
|
||||||
|
|
||||||
public string DeviceId { get; set; }
|
public string DeviceId { get; set; }
|
||||||
|
|
||||||
public string MediaSourceId { get; set; }
|
public string MediaSourceId { get; set; }
|
||||||
|
|
||||||
public string LiveStreamId { get; set; }
|
public string LiveStreamId { get; set; }
|
||||||
|
|
||||||
public BaseItem Item { get; set; }
|
public BaseItem Item { get; set; }
|
||||||
|
|
||||||
private MediaSourceInfo MediaSource;
|
private MediaSourceInfo MediaSource;
|
||||||
|
|
||||||
private IMediaSourceManager _mediaSourceManager;
|
private IMediaSourceManager _mediaSourceManager;
|
||||||
|
|
|
@ -78,9 +78,15 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var info = e.Argument;
|
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();
|
string location = info.Location.ToString();
|
||||||
|
|
||||||
|
@ -88,7 +94,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
|
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
|
||||||
nt.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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +118,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -133,6 +138,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
usn = usn.Substring(index);
|
usn = usn.Substring(index);
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase);
|
index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase);
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
{
|
{
|
||||||
|
@ -184,7 +190,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
|
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
controller = new PlayToController(sessionInfo,
|
controller = new PlayToController(
|
||||||
|
sessionInfo,
|
||||||
_sessionManager,
|
_sessionManager,
|
||||||
_libraryManager,
|
_libraryManager,
|
||||||
_logger,
|
_logger,
|
||||||
|
@ -242,7 +249,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_sessionLock.Dispose();
|
_sessionLock.Dispose();
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
public class MediaChangedEventArgs : EventArgs
|
public class MediaChangedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public uBaseObject OldMediaInfo { get; set; }
|
public uBaseObject OldMediaInfo { get; set; }
|
||||||
|
|
||||||
public uBaseObject NewMediaInfo { get; set; }
|
public uBaseObject NewMediaInfo { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
|
using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
return MediaBrowser.Model.Entities.MediaType.Audio;
|
return MediaBrowser.Model.Entities.MediaType.Audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1)
|
if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1)
|
||||||
{
|
{
|
||||||
return MediaBrowser.Model.Entities.MediaType.Video;
|
return MediaBrowser.Model.Entities.MediaType.Video;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classType.IndexOf("image", StringComparison.Ordinal) != -1)
|
if (classType.IndexOf("image", StringComparison.Ordinal) != -1)
|
||||||
{
|
{
|
||||||
return MediaBrowser.Model.Entities.MediaType.Photo;
|
return MediaBrowser.Model.Entities.MediaType.Photo;
|
||||||
|
|
|
@ -134,6 +134,7 @@ namespace Emby.Dlna.Server
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.ToString(CultureInfo.InvariantCulture);
|
return c.ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,18 +158,22 @@ namespace Emby.Dlna.Server
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stringBuilder == null)
|
if (stringBuilder == null)
|
||||||
{
|
{
|
||||||
stringBuilder = new StringBuilder();
|
stringBuilder = new StringBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
stringBuilder.Append(str, num, num2 - num);
|
stringBuilder.Append(str, num, num2 - num);
|
||||||
stringBuilder.Append(GetEscapeSequence(str[num2]));
|
stringBuilder.Append(GetEscapeSequence(str[num2]));
|
||||||
num = num2 + 1;
|
num = num2 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stringBuilder == null)
|
if (stringBuilder == null)
|
||||||
{
|
{
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
stringBuilder.Append(str, num, length - num);
|
stringBuilder.Append(str, num, length - num);
|
||||||
return stringBuilder.ToString();
|
return stringBuilder.ToString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace Emby.Dlna.Service
|
||||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||||
|
|
||||||
protected IServerConfigurationManager Config { get; }
|
protected IServerConfigurationManager Config { get; }
|
||||||
|
|
||||||
protected ILogger Logger { get; }
|
protected ILogger Logger { get; }
|
||||||
|
|
||||||
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
||||||
|
@ -135,6 +136,7 @@ namespace Emby.Dlna.Service
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
await reader.SkipAsync().ConfigureAwait(false);
|
await reader.SkipAsync().ConfigureAwait(false);
|
||||||
|
@ -211,7 +213,9 @@ namespace Emby.Dlna.Service
|
||||||
private class ControlRequestInfo
|
private class ControlRequestInfo
|
||||||
{
|
{
|
||||||
public string LocalName { get; set; }
|
public string LocalName { get; set; }
|
||||||
|
|
||||||
public string NamespaceURI { get; set; }
|
public string NamespaceURI { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace Emby.Dlna.Service
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
HttpClient = httpClient;
|
HttpClient = httpClient;
|
||||||
|
|
||||||
EventManager = new EventManager(Logger, HttpClient);
|
EventManager = new EventManager(logger, HttpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
|
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
|
||||||
|
|
|
@ -80,6 +80,7 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>");
|
builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("</allowedValueList>");
|
builder.Append("</allowedValueList>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ namespace Emby.Dlna.Ssdp
|
||||||
// (Optional) Set the filter so we only see notifications for devices we care about
|
// (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
|
// (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).
|
// 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
|
// Connect our event handler so we process devices as they are found
|
||||||
_deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;
|
_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 headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var args = new GenericEventArgs<UpnpDeviceInfo>
|
var args = new GenericEventArgs<UpnpDeviceInfo>(
|
||||||
{
|
new UpnpDeviceInfo
|
||||||
Argument = new UpnpDeviceInfo
|
|
||||||
{
|
{
|
||||||
Location = e.DiscoveredDevice.DescriptionLocation,
|
Location = e.DiscoveredDevice.DescriptionLocation,
|
||||||
Headers = headers,
|
Headers = headers,
|
||||||
LocalIpAddress = e.LocalIpAddress
|
LocalIpAddress = e.LocalIpAddress
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
DeviceDiscoveredInternal?.Invoke(this, args);
|
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 headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var args = new GenericEventArgs<UpnpDeviceInfo>
|
var args = new GenericEventArgs<UpnpDeviceInfo>(
|
||||||
{
|
new UpnpDeviceInfo
|
||||||
Argument = new UpnpDeviceInfo
|
|
||||||
{
|
{
|
||||||
Location = e.DiscoveredDevice.DescriptionLocation,
|
Location = e.DiscoveredDevice.DescriptionLocation,
|
||||||
Headers = headers
|
Headers = headers
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
DeviceLeft?.Invoke(this, args);
|
DeviceLeft?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Emby.Dlna.Ssdp
|
namespace Emby.Dlna.Ssdp
|
||||||
|
@ -10,24 +11,17 @@ namespace Emby.Dlna.Ssdp
|
||||||
{
|
{
|
||||||
var node = container.Element(name);
|
var node = container.Element(name);
|
||||||
|
|
||||||
return node == null ? null : node.Value;
|
return node?.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetAttributeValue(this XElement container, XName name)
|
public static string GetAttributeValue(this XElement container, XName name)
|
||||||
{
|
{
|
||||||
var node = container.Attribute(name);
|
var node = container.Attribute(name);
|
||||||
|
|
||||||
return node == null ? null : node.Value;
|
return node?.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDescendantValue(this XElement container, XName name)
|
public static string GetDescendantValue(this XElement container, XName name)
|
||||||
{
|
=> container.Descendants(name).FirstOrDefault()?.Value;
|
||||||
foreach (var node in container.Descendants(name))
|
|
||||||
{
|
|
||||||
return node.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
@ -14,6 +15,7 @@ using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Photo = MediaBrowser.Controller.Entities.Photo;
|
||||||
|
|
||||||
namespace Emby.Drawing
|
namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
|
@ -28,7 +30,7 @@ namespace Emby.Drawing
|
||||||
private static readonly HashSet<string> _transparentImageTypes
|
private static readonly HashSet<string> _transparentImageTypes
|
||||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
|
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<ImageProcessor> _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
private readonly IImageEncoder _imageEncoder;
|
private readonly IImageEncoder _imageEncoder;
|
||||||
|
@ -114,7 +116,7 @@ namespace Emby.Drawing
|
||||||
=> _transparentImageTypes.Contains(Path.GetExtension(path));
|
=> _transparentImageTypes.Contains(Path.GetExtension(path));
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
|
public async Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
|
||||||
{
|
{
|
||||||
ItemImageInfo originalImage = options.Image;
|
ItemImageInfo originalImage = options.Image;
|
||||||
BaseItem item = options.Item;
|
BaseItem item = options.Item;
|
||||||
|
@ -230,7 +232,7 @@ namespace Emby.Drawing
|
||||||
return ImageFormat.Jpg;
|
return ImageFormat.Jpg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetMimeType(ImageFormat format, string path)
|
private string? GetMimeType(ImageFormat format, string path)
|
||||||
=> format switch
|
=> format switch
|
||||||
{
|
{
|
||||||
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
|
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
|
||||||
|
@ -300,7 +302,7 @@ namespace Emby.Drawing
|
||||||
}
|
}
|
||||||
|
|
||||||
string path = info.Path;
|
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);
|
ImageDimensions size = GetImageDimensions(path);
|
||||||
info.Width = size.Width;
|
info.Width = size.Width;
|
||||||
|
@ -313,6 +315,27 @@ namespace Emby.Drawing
|
||||||
public ImageDimensions GetImageDimensions(string path)
|
public ImageDimensions GetImageDimensions(string path)
|
||||||
=> _imageEncoder.GetImageSize(path);
|
=> _imageEncoder.GetImageSize(path);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
|
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
|
||||||
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
@ -328,6 +351,13 @@ namespace Emby.Drawing
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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)
|
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||||
{
|
{
|
||||||
var inputFormat = Path.GetExtension(originalImagePath)
|
var inputFormat = Path.GetExtension(originalImagePath)
|
||||||
|
|
|
@ -42,5 +42,11 @@ namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string GetImageBlurHash(int xComp, int yComp, string path)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ namespace Emby.Naming.AudioBook
|
||||||
{
|
{
|
||||||
result.ChapterNumber = int.Parse(matches[0].Groups[0].Value);
|
result.ChapterNumber = int.Parse(matches[0].Groups[0].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matches.Count > 1)
|
if (matches.Count > 1)
|
||||||
{
|
{
|
||||||
result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value);
|
result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value);
|
||||||
|
|
|
@ -5,17 +5,17 @@ namespace Emby.Naming.Common
|
||||||
public enum MediaType
|
public enum MediaType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The audio
|
/// The audio.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Audio = 0,
|
Audio = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The photo
|
/// The photo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Photo = 1,
|
Photo = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The video
|
/// The video.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Video = 2
|
Video = 2
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
CleanStrings = new[]
|
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|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
||||||
@"(\[.*\])"
|
@"(\[.*\])"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Notifications;
|
using MediaBrowser.Controller.Notifications;
|
||||||
|
@ -149,9 +150,7 @@ namespace Emby.Notifications.Api
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetNotificationsSummary request)
|
public object Get(GetNotificationsSummary request)
|
||||||
{
|
{
|
||||||
return new NotificationsSummary
|
return new NotificationsSummary();
|
||||||
{
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Post(AddAdminNotification request)
|
public Task Post(AddAdminNotification request)
|
||||||
|
@ -164,7 +163,10 @@ namespace Emby.Notifications.Api
|
||||||
Level = request.Level,
|
Level = request.Level,
|
||||||
Name = request.Name,
|
Name = request.Name,
|
||||||
Url = request.Url,
|
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);
|
return _notificationManager.SendNotification(notification, CancellationToken.None);
|
||||||
|
|
|
@ -25,7 +25,7 @@ namespace Emby.Notifications
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NotificationEntryPoint : IServerEntryPoint
|
public class NotificationEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<NotificationEntryPoint> _logger;
|
||||||
private readonly IActivityManager _activityManager;
|
private readonly IActivityManager _activityManager;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly INotificationManager _notificationManager;
|
private readonly INotificationManager _notificationManager;
|
||||||
|
|
|
@ -4,6 +4,8 @@ using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
@ -21,7 +23,7 @@ namespace Emby.Notifications
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NotificationManager : INotificationManager
|
public class NotificationManager : INotificationManager
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<NotificationManager> _logger;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
|
@ -101,7 +103,7 @@ namespace Emby.Notifications
|
||||||
switch (request.SendToUserMode.Value)
|
switch (request.SendToUserMode.Value)
|
||||||
{
|
{
|
||||||
case SendToUserType.Admins:
|
case SendToUserType.Admins:
|
||||||
return _userManager.Users.Where(i => i.Policy.IsAdministrator)
|
return _userManager.Users.Where(i => i.HasPermission(PermissionKind.IsAdministrator))
|
||||||
.Select(i => i.Id);
|
.Select(i => i.Id);
|
||||||
case SendToUserType.All:
|
case SendToUserType.All:
|
||||||
return _userManager.UsersIds;
|
return _userManager.UsersIds;
|
||||||
|
@ -117,7 +119,7 @@ namespace Emby.Notifications
|
||||||
var config = GetConfiguration();
|
var config = GetConfiguration();
|
||||||
|
|
||||||
return _userManager.Users
|
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);
|
.Select(i => i.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +144,7 @@ namespace Emby.Notifications
|
||||||
User = user
|
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
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Emby.Photos
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
|
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<PhotoProvider> _logger;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
|
||||||
// These are causing taglib to hang
|
// These are causing taglib to hang
|
||||||
|
@ -104,7 +104,7 @@ namespace Emby.Photos
|
||||||
item.Overview = image.ImageTag.Comment;
|
item.Overview = image.ImageTag.Comment;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
|
if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
|
||||||
&& !item.LockedFields.Contains(MetadataFields.Name))
|
&& !item.LockedFields.Contains(MetadataField.Name))
|
||||||
{
|
{
|
||||||
item.Name = image.ImageTag.Title;
|
item.Name = image.ImageTag.Title;
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,25 +88,26 @@ namespace Emby.Server.Implementations.Activity
|
||||||
|
|
||||||
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
|
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
|
||||||
|
|
||||||
_userManager.UserCreated += OnUserCreated;
|
_userManager.OnUserCreated += OnUserCreated;
|
||||||
_userManager.UserPasswordChanged += OnUserPasswordChanged;
|
_userManager.OnUserPasswordChanged += OnUserPasswordChanged;
|
||||||
_userManager.UserDeleted += OnUserDeleted;
|
_userManager.OnUserDeleted += OnUserDeleted;
|
||||||
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
_userManager.OnUserLockedOut += OnUserLockedOut;
|
||||||
_userManager.UserLockedOut += OnUserLockedOut;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnUserLockedOut(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLog(
|
await CreateLogEntry(new ActivityLog(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserLockedOutWithName"),
|
_localization.GetLocalizedString("UserLockedOutWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Username),
|
||||||
NotificationType.UserLockedOut.ToString(),
|
NotificationType.UserLockedOut.ToString(),
|
||||||
e.Argument.Id))
|
e.Argument.Id)
|
||||||
.ConfigureAwait(false);
|
{
|
||||||
|
LogSeverity = LogLevel.Error
|
||||||
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||||
|
@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Activity
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
||||||
user.Name,
|
user.Username,
|
||||||
GetItemName(item),
|
GetItemName(item),
|
||||||
e.DeviceName),
|
e.DeviceName),
|
||||||
GetPlaybackStoppedNotificationType(item.MediaType),
|
GetPlaybackStoppedNotificationType(item.MediaType),
|
||||||
|
@ -187,7 +188,7 @@ namespace Emby.Server.Implementations.Activity
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
|
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
|
||||||
user.Name,
|
user.Username,
|
||||||
GetItemName(item),
|
GetItemName(item),
|
||||||
e.DeviceName),
|
e.DeviceName),
|
||||||
GetPlaybackNotificationType(item.MediaType),
|
GetPlaybackNotificationType(item.MediaType),
|
||||||
|
@ -304,49 +305,37 @@ namespace Emby.Server.Implementations.Activity
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnUserPolicyUpdated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
|
|
||||||
e.Argument.Name),
|
|
||||||
"UserPolicyUpdated",
|
|
||||||
e.Argument.Id))
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnUserDeleted(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLog(
|
await CreateLogEntry(new ActivityLog(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserDeletedWithName"),
|
_localization.GetLocalizedString("UserDeletedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Username),
|
||||||
"UserDeleted",
|
"UserDeleted",
|
||||||
Guid.Empty))
|
Guid.Empty))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnUserPasswordChanged(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLog(
|
await CreateLogEntry(new ActivityLog(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserPasswordChangedWithName"),
|
_localization.GetLocalizedString("UserPasswordChangedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Username),
|
||||||
"UserPasswordChanged",
|
"UserPasswordChanged",
|
||||||
e.Argument.Id))
|
e.Argument.Id))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnUserCreated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
private async void OnUserCreated(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLog(
|
await CreateLogEntry(new ActivityLog(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserCreatedWithName"),
|
_localization.GetLocalizedString("UserCreatedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Username),
|
||||||
"UserCreated",
|
"UserCreated",
|
||||||
e.Argument.Id))
|
e.Argument.Id))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
@ -377,50 +366,50 @@ namespace Emby.Server.Implementations.Activity
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
|
private async void OnPluginUpdated(object sender, InstallationInfo e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLog(
|
await CreateLogEntry(new ActivityLog(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("PluginUpdatedWithName"),
|
_localization.GetLocalizedString("PluginUpdatedWithName"),
|
||||||
e.Argument.Item1.Name),
|
e.Name),
|
||||||
NotificationType.PluginUpdateInstalled.ToString(),
|
NotificationType.PluginUpdateInstalled.ToString(),
|
||||||
Guid.Empty)
|
Guid.Empty)
|
||||||
{
|
{
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
e.Argument.Item2.version),
|
e.Version),
|
||||||
Overview = e.Argument.Item2.changelog
|
Overview = e.Changelog
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
private async void OnPluginUninstalled(object sender, IPlugin e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLog(
|
await CreateLogEntry(new ActivityLog(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("PluginUninstalledWithName"),
|
_localization.GetLocalizedString("PluginUninstalledWithName"),
|
||||||
e.Argument.Name),
|
e.Name),
|
||||||
NotificationType.PluginUninstalled.ToString(),
|
NotificationType.PluginUninstalled.ToString(),
|
||||||
Guid.Empty))
|
Guid.Empty))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
|
private async void OnPluginInstalled(object sender, InstallationInfo e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLog(
|
await CreateLogEntry(new ActivityLog(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("PluginInstalledWithName"),
|
_localization.GetLocalizedString("PluginInstalledWithName"),
|
||||||
e.Argument.name),
|
e.Name),
|
||||||
NotificationType.PluginInstalled.ToString(),
|
NotificationType.PluginInstalled.ToString(),
|
||||||
Guid.Empty)
|
Guid.Empty)
|
||||||
{
|
{
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
e.Argument.version)
|
e.Version)
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,11 +499,10 @@ namespace Emby.Server.Implementations.Activity
|
||||||
|
|
||||||
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
|
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
|
||||||
|
|
||||||
_userManager.UserCreated -= OnUserCreated;
|
_userManager.OnUserCreated -= OnUserCreated;
|
||||||
_userManager.UserPasswordChanged -= OnUserPasswordChanged;
|
_userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
|
||||||
_userManager.UserDeleted -= OnUserDeleted;
|
_userManager.OnUserDeleted -= OnUserDeleted;
|
||||||
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
_userManager.OnUserLockedOut -= OnUserLockedOut;
|
||||||
_userManager.UserLockedOut -= OnUserLockedOut;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
CommonApplicationPaths = applicationPaths;
|
CommonApplicationPaths = applicationPaths;
|
||||||
XmlSerializer = xmlSerializer;
|
XmlSerializer = xmlSerializer;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
Logger = loggerFactory.CreateLogger(GetType().Name);
|
Logger = loggerFactory.CreateLogger<BaseConfigurationManager>();
|
||||||
|
|
||||||
UpdateCachePath();
|
UpdateCachePath();
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// Gets the logger.
|
/// Gets the logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The logger.</value>
|
/// <value>The logger.</value>
|
||||||
protected ILogger Logger { get; private set; }
|
protected ILogger<BaseConfigurationManager> Logger { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the XML serializer.
|
/// Gets the XML serializer.
|
||||||
|
|
|
@ -45,6 +45,7 @@ using Emby.Server.Implementations.Services;
|
||||||
using Emby.Server.Implementations.Session;
|
using Emby.Server.Implementations.Session;
|
||||||
using Emby.Server.Implementations.TV;
|
using Emby.Server.Implementations.TV;
|
||||||
using Emby.Server.Implementations.Updates;
|
using Emby.Server.Implementations.Updates;
|
||||||
|
using Emby.Server.Implementations.SyncPlay;
|
||||||
using MediaBrowser.Api;
|
using MediaBrowser.Api;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
@ -78,6 +79,7 @@ using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Controller.Sorting;
|
using MediaBrowser.Controller.Sorting;
|
||||||
using MediaBrowser.Controller.Subtitles;
|
using MediaBrowser.Controller.Subtitles;
|
||||||
using MediaBrowser.Controller.TV;
|
using MediaBrowser.Controller.TV;
|
||||||
|
using MediaBrowser.Controller.SyncPlay;
|
||||||
using MediaBrowser.LocalMetadata.Savers;
|
using MediaBrowser.LocalMetadata.Savers;
|
||||||
using MediaBrowser.MediaEncoding.BdInfo;
|
using MediaBrowser.MediaEncoding.BdInfo;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
|
@ -171,7 +173,7 @@ namespace Emby.Server.Implementations
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the logger.
|
/// Gets the logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected ILogger Logger { get; }
|
protected ILogger<ApplicationHost> Logger { get; }
|
||||||
|
|
||||||
private IPlugin[] _plugins;
|
private IPlugin[] _plugins;
|
||||||
|
|
||||||
|
@ -560,11 +562,8 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
|
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>();
|
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
|
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
|
||||||
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
|
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
|
||||||
|
@ -613,6 +612,8 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
||||||
|
|
||||||
|
serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<LiveTvDtoService>();
|
serviceCollection.AddSingleton<LiveTvDtoService>();
|
||||||
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||||
|
|
||||||
|
@ -655,15 +656,11 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
|
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
|
||||||
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
||||||
((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
|
|
||||||
|
|
||||||
SetStaticProperties();
|
SetStaticProperties();
|
||||||
|
|
||||||
var userManager = (UserManager)Resolve<IUserManager>();
|
|
||||||
userManager.Initialize();
|
|
||||||
|
|
||||||
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
|
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
|
||||||
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager);
|
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, Resolve<IUserManager>());
|
||||||
|
|
||||||
FindParts();
|
FindParts();
|
||||||
}
|
}
|
||||||
|
@ -746,7 +743,6 @@ namespace Emby.Server.Implementations
|
||||||
BaseItem.ProviderManager = Resolve<IProviderManager>();
|
BaseItem.ProviderManager = Resolve<IProviderManager>();
|
||||||
BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
|
BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
|
||||||
BaseItem.ItemRepository = Resolve<IItemRepository>();
|
BaseItem.ItemRepository = Resolve<IItemRepository>();
|
||||||
User.UserManager = Resolve<IUserManager>();
|
|
||||||
BaseItem.FileSystem = _fileSystemManager;
|
BaseItem.FileSystem = _fileSystemManager;
|
||||||
BaseItem.UserDataManager = Resolve<IUserDataManager>();
|
BaseItem.UserDataManager = Resolve<IUserDataManager>();
|
||||||
BaseItem.ChannelManager = Resolve<IChannelManager>();
|
BaseItem.ChannelManager = Resolve<IChannelManager>();
|
||||||
|
@ -960,7 +956,7 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies that the kernel that a change has been made that requires a restart
|
/// Notifies that the kernel that a change has been made that requires a restart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void NotifyPendingRestart()
|
public void NotifyPendingRestart()
|
||||||
{
|
{
|
||||||
|
@ -1238,7 +1234,7 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
if (addresses.Count == 0)
|
if (addresses.Count == 0)
|
||||||
{
|
{
|
||||||
addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
|
addresses.AddRange(_networkManager.GetLocalIpAddresses());
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultList = new List<IPAddress>();
|
var resultList = new List<IPAddress>();
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Browser
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var logger = appHost.Resolve<ILogger>();
|
var logger = appHost.Resolve<ILogger<IServerApplicationHost>>();
|
||||||
logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
|
logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Progress;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
|
@ -13,8 +14,6 @@ using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Channels;
|
using MediaBrowser.Model.Channels;
|
||||||
|
@ -24,6 +23,11 @@ using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||||
|
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;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
@ -36,7 +40,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<ChannelManager> _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
|
@ -46,14 +50,14 @@ namespace Emby.Server.Implementations.Channels
|
||||||
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
||||||
|
|
||||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
|
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userManager">The user manager.</param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
/// <param name="dtoService">The dto service.</param>
|
/// <param name="dtoService">The dto service.</param>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
/// <param name="loggerFactory">The logger factory.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="config">The server configuration manager.</param>
|
/// <param name="config">The server configuration manager.</param>
|
||||||
/// <param name="fileSystem">The filesystem.</param>
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
/// <param name="userDataManager">The user data manager.</param>
|
/// <param name="userDataManager">The user data manager.</param>
|
||||||
|
@ -791,7 +795,8 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
|
private async Task<ChannelItemResult> GetChannelItems(
|
||||||
|
IChannel channel,
|
||||||
User user,
|
User user,
|
||||||
string externalFolderId,
|
string externalFolderId,
|
||||||
ChannelItemSortField? sortField,
|
ChannelItemSortField? sortField,
|
||||||
|
@ -1067,7 +1072,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
|
|
||||||
// was used for status
|
// was used for status
|
||||||
//if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
|
// if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
|
||||||
//{
|
//{
|
||||||
// item.ExternalEtag = info.Etag;
|
// item.ExternalEtag = info.Etag;
|
||||||
// forceUpdate = true;
|
// forceUpdate = true;
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||||
{
|
{
|
||||||
private readonly IChannelManager _channelManager;
|
private readonly IChannelManager _channelManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<RefreshChannelsScheduledTask> _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Collections;
|
using MediaBrowser.Controller.Collections;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
@ -29,7 +30,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ILibraryMonitor _iLibraryMonitor;
|
private readonly ILibraryMonitor _iLibraryMonitor;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<CollectionManager> _logger;
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
private readonly ILocalizationManager _localizationManager;
|
private readonly ILocalizationManager _localizationManager;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
@ -56,7 +57,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_iLibraryMonitor = iLibraryMonitor;
|
_iLibraryMonitor = iLibraryMonitor;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(CollectionManager));
|
_logger = loggerFactory.CreateLogger<CollectionManager>();
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
_localizationManager = localizationManager;
|
_localizationManager = localizationManager;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
|
@ -370,7 +371,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
{
|
{
|
||||||
private readonly CollectionManager _collectionManager;
|
private readonly CollectionManager _collectionManager;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<CollectionManagerEntryPoint> _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.
|
/// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.
|
||||||
|
|
|
@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Configuration
|
||||||
ValidateMetadataPath(newConfig);
|
ValidateMetadataPath(newConfig);
|
||||||
ValidateSslCertificate(newConfig);
|
ValidateSslCertificate(newConfig);
|
||||||
|
|
||||||
ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration> { Argument = newConfig });
|
ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration>(newConfig));
|
||||||
|
|
||||||
base.ReplaceConfiguration(newConfiguration);
|
base.ReplaceConfiguration(newConfiguration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ namespace Emby.Server.Implementations
|
||||||
{
|
{
|
||||||
{ HostWebClientKey, bool.TrueString },
|
{ HostWebClientKey, bool.TrueString },
|
||||||
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
||||||
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
|
|
||||||
{ FfmpegProbeSizeKey, "1G" },
|
{ FfmpegProbeSizeKey, "1G" },
|
||||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||||
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
@ -129,8 +131,6 @@ namespace Emby.Server.Implementations.Cryptography
|
||||||
_randomNumberGenerator.Dispose();
|
_randomNumberGenerator.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_randomNumberGenerator = null;
|
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
/// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class.
|
/// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
protected BaseSqliteRepository(ILogger logger)
|
protected BaseSqliteRepository(ILogger<BaseSqliteRepository> logger)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
/// Gets the logger.
|
/// Gets the logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The logger.</value>
|
/// <value>The logger.</value>
|
||||||
protected ILogger Logger { get; }
|
protected ILogger<BaseSqliteRepository> Logger { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default connection flags.
|
/// Gets the default connection flags.
|
||||||
|
@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}, ReadTransactionMode);
|
}, ReadTransactionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,12 +247,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
public enum SynchronousMode
|
public enum SynchronousMode
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SQLite continues without syncing as soon as it has handed data off to the operating system
|
/// SQLite continues without syncing as soon as it has handed data off to the operating system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Off = 0,
|
Off = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SQLite database engine will still sync at the most critical moments
|
/// SQLite database engine will still sync at the most critical moments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Normal = 1,
|
Normal = 1,
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
public class CleanDatabaseScheduledTask : ILibraryPostScanTask
|
public class CleanDatabaseScheduledTask : ILibraryPostScanTask
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<CleanDatabaseScheduledTask> _logger;
|
||||||
|
|
||||||
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
|
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
|
||||||
{
|
{
|
||||||
|
@ -51,7 +51,6 @@ namespace Emby.Server.Implementations.Data
|
||||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||||
{
|
{
|
||||||
DeleteFileLocation = false
|
DeleteFileLocation = false
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the connection to the database
|
/// Opens the connection to the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
private void InitializeInternal()
|
private void InitializeInternal()
|
||||||
|
@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save the display preferences associated with an item in the repo
|
/// Save the display preferences associated with an item in the repo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="displayPreferences">The display preferences.</param>
|
/// <param name="displayPreferences">The display preferences.</param>
|
||||||
/// <param name="userId">The user id.</param>
|
/// <param name="userId">The user id.</param>
|
||||||
|
@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save all display preferences associated with a user in the repo
|
/// Save all display preferences associated with a user in the repo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="displayPreferences">The display preferences.</param>
|
/// <param name="displayPreferences">The display preferences.</param>
|
||||||
/// <param name="userId">The user id.</param>
|
/// <param name="userId">The user id.</param>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
@ -134,10 +135,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(userData));
|
throw new ArgumentNullException(nameof(userData));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (internalUserId <= 0)
|
if (internalUserId <= 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(internalUserId));
|
throw new ArgumentNullException(nameof(internalUserId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(key))
|
if (string.IsNullOrEmpty(key))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(key));
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
@ -152,6 +155,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(userData));
|
throw new ArgumentNullException(nameof(userData));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (internalUserId <= 0)
|
if (internalUserId <= 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(internalUserId));
|
throw new ArgumentNullException(nameof(internalUserId));
|
||||||
|
@ -234,7 +238,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Persist all user data for the specified user
|
/// Persist all user data for the specified user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken)
|
private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
@ -308,7 +312,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return all user-data associated with the given user
|
/// Return all user-data associated with the given user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="internalUserId"></param>
|
/// <param name="internalUserId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
@ -338,7 +342,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read a row from the specified reader into the provided userData object
|
/// Read a row from the specified reader into the provided userData object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader"></param>
|
/// <param name="reader"></param>
|
||||||
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
|
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
|
||||||
|
@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
var userData = new UserItemData();
|
var userData = new UserItemData();
|
||||||
|
|
||||||
userData.Key = reader[0].ToString();
|
userData.Key = reader[0].ToString();
|
||||||
//userData.UserId = reader[1].ReadGuidFromBlob();
|
// userData.UserId = reader[1].ReadGuidFromBlob();
|
||||||
|
|
||||||
if (reader[2].SQLiteType != SQLiteType.Null)
|
if (reader[2].SQLiteType != SQLiteType.Null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,240 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text.Json;
|
|
||||||
using MediaBrowser.Common.Json;
|
|
||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Persistence;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using SQLitePCL.pretty;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Data
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class SQLiteUserRepository
|
|
||||||
/// </summary>
|
|
||||||
public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
|
|
||||||
{
|
|
||||||
private readonly JsonSerializerOptions _jsonOptions;
|
|
||||||
|
|
||||||
public SqliteUserRepository(
|
|
||||||
ILogger<SqliteUserRepository> logger,
|
|
||||||
IServerApplicationPaths appPaths)
|
|
||||||
: base(logger)
|
|
||||||
{
|
|
||||||
_jsonOptions = JsonDefaults.GetOptions();
|
|
||||||
|
|
||||||
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the repository
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name => "SQLite";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Opens the connection to the database.
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
var localUsersTableExists = TableExists(connection, "LocalUsersv2");
|
|
||||||
|
|
||||||
connection.RunQueries(new[] {
|
|
||||||
"create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)",
|
|
||||||
"drop index if exists idx_users"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!localUsersTableExists && TableExists(connection, "Users"))
|
|
||||||
{
|
|
||||||
TryMigrateToLocalUsersTable(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveEmptyPasswordHashes(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryMigrateToLocalUsersTable(ManagedConnection connection)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
connection.RunQueries(new[]
|
|
||||||
{
|
|
||||||
"INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Error migrating users database");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveEmptyPasswordHashes(ManagedConnection connection)
|
|
||||||
{
|
|
||||||
foreach (var user in RetrieveAllUsers(connection))
|
|
||||||
{
|
|
||||||
// If the user password is the sha1 hash of the empty string, remove it
|
|
||||||
if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
|
|
||||||
&& !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Password = null;
|
|
||||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
|
|
||||||
|
|
||||||
connection.RunInTransaction(db =>
|
|
||||||
{
|
|
||||||
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
|
||||||
{
|
|
||||||
statement.TryBind("@InternalId", user.InternalId);
|
|
||||||
statement.TryBind("@data", serialized);
|
|
||||||
statement.MoveNext();
|
|
||||||
}
|
|
||||||
}, TransactionMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Save a user in the repo
|
|
||||||
/// </summary>
|
|
||||||
public void CreateUser(User user)
|
|
||||||
{
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
|
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
connection.RunInTransaction(db =>
|
|
||||||
{
|
|
||||||
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
|
|
||||||
{
|
|
||||||
statement.TryBind("@guid", user.Id.ToByteArray());
|
|
||||||
statement.TryBind("@data", serialized);
|
|
||||||
|
|
||||||
statement.MoveNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
var createdUser = GetUser(user.Id, connection);
|
|
||||||
|
|
||||||
if (createdUser == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("created user should never be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
user.InternalId = createdUser.InternalId;
|
|
||||||
|
|
||||||
}, TransactionMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateUser(User user)
|
|
||||||
{
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
|
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
connection.RunInTransaction(db =>
|
|
||||||
{
|
|
||||||
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
|
||||||
{
|
|
||||||
statement.TryBind("@InternalId", user.InternalId);
|
|
||||||
statement.TryBind("@data", serialized);
|
|
||||||
statement.MoveNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
}, TransactionMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private User GetUser(Guid guid, ManagedConnection connection)
|
|
||||||
{
|
|
||||||
using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
|
|
||||||
{
|
|
||||||
statement.TryBind("@guid", guid);
|
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
|
||||||
{
|
|
||||||
return GetUser(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private User GetUser(IReadOnlyList<IResultSetValue> row)
|
|
||||||
{
|
|
||||||
var id = row[0].ToInt64();
|
|
||||||
var guid = row[1].ReadGuidFromBlob();
|
|
||||||
|
|
||||||
var user = JsonSerializer.Deserialize<User>(row[2].ToBlob(), _jsonOptions);
|
|
||||||
user.InternalId = id;
|
|
||||||
user.Id = guid;
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieve all users from the database
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>IEnumerable{User}.</returns>
|
|
||||||
public List<User> RetrieveAllUsers()
|
|
||||||
{
|
|
||||||
using (var connection = GetConnection(true))
|
|
||||||
{
|
|
||||||
return new List<User>(RetrieveAllUsers(connection));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieve all users from the database
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>IEnumerable{User}.</returns>
|
|
||||||
private IEnumerable<User> RetrieveAllUsers(ManagedConnection connection)
|
|
||||||
{
|
|
||||||
foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
|
|
||||||
{
|
|
||||||
yield return GetUser(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deletes the user.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user">The user.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">user</exception>
|
|
||||||
public void DeleteUser(User user)
|
|
||||||
{
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
connection.RunInTransaction(db =>
|
|
||||||
{
|
|
||||||
using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
|
|
||||||
{
|
|
||||||
statement.TryBind("@id", user.InternalId);
|
|
||||||
statement.MoveNext();
|
|
||||||
}
|
|
||||||
}, TransactionMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
public class DeviceId
|
public class DeviceId
|
||||||
{
|
{
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<DeviceId> _logger;
|
||||||
|
|
||||||
private readonly object _syncLock = new object();
|
private readonly object _syncLock = new object();
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
|
public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_logger = loggerFactory.CreateLogger("SystemId");
|
_logger = loggerFactory.CreateLogger<DeviceId>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Value => _id ?? (_id = GetDeviceId());
|
public string Value => _id ?? (_id = GetDeviceId());
|
||||||
|
|
|
@ -5,10 +5,11 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Model.Devices;
|
using MediaBrowser.Model.Devices;
|
||||||
|
@ -16,7 +17,6 @@ using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using MediaBrowser.Model.Users;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Devices
|
namespace Emby.Server.Implementations.Devices
|
||||||
{
|
{
|
||||||
|
@ -27,11 +27,10 @@ namespace Emby.Server.Implementations.Devices
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IAuthenticationRepository _authRepo;
|
private readonly IAuthenticationRepository _authRepo;
|
||||||
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
|
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
|
||||||
|
private readonly object _capabilitiesSyncLock = new object();
|
||||||
|
|
||||||
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
|
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
|
||||||
|
|
||||||
private readonly object _capabilitiesSyncLock = new object();
|
|
||||||
|
|
||||||
public DeviceManager(
|
public DeviceManager(
|
||||||
IAuthenticationRepository authRepo,
|
IAuthenticationRepository authRepo,
|
||||||
IJsonSerializer json,
|
IJsonSerializer json,
|
||||||
|
@ -62,13 +61,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
{
|
{
|
||||||
_authRepo.UpdateDeviceOptions(deviceId, options);
|
_authRepo.UpdateDeviceOptions(deviceId, options);
|
||||||
|
|
||||||
if (DeviceOptionsUpdated != null)
|
DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options)));
|
||||||
{
|
|
||||||
DeviceOptionsUpdated(this, new GenericEventArgs<Tuple<string, DeviceOptions>>()
|
|
||||||
{
|
|
||||||
Argument = new Tuple<string, DeviceOptions>(deviceId, options)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceOptions GetDeviceOptions(string deviceId)
|
public DeviceOptions GetDeviceOptions(string deviceId)
|
||||||
|
@ -119,7 +112,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
{
|
{
|
||||||
IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
|
IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
|
||||||
{
|
{
|
||||||
//UserId = query.UserId
|
// UserId = query.UserId
|
||||||
HasUser = true
|
HasUser = true
|
||||||
}).Items;
|
}).Items;
|
||||||
|
|
||||||
|
@ -176,12 +169,18 @@ namespace Emby.Server.Implementations.Devices
|
||||||
{
|
{
|
||||||
throw new ArgumentException("user not found");
|
throw new ArgumentException("user not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(deviceId))
|
if (string.IsNullOrEmpty(deviceId))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(deviceId));
|
throw new ArgumentNullException(nameof(deviceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CanAccessDevice(user.Policy, deviceId))
|
if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var capabilities = GetCapabilities(deviceId);
|
var capabilities = GetCapabilities(deviceId);
|
||||||
|
|
||||||
|
@ -193,20 +192,5 @@ namespace Emby.Server.Implementations.Devices
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CanAccessDevice(UserPolicy policy, string id)
|
|
||||||
{
|
|
||||||
if (policy.EnableAllDevices)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (policy.IsAdministrator)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
|
@ -24,12 +24,20 @@ using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Book = MediaBrowser.Controller.Entities.Book;
|
||||||
|
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||||
|
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
||||||
|
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
|
||||||
|
using Person = MediaBrowser.Controller.Entities.Person;
|
||||||
|
using Photo = MediaBrowser.Controller.Entities.Photo;
|
||||||
|
using Season = MediaBrowser.Controller.Entities.TV.Season;
|
||||||
|
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Dto
|
namespace Emby.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
public class DtoService : IDtoService
|
public class DtoService : IDtoService
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<DtoService> _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserDataManager _userDataRepository;
|
private readonly IUserDataManager _userDataRepository;
|
||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
|
@ -66,7 +74,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a BaseItem to a DTOBaseItem
|
/// Converts a BaseItem to a DTOBaseItem.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="fields">The fields.</param>
|
/// <param name="fields">The fields.</param>
|
||||||
|
@ -269,6 +277,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
dto.EpisodeTitle = dto.Name;
|
dto.EpisodeTitle = dto.Name;
|
||||||
dto.Name = dto.SeriesName;
|
dto.Name = dto.SeriesName;
|
||||||
}
|
}
|
||||||
|
|
||||||
liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
|
liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,6 +293,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var containers = container.Split(new[] { ',' });
|
var containers = container.Split(new[] { ',' });
|
||||||
if (containers.Length < 2)
|
if (containers.Length < 2)
|
||||||
{
|
{
|
||||||
|
@ -384,7 +394,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.ChildCount))
|
if (options.ContainsField(ItemFields.ChildCount))
|
||||||
{
|
{
|
||||||
dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user);
|
dto.ChildCount ??= GetChildCount(folder, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +408,6 @@ namespace Emby.Server.Implementations.Dto
|
||||||
dto.DateLastMediaAdded = folder.DateLastMediaAdded;
|
dto.DateLastMediaAdded = folder.DateLastMediaAdded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (options.EnableUserData)
|
if (options.EnableUserData)
|
||||||
|
@ -414,7 +423,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.BasicSyncInfo))
|
if (options.ContainsField(ItemFields.BasicSyncInfo))
|
||||||
{
|
{
|
||||||
var userCanSync = user != null && user.Policy.EnableContentDownloading;
|
var userCanSync = user != null && user.HasPermission(PermissionKind.EnableContentDownloading);
|
||||||
if (userCanSync && item.SupportsExternalTransfer)
|
if (userCanSync && item.SupportsExternalTransfer)
|
||||||
{
|
{
|
||||||
dto.SupportsSync = true;
|
dto.SupportsSync = true;
|
||||||
|
@ -435,7 +444,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets client-side Id of a server-side BaseItem
|
/// Gets client-side Id of a server-side BaseItem.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
|
@ -449,6 +458,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
dto.SeriesName = item.SeriesName;
|
dto.SeriesName = item.SeriesName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetPhotoProperties(BaseItemDto dto, Photo item)
|
private static void SetPhotoProperties(BaseItemDto dto, Photo item)
|
||||||
{
|
{
|
||||||
dto.CameraMake = item.CameraMake;
|
dto.CameraMake = item.CameraMake;
|
||||||
|
@ -530,7 +540,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attaches People DTO's to a DTOBaseItem
|
/// Attaches People DTO's to a DTOBaseItem.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dto">The dto.</param>
|
/// <param name="dto">The dto.</param>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
|
@ -547,22 +557,27 @@ namespace Emby.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.IsType(PersonType.GuestStar))
|
if (i.IsType(PersonType.GuestStar))
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.IsType(PersonType.Director))
|
if (i.IsType(PersonType.Director))
|
||||||
{
|
{
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.IsType(PersonType.Writer))
|
if (i.IsType(PersonType.Writer))
|
||||||
{
|
{
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.IsType(PersonType.Producer))
|
if (i.IsType(PersonType.Producer))
|
||||||
{
|
{
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.IsType(PersonType.Composer))
|
if (i.IsType(PersonType.Composer))
|
||||||
{
|
{
|
||||||
return 4;
|
return 4;
|
||||||
|
@ -586,7 +601,6 @@ namespace Emby.Server.Implementations.Dto
|
||||||
_logger.LogError(ex, "Error getting person {Name}", c);
|
_logger.LogError(ex, "Error getting person {Name}", c);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).Where(i => i != null)
|
}).Where(i => i != null)
|
||||||
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(x => x.First())
|
.Select(x => x.First())
|
||||||
|
@ -605,8 +619,9 @@ namespace Emby.Server.Implementations.Dto
|
||||||
|
|
||||||
if (dictionary.TryGetValue(person.Name, out Person entity))
|
if (dictionary.TryGetValue(person.Name, out Person entity))
|
||||||
{
|
{
|
||||||
baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
|
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
|
||||||
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
|
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
baseItemPerson.ImageBlurHashes = dto.ImageBlurHashes;
|
||||||
list.Add(baseItemPerson);
|
list.Add(baseItemPerson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -654,8 +669,72 @@ namespace Emby.Server.Implementations.Dto
|
||||||
return _libraryManager.GetGenreId(name);
|
return _libraryManager.GetGenreId(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
|
||||||
|
{
|
||||||
|
var image = item.GetImageInfo(imageType, imageIndex);
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
return GetTagAndFillBlurhash(dto, item, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
|
||||||
|
{
|
||||||
|
var tag = GetImageCacheTag(item, image);
|
||||||
|
if (!string.IsNullOrEmpty(image.BlurHash))
|
||||||
|
{
|
||||||
|
if (dto.ImageBlurHashes == null)
|
||||||
|
{
|
||||||
|
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dto.ImageBlurHashes.ContainsKey(image.Type))
|
||||||
|
{
|
||||||
|
dto.ImageBlurHashes[image.Type] = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
dto.ImageBlurHashes[image.Type][tag] = image.BlurHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
|
||||||
|
{
|
||||||
|
return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInfo> images)
|
||||||
|
{
|
||||||
|
var tags = GetImageTags(item, images);
|
||||||
|
var hashes = new Dictionary<string, string>();
|
||||||
|
for (int i = 0; i < images.Count; i++)
|
||||||
|
{
|
||||||
|
var img = images[i];
|
||||||
|
if (!string.IsNullOrEmpty(img.BlurHash))
|
||||||
|
{
|
||||||
|
var tag = tags[i];
|
||||||
|
hashes[tag] = img.BlurHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashes.Count > 0)
|
||||||
|
{
|
||||||
|
if (dto.ImageBlurHashes == null)
|
||||||
|
{
|
||||||
|
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
dto.ImageBlurHashes[imageType] = hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets simple property values on a DTOBaseItem
|
/// Sets simple property values on a DTOBaseItem.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dto">The dto.</param>
|
/// <param name="dto">The dto.</param>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
|
@ -674,8 +753,8 @@ namespace Emby.Server.Implementations.Dto
|
||||||
dto.LockData = item.IsLocked;
|
dto.LockData = item.IsLocked;
|
||||||
dto.ForcedSortName = item.ForcedSortName;
|
dto.ForcedSortName = item.ForcedSortName;
|
||||||
}
|
}
|
||||||
dto.Container = item.Container;
|
|
||||||
|
|
||||||
|
dto.Container = item.Container;
|
||||||
dto.EndDate = item.EndDate;
|
dto.EndDate = item.EndDate;
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.ExternalUrls))
|
if (options.ContainsField(ItemFields.ExternalUrls))
|
||||||
|
@ -694,10 +773,12 @@ namespace Emby.Server.Implementations.Dto
|
||||||
dto.AspectRatio = hasAspectRatio.AspectRatio;
|
dto.AspectRatio = hasAspectRatio.AspectRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
||||||
|
|
||||||
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
|
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
|
||||||
if (backdropLimit > 0)
|
if (backdropLimit > 0)
|
||||||
{
|
{
|
||||||
dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
|
dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.ScreenshotImageTags))
|
if (options.ContainsField(ItemFields.ScreenshotImageTags))
|
||||||
|
@ -705,7 +786,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
|
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
|
||||||
if (screenshotLimit > 0)
|
if (screenshotLimit > 0)
|
||||||
{
|
{
|
||||||
dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
|
dto.ScreenshotImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Screenshot, screenshotLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,12 +802,11 @@ namespace Emby.Server.Implementations.Dto
|
||||||
|
|
||||||
// Prevent implicitly captured closure
|
// Prevent implicitly captured closure
|
||||||
var currentItem = item;
|
var currentItem = item;
|
||||||
foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type))
|
foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
|
||||||
.ToList())
|
|
||||||
{
|
{
|
||||||
if (options.GetImageLimit(image.Type) > 0)
|
if (options.GetImageLimit(image.Type) > 0)
|
||||||
{
|
{
|
||||||
var tag = GetImageCacheTag(item, image);
|
var tag = GetTagAndFillBlurhash(dto, item, image);
|
||||||
|
|
||||||
if (tag != null)
|
if (tag != null)
|
||||||
{
|
{
|
||||||
|
@ -871,11 +951,10 @@ namespace Emby.Server.Implementations.Dto
|
||||||
if (albumParent != null)
|
if (albumParent != null)
|
||||||
{
|
{
|
||||||
dto.AlbumId = albumParent.Id;
|
dto.AlbumId = albumParent.Id;
|
||||||
|
dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
|
||||||
dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (options.ContainsField(ItemFields.MediaSourceCount))
|
// if (options.ContainsField(ItemFields.MediaSourceCount))
|
||||||
//{
|
//{
|
||||||
// Songs always have one
|
// Songs always have one
|
||||||
//}
|
//}
|
||||||
|
@ -885,13 +964,13 @@ namespace Emby.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
dto.Artists = hasArtist.Artists;
|
dto.Artists = hasArtist.Artists;
|
||||||
|
|
||||||
//var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
|
// var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
|
||||||
//{
|
//{
|
||||||
// EnableTotalRecordCount = false,
|
// EnableTotalRecordCount = false,
|
||||||
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
||||||
//});
|
//});
|
||||||
|
|
||||||
//dto.ArtistItems = artistItems.Items
|
// dto.ArtistItems = artistItems.Items
|
||||||
// .Select(i =>
|
// .Select(i =>
|
||||||
// {
|
// {
|
||||||
// var artist = i.Item1;
|
// var artist = i.Item1;
|
||||||
|
@ -904,7 +983,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
// .ToList();
|
// .ToList();
|
||||||
|
|
||||||
// Include artists that are not in the database yet, e.g., just added via metadata editor
|
// Include artists that are not in the database yet, e.g., just added via metadata editor
|
||||||
//var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
|
// var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
|
||||||
dto.ArtistItems = hasArtist.Artists
|
dto.ArtistItems = hasArtist.Artists
|
||||||
//.Except(foundArtists, new DistinctNameComparer())
|
//.Except(foundArtists, new DistinctNameComparer())
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
|
@ -929,7 +1008,6 @@ namespace Emby.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}).Where(i => i != null).ToArray();
|
}).Where(i => i != null).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -938,13 +1016,13 @@ namespace Emby.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
||||||
|
|
||||||
//var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
|
// var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
|
||||||
//{
|
//{
|
||||||
// EnableTotalRecordCount = false,
|
// EnableTotalRecordCount = false,
|
||||||
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
||||||
//});
|
//});
|
||||||
|
|
||||||
//dto.AlbumArtists = artistItems.Items
|
// dto.AlbumArtists = artistItems.Items
|
||||||
// .Select(i =>
|
// .Select(i =>
|
||||||
// {
|
// {
|
||||||
// var artist = i.Item1;
|
// var artist = i.Item1;
|
||||||
|
@ -980,7 +1058,6 @@ namespace Emby.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}).Where(i => i != null).ToArray();
|
}).Where(i => i != null).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1094,12 +1171,12 @@ namespace Emby.Server.Implementations.Dto
|
||||||
|
|
||||||
// this block will add the series poster for episodes without a poster
|
// this block will add the series poster for episodes without a poster
|
||||||
// TODO maybe remove the if statement entirely
|
// TODO maybe remove the if statement entirely
|
||||||
//if (options.ContainsField(ItemFields.SeriesPrimaryImage))
|
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
|
||||||
{
|
{
|
||||||
episodeSeries = episodeSeries ?? episode.Series;
|
episodeSeries = episodeSeries ?? episode.Series;
|
||||||
if (episodeSeries != null)
|
if (episodeSeries != null)
|
||||||
{
|
{
|
||||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
|
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1140,12 +1217,12 @@ namespace Emby.Server.Implementations.Dto
|
||||||
|
|
||||||
// this block will add the series poster for seasons without a poster
|
// this block will add the series poster for seasons without a poster
|
||||||
// TODO maybe remove the if statement entirely
|
// TODO maybe remove the if statement entirely
|
||||||
//if (options.ContainsField(ItemFields.SeriesPrimaryImage))
|
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
|
||||||
{
|
{
|
||||||
series = series ?? season.Series;
|
series = series ?? season.Series;
|
||||||
if (series != null)
|
if (series != null)
|
||||||
{
|
{
|
||||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
|
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1275,9 +1352,10 @@ namespace Emby.Server.Implementations.Dto
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
dto.ParentLogoItemId = GetDtoId(parent);
|
dto.ParentLogoItemId = GetDtoId(parent);
|
||||||
dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
|
dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null)
|
if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null)
|
||||||
{
|
{
|
||||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
|
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
|
||||||
|
@ -1285,9 +1363,10 @@ namespace Emby.Server.Implementations.Dto
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
dto.ParentArtItemId = GetDtoId(parent);
|
dto.ParentArtItemId = GetDtoId(parent);
|
||||||
dto.ParentArtImageTag = GetImageCacheTag(parent, image);
|
dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
|
if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
|
||||||
{
|
{
|
||||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
|
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
|
||||||
|
@ -1295,9 +1374,10 @@ namespace Emby.Server.Implementations.Dto
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
dto.ParentThumbItemId = GetDtoId(parent);
|
dto.ParentThumbItemId = GetDtoId(parent);
|
||||||
dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
|
dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0)))
|
if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0)))
|
||||||
{
|
{
|
||||||
var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
|
var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
|
||||||
|
@ -1305,7 +1385,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
if (images.Count > 0)
|
if (images.Count > 0)
|
||||||
{
|
{
|
||||||
dto.ParentBackdropItemId = GetDtoId(parent);
|
dto.ParentBackdropItemId = GetDtoId(parent);
|
||||||
dto.ParentBackdropImageTags = GetImageTags(parent, images);
|
dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="IPNetwork2" Version="2.4.0.126" />
|
<PackageReference Include="IPNetwork2" Version="2.5.211" />
|
||||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" />
|
<PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||||
|
@ -34,15 +34,16 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
|
||||||
<PackageReference Include="Mono.Nat" Version="2.0.1" />
|
<PackageReference Include="Mono.Nat" Version="2.0.1" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
|
||||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.25.0" />
|
<PackageReference Include="sharpcompress" Version="0.25.1" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||||
|
<PackageReference Include="DotNet.Glob" Version="3.0.9" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -53,6 +54,7 @@
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
public class ExternalPortForwarding : IServerEntryPoint
|
public class ExternalPortForwarding : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<ExternalPortForwarding> _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
|
@ -28,7 +29,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<LibraryChangedNotifier> _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The library changed sync lock.
|
/// The library changed sync lock.
|
||||||
|
@ -131,7 +132,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,7 +302,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
.Select(x => x.First())
|
.Select(x => x.First())
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None);
|
SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (LibraryUpdateTimer != null)
|
if (LibraryUpdateTimer != null)
|
||||||
{
|
{
|
||||||
|
@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
/// <param name="foldersAddedTo">The folders added to.</param>
|
/// <param name="foldersAddedTo">The folders added to.</param>
|
||||||
/// <param name="foldersRemovedFrom">The folders removed from.</param>
|
/// <param name="foldersRemovedFrom">The folders removed from.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
private async void SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
|
private async Task SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var userIds = _sessionManager.Sessions
|
var userIds = _sessionManager.Sessions
|
||||||
.Select(i => i.UserId)
|
.Select(i => i.UserId)
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
|
@ -17,7 +18,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
private readonly ILiveTvManager _liveTvManager;
|
private readonly ILiveTvManager _liveTvManager;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<RecordingNotifier> _logger;
|
||||||
|
|
||||||
public RecordingNotifier(
|
public RecordingNotifier(
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
|
@ -42,29 +43,29 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||||
{
|
{
|
||||||
SendMessage("SeriesTimerCreated", e.Argument);
|
await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||||
{
|
{
|
||||||
SendMessage("TimerCreated", e.Argument);
|
await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||||
{
|
{
|
||||||
SendMessage("SeriesTimerCancelled", e.Argument);
|
await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||||
{
|
{
|
||||||
SendMessage("TimerCancelled", e.Argument);
|
await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void SendMessage(string name, TimerEventInfo info)
|
private async Task SendMessage(string name, TimerEventInfo info)
|
||||||
{
|
{
|
||||||
var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList();
|
var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Providers;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Tasks;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.EntryPoints
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class RefreshUsersMetadata.
|
|
||||||
/// </summary>
|
|
||||||
public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The user manager.
|
|
||||||
/// </summary>
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
|
|
||||||
/// </summary>
|
|
||||||
public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => "Refresh Users";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Key => "RefreshUsers";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => "Refresh user infos";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Category => "Library";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsEnabled => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsLogged => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
|
||||||
{
|
|
||||||
foreach (var user in _userManager.Users)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
|
||||||
{
|
|
||||||
return new[]
|
|
||||||
{
|
|
||||||
new TaskTriggerInfo
|
|
||||||
{
|
|
||||||
IntervalTicks = TimeSpan.FromDays(1).Ticks,
|
|
||||||
Type = TaskTriggerInfo.TriggerInterval
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,15 +3,16 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Common.Updates;
|
using MediaBrowser.Common.Updates;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
|
using MediaBrowser.Model.Updates;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.EntryPoints
|
namespace Emby.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
|
@ -67,10 +68,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task RunAsync()
|
public Task RunAsync()
|
||||||
{
|
{
|
||||||
_userManager.UserDeleted += OnUserDeleted;
|
_userManager.OnUserDeleted += OnUserDeleted;
|
||||||
_userManager.UserUpdated += OnUserUpdated;
|
_userManager.OnUserUpdated += OnUserUpdated;
|
||||||
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
|
||||||
_userManager.UserConfigurationUpdated += OnUserConfigurationUpdated;
|
|
||||||
|
|
||||||
_appHost.HasPendingRestartChanged += OnHasPendingRestartChanged;
|
_appHost.HasPendingRestartChanged += OnHasPendingRestartChanged;
|
||||||
|
|
||||||
|
@ -85,29 +84,29 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPackageInstalling(object sender, InstallationEventArgs e)
|
private async void OnPackageInstalling(object sender, InstallationInfo e)
|
||||||
{
|
{
|
||||||
SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo);
|
await SendMessageToAdminSessions("PackageInstalling", e).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPackageInstallationCancelled(object sender, InstallationEventArgs e)
|
private async void OnPackageInstallationCancelled(object sender, InstallationInfo e)
|
||||||
{
|
{
|
||||||
SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo);
|
await SendMessageToAdminSessions("PackageInstallationCancelled", e).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPackageInstallationCompleted(object sender, InstallationEventArgs e)
|
private async void OnPackageInstallationCompleted(object sender, InstallationInfo e)
|
||||||
{
|
{
|
||||||
SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo);
|
await SendMessageToAdminSessions("PackageInstallationCompleted", e).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||||
{
|
{
|
||||||
SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo);
|
await SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||||
{
|
{
|
||||||
SendMessageToAdminSessions("ScheduledTaskEnded", e.Result);
|
await SendMessageToAdminSessions("ScheduledTaskEnded", e.Result).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -115,9 +114,9 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The sender.</param>
|
/// <param name="sender">The sender.</param>
|
||||||
/// <param name="e">The e.</param>
|
/// <param name="e">The e.</param>
|
||||||
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
private async void OnPluginUninstalled(object sender, IPlugin e)
|
||||||
{
|
{
|
||||||
SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo());
|
await SendMessageToAdminSessions("PluginUninstalled", e).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -125,9 +124,9 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The source of the event.</param>
|
/// <param name="sender">The source of the event.</param>
|
||||||
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
|
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
|
||||||
private void OnHasPendingRestartChanged(object sender, EventArgs e)
|
private async void OnHasPendingRestartChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_sessionManager.SendRestartRequiredNotification(CancellationToken.None);
|
await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -135,11 +134,11 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The sender.</param>
|
/// <param name="sender">The sender.</param>
|
||||||
/// <param name="e">The e.</param>
|
/// <param name="e">The e.</param>
|
||||||
private void OnUserUpdated(object sender, GenericEventArgs<User> e)
|
private async void OnUserUpdated(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
var dto = _userManager.GetUserDto(e.Argument);
|
var dto = _userManager.GetUserDto(e.Argument);
|
||||||
|
|
||||||
SendMessageToUserSession(e.Argument, "UserUpdated", dto);
|
await SendMessageToUserSession(e.Argument, "UserUpdated", dto).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -147,26 +146,12 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The sender.</param>
|
/// <param name="sender">The sender.</param>
|
||||||
/// <param name="e">The e.</param>
|
/// <param name="e">The e.</param>
|
||||||
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture));
|
await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
private async Task SendMessageToAdminSessions<T>(string name, T data)
|
||||||
{
|
|
||||||
var dto = _userManager.GetUserDto(e.Argument);
|
|
||||||
|
|
||||||
SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUserConfigurationUpdated(object sender, GenericEventArgs<User> e)
|
|
||||||
{
|
|
||||||
var dto = _userManager.GetUserDto(e.Argument);
|
|
||||||
|
|
||||||
SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void SendMessageToAdminSessions<T>(string name, T data)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -174,11 +159,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void SendMessageToUserSession<T>(User user, string name, T data)
|
private async Task SendMessageToUserSession<T>(User user, string name, T data)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -190,7 +174,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,10 +192,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
if (dispose)
|
if (dispose)
|
||||||
{
|
{
|
||||||
_userManager.UserDeleted -= OnUserDeleted;
|
_userManager.OnUserDeleted -= OnUserDeleted;
|
||||||
_userManager.UserUpdated -= OnUserUpdated;
|
_userManager.OnUserUpdated -= OnUserUpdated;
|
||||||
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
|
||||||
_userManager.UserConfigurationUpdated -= OnUserConfigurationUpdated;
|
|
||||||
|
|
||||||
_installationManager.PluginUninstalled -= OnPluginUninstalled;
|
_installationManager.PluginUninstalled -= OnPluginUninstalled;
|
||||||
_installationManager.PackageInstalling -= OnPackageInstalling;
|
_installationManager.PackageInstalling -= OnPackageInstalling;
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Udp;
|
using Emby.Server.Implementations.Udp;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.EntryPoints
|
namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
@ -20,8 +21,9 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger.
|
/// The logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<UdpServerEntryPoint> _logger;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The UDP server.
|
/// The UDP server.
|
||||||
|
@ -35,19 +37,20 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UdpServerEntryPoint(
|
public UdpServerEntryPoint(
|
||||||
ILogger<UdpServerEntryPoint> logger,
|
ILogger<UdpServerEntryPoint> logger,
|
||||||
IServerApplicationHost appHost)
|
IServerApplicationHost appHost,
|
||||||
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
|
_config = configuration;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task RunAsync()
|
public Task RunAsync()
|
||||||
{
|
{
|
||||||
_udpServer = new UdpServer(_logger, _appHost);
|
_udpServer = new UdpServer(_logger, _appHost, _config);
|
||||||
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
|
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class HttpClientManager : IHttpClient
|
public class HttpClientManager : IHttpClient
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<HttpClientManager> _logger;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IApplicationHost _appHost;
|
private readonly IApplicationHost _appHost;
|
||||||
|
@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||||
=> SendAsync(options, HttpMethod.Get);
|
=> SendAsync(options, HttpMethod.Get);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a GET request and returns the resulting stream
|
/// Performs a GET request and returns the resulting stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The options.</param>
|
/// <param name="options">The options.</param>
|
||||||
/// <returns>Task{Stream}.</returns>
|
/// <returns>Task{Stream}.</returns>
|
||||||
|
|
|
@ -32,12 +32,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _options
|
/// The _options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
|
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _requested ranges
|
/// The _requested ranges.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<KeyValuePair<long, long?>> _requestedRanges;
|
private List<KeyValuePair<long, long?>> _requestedRanges;
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
|
public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<HttpListenerHost> _logger;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
|
@ -210,16 +210,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog)
|
private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog, bool ignoreStackTrace)
|
||||||
{
|
{
|
||||||
bool ignoreStackTrace =
|
|
||||||
ex is SocketException
|
|
||||||
|| ex is IOException
|
|
||||||
|| ex is OperationCanceledException
|
|
||||||
|| ex is SecurityException
|
|
||||||
|| ex is AuthenticationException
|
|
||||||
|| ex is FileNotFoundException;
|
|
||||||
|
|
||||||
if (ignoreStackTrace)
|
if (ignoreStackTrace)
|
||||||
{
|
{
|
||||||
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
|
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
|
||||||
|
@ -238,7 +230,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
httpRes.StatusCode = statusCode;
|
httpRes.StatusCode = statusCode;
|
||||||
|
|
||||||
var errContent = NormalizeExceptionMessage(ex) ?? string.Empty;
|
var errContent = _hostEnvironment.IsDevelopment()
|
||||||
|
? (NormalizeExceptionMessage(ex) ?? string.Empty)
|
||||||
|
: "Error processing request.";
|
||||||
httpRes.ContentType = "text/plain";
|
httpRes.ContentType = "text/plain";
|
||||||
httpRes.ContentLength = errContent.Length;
|
httpRes.ContentLength = errContent.Length;
|
||||||
await httpRes.WriteAsync(errContent).ConfigureAwait(false);
|
await httpRes.WriteAsync(errContent).ConfigureAwait(false);
|
||||||
|
@ -405,7 +399,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
var response = context.Response;
|
var response = context.Response;
|
||||||
var localPath = context.Request.Path.ToString();
|
var localPath = context.Request.Path.ToString();
|
||||||
|
|
||||||
var req = new WebSocketSharpRequest(request, response, request.Path, _logger);
|
var req = new WebSocketSharpRequest(request, response, request.Path);
|
||||||
return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted);
|
return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,6 +453,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
httpRes.Headers.Add(key, value);
|
httpRes.Headers.Add(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
httpRes.ContentType = "text/plain";
|
httpRes.ContentType = "text/plain";
|
||||||
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
|
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
|
@ -505,14 +500,32 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
var requestInnerEx = GetActualException(requestEx);
|
var requestInnerEx = GetActualException(requestEx);
|
||||||
var statusCode = GetStatusCode(requestInnerEx);
|
var statusCode = GetStatusCode(requestInnerEx);
|
||||||
|
|
||||||
// Do not handle 500 server exceptions manually when in development mode
|
foreach (var (key, value) in GetDefaultCorsHeaders(httpReq))
|
||||||
// The framework-defined development exception page will be returned instead
|
{
|
||||||
if (statusCode == 500 && _hostEnvironment.IsDevelopment())
|
if (!httpRes.Headers.ContainsKey(key))
|
||||||
|
{
|
||||||
|
httpRes.Headers.Add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ignoreStackTrace =
|
||||||
|
requestInnerEx is SocketException
|
||||||
|
|| requestInnerEx is IOException
|
||||||
|
|| requestInnerEx is OperationCanceledException
|
||||||
|
|| requestInnerEx is SecurityException
|
||||||
|
|| requestInnerEx is AuthenticationException
|
||||||
|
|| requestInnerEx is FileNotFoundException;
|
||||||
|
|
||||||
|
// Do not handle 500 server exceptions manually when in development mode.
|
||||||
|
// Instead, re-throw the exception so it can be handled by the DeveloperExceptionPageMiddleware.
|
||||||
|
// However, do not use the DeveloperExceptionPageMiddleware when the stack trace should be ignored,
|
||||||
|
// because it will log the stack trace when it handles the exception.
|
||||||
|
if (statusCode == 500 && !ignoreStackTrace && _hostEnvironment.IsDevelopment())
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog).ConfigureAwait(false);
|
await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog, ignoreStackTrace).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception handlerException)
|
catch (Exception handlerException)
|
||||||
{
|
{
|
||||||
|
@ -579,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the default CORS headers
|
/// Get the default CORS headers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="req"></param>
|
/// <param name="req"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger.
|
/// The logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<HttpResultFactory> _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IStreamHelper _streamHelper;
|
private readonly IStreamHelper _streamHelper;
|
||||||
|
@ -50,12 +50,13 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_streamHelper = streamHelper;
|
_streamHelper = streamHelper;
|
||||||
_logger = loggerfactory.CreateLogger("HttpResultFactory");
|
_logger = loggerfactory.CreateLogger<HttpResultFactory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the result.
|
/// Gets the result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="requestContext">The request context.</param>
|
||||||
/// <param name="content">The content.</param>
|
/// <param name="content">The content.</param>
|
||||||
/// <param name="contentType">Type of the content.</param>
|
/// <param name="contentType">Type of the content.</param>
|
||||||
/// <param name="responseHeaders">The response headers.</param>
|
/// <param name="responseHeaders">The response headers.</param>
|
||||||
|
@ -255,16 +256,20 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString();
|
var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(acceptEncoding))
|
if (!string.IsNullOrEmpty(acceptEncoding))
|
||||||
{
|
{
|
||||||
//if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
|
// if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
// return "br";
|
// return "br";
|
||||||
|
|
||||||
if (acceptEncoding.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1)
|
if (acceptEncoding.Contains("deflate", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
return "deflate";
|
return "deflate";
|
||||||
|
}
|
||||||
|
|
||||||
if (acceptEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)
|
if (acceptEncoding.Contains("gzip", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
return "gzip";
|
return "gzip";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -421,7 +426,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
|
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
|
||||||
{
|
{
|
||||||
bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
|
bool noCache = requestContext.Headers[HeaderNames.CacheControl].ToString().IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
|
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
|
||||||
|
|
||||||
if (!noCache)
|
if (!noCache)
|
||||||
|
@ -575,13 +580,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
catch (NotSupportedException)
|
catch (NotSupportedException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue)
|
if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue)
|
||||||
{
|
{
|
||||||
var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest, _logger)
|
var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest)
|
||||||
{
|
{
|
||||||
OnComplete = options.OnComplete
|
OnComplete = options.OnComplete
|
||||||
};
|
};
|
||||||
|
@ -618,8 +622,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the caching responseHeaders.
|
/// Adds the caching responseHeaders.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration,
|
private void AddCachingHeaders(
|
||||||
bool noCache, DateTime? lastModifiedDate)
|
IDictionary<string, string> responseHeaders,
|
||||||
|
TimeSpan? cacheDuration,
|
||||||
|
bool noCache,
|
||||||
|
DateTime? lastModifiedDate)
|
||||||
{
|
{
|
||||||
if (noCache)
|
if (noCache)
|
||||||
{
|
{
|
||||||
|
@ -688,7 +695,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
|
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="date">The date.</param>
|
/// <param name="date">The date.</param>
|
||||||
/// <returns>DateTime.</returns>
|
/// <returns>DateTime.</returns>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -8,13 +9,45 @@ using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.HttpServer
|
namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult
|
public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult
|
||||||
{
|
{
|
||||||
|
private const int BufferSize = 81920;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, string> _options = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
private List<KeyValuePair<long, long?>> _requestedRanges;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RangeRequestWriter" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rangeHeader">The range header.</param>
|
||||||
|
/// <param name="contentLength">The content length.</param>
|
||||||
|
/// <param name="source">The source.</param>
|
||||||
|
/// <param name="contentType">Type of the content.</param>
|
||||||
|
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
|
||||||
|
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(contentType))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(contentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
RangeHeader = rangeHeader;
|
||||||
|
SourceStream = source;
|
||||||
|
IsHeadRequest = isHeadRequest;
|
||||||
|
|
||||||
|
ContentType = contentType;
|
||||||
|
Headers[HeaderNames.ContentType] = contentType;
|
||||||
|
Headers[HeaderNames.AcceptRanges] = "bytes";
|
||||||
|
StatusCode = HttpStatusCode.PartialContent;
|
||||||
|
|
||||||
|
SetRangeValues(contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the source stream.
|
/// Gets or sets the source stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -29,19 +62,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
private long TotalContentLength { get; set; }
|
private long TotalContentLength { get; set; }
|
||||||
|
|
||||||
public Action OnComplete { get; set; }
|
public Action OnComplete { get; set; }
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
private const int BufferSize = 81920;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _options
|
|
||||||
/// </summary>
|
|
||||||
private readonly Dictionary<string, string> _options = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The us culture
|
|
||||||
/// </summary>
|
|
||||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Additional HTTP Headers
|
/// Additional HTTP Headers
|
||||||
|
@ -50,32 +70,57 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
public IDictionary<string, string> Headers => _options;
|
public IDictionary<string, string> Headers => _options;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RangeRequestWriter" /> class.
|
/// Gets the requested ranges.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rangeHeader">The range header.</param>
|
/// <value>The requested ranges.</value>
|
||||||
/// <param name="contentLength">The content length.</param>
|
protected List<KeyValuePair<long, long?>> RequestedRanges
|
||||||
/// <param name="source">The source.</param>
|
|
||||||
/// <param name="contentType">Type of the content.</param>
|
|
||||||
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
|
|
||||||
/// <param name="logger">The logger instance.</param>
|
|
||||||
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(contentType))
|
get
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(contentType));
|
if (_requestedRanges == null)
|
||||||
|
{
|
||||||
|
_requestedRanges = new List<KeyValuePair<long, long?>>();
|
||||||
|
|
||||||
|
// Example: bytes=0-,32-63
|
||||||
|
var ranges = RangeHeader.Split('=')[1].Split(',');
|
||||||
|
|
||||||
|
foreach (var range in ranges)
|
||||||
|
{
|
||||||
|
var vals = range.Split('-');
|
||||||
|
|
||||||
|
long start = 0;
|
||||||
|
long? end = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(vals[0]))
|
||||||
|
{
|
||||||
|
start = long.Parse(vals[0], CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(vals[1]))
|
||||||
|
{
|
||||||
|
end = long.Parse(vals[1], CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _requestedRanges;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RangeHeader = rangeHeader;
|
public string ContentType { get; set; }
|
||||||
SourceStream = source;
|
|
||||||
IsHeadRequest = isHeadRequest;
|
|
||||||
this._logger = logger;
|
|
||||||
|
|
||||||
ContentType = contentType;
|
public IRequest RequestContext { get; set; }
|
||||||
Headers[HeaderNames.ContentType] = contentType;
|
|
||||||
Headers[HeaderNames.AcceptRanges] = "bytes";
|
|
||||||
StatusCode = HttpStatusCode.PartialContent;
|
|
||||||
|
|
||||||
SetRangeValues(contentLength);
|
public object Response { get; set; }
|
||||||
|
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
public HttpStatusCode StatusCode
|
||||||
|
{
|
||||||
|
get => (HttpStatusCode)Status;
|
||||||
|
set => Status = (int)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -109,49 +154,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _requested ranges
|
|
||||||
/// </summary>
|
|
||||||
private List<KeyValuePair<long, long?>> _requestedRanges;
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the requested ranges.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The requested ranges.</value>
|
|
||||||
protected List<KeyValuePair<long, long?>> RequestedRanges
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_requestedRanges == null)
|
|
||||||
{
|
|
||||||
_requestedRanges = new List<KeyValuePair<long, long?>>();
|
|
||||||
|
|
||||||
// Example: bytes=0-,32-63
|
|
||||||
var ranges = RangeHeader.Split('=')[1].Split(',');
|
|
||||||
|
|
||||||
foreach (var range in ranges)
|
|
||||||
{
|
|
||||||
var vals = range.Split('-');
|
|
||||||
|
|
||||||
long start = 0;
|
|
||||||
long? end = null;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(vals[0]))
|
|
||||||
{
|
|
||||||
start = long.Parse(vals[0], UsCulture);
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrEmpty(vals[1]))
|
|
||||||
{
|
|
||||||
end = long.Parse(vals[1], UsCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _requestedRanges;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
|
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -167,59 +169,44 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
// If the requested range is "0-", we can optimize by just doing a stream copy
|
// If the requested range is "0-", we can optimize by just doing a stream copy
|
||||||
if (RangeEnd >= TotalContentLength - 1)
|
if (RangeEnd >= TotalContentLength - 1)
|
||||||
{
|
{
|
||||||
await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
|
await source.CopyToAsync(responseStream, BufferSize, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false);
|
await CopyToInternalAsync(source, responseStream, RangeLength, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (OnComplete != null)
|
OnComplete?.Invoke();
|
||||||
{
|
|
||||||
OnComplete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
|
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var array = new byte[BufferSize];
|
var array = ArrayPool<byte>.Shared.Rent(BufferSize);
|
||||||
int bytesRead;
|
try
|
||||||
while ((bytesRead = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
|
|
||||||
{
|
{
|
||||||
if (bytesRead == 0)
|
int bytesRead;
|
||||||
|
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||||
{
|
{
|
||||||
break;
|
var bytesToCopy = Math.Min(bytesRead, copyLength);
|
||||||
}
|
|
||||||
|
|
||||||
var bytesToCopy = Math.Min(bytesRead, copyLength);
|
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
|
copyLength -= bytesToCopy;
|
||||||
|
|
||||||
copyLength -= bytesToCopy;
|
if (copyLength <= 0)
|
||||||
|
{
|
||||||
if (copyLength <= 0)
|
break;
|
||||||
{
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
finally
|
||||||
|
{
|
||||||
public string ContentType { get; set; }
|
ArrayPool<byte>.Shared.Return(array);
|
||||||
|
}
|
||||||
public IRequest RequestContext { get; set; }
|
|
||||||
|
|
||||||
public object Response { get; set; }
|
|
||||||
|
|
||||||
public int Status { get; set; }
|
|
||||||
|
|
||||||
public HttpStatusCode StatusCode
|
|
||||||
{
|
|
||||||
get => (HttpStatusCode)Status;
|
|
||||||
set => Status = (int)value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
|
@ -42,11 +41,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
res.Headers.Add(key, value);
|
res.Headers.Add(key, value);
|
||||||
}
|
}
|
||||||
// Try to prevent compatibility view
|
// Try to prevent compatibility view
|
||||||
res.Headers["Access-Control-Allow-Headers"] = ("Accept, Accept-Language, Authorization, Cache-Control, " +
|
res.Headers["Access-Control-Allow-Headers"] = "Accept, Accept-Language, Authorization, Cache-Control, " +
|
||||||
"Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " +
|
"Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " +
|
||||||
"Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " +
|
"Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " +
|
||||||
"Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " +
|
"Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " +
|
||||||
"X-Emby-Authorization");
|
"X-Emby-Authorization";
|
||||||
|
|
||||||
if (dto is Exception exception)
|
if (dto is Exception exception)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Authentication;
|
|
||||||
using Emby.Server.Implementations.SocketSharp;
|
using Emby.Server.Implementations.SocketSharp;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
@ -45,11 +46,27 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
|
|
||||||
public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
|
public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
|
||||||
{
|
{
|
||||||
var req = new WebSocketSharpRequest(request, null, request.Path, _logger);
|
var req = new WebSocketSharpRequest(request, null, request.Path);
|
||||||
var user = ValidateUser(req, authAttributes);
|
var user = ValidateUser(req, authAttributes);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthorizationInfo Authenticate(HttpRequest request)
|
||||||
|
{
|
||||||
|
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||||
|
if (auth?.User == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth.User.HasPermission(PermissionKind.IsDisabled))
|
||||||
|
{
|
||||||
|
throw new SecurityException("User account has been disabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth;
|
||||||
|
}
|
||||||
|
|
||||||
private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||||
{
|
{
|
||||||
// This code is executed before the service
|
// This code is executed before the service
|
||||||
|
@ -90,7 +107,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
!string.IsNullOrEmpty(auth.Client) &&
|
!string.IsNullOrEmpty(auth.Client) &&
|
||||||
!string.IsNullOrEmpty(auth.Device))
|
!string.IsNullOrEmpty(auth.Device))
|
||||||
{
|
{
|
||||||
_sessionManager.LogSessionActivity(auth.Client,
|
_sessionManager.LogSessionActivity(
|
||||||
|
auth.Client,
|
||||||
auth.Version,
|
auth.Version,
|
||||||
auth.DeviceId,
|
auth.DeviceId,
|
||||||
auth.Device,
|
auth.Device,
|
||||||
|
@ -104,21 +122,21 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
private void ValidateUserAccess(
|
private void ValidateUserAccess(
|
||||||
User user,
|
User user,
|
||||||
IRequest request,
|
IRequest request,
|
||||||
IAuthenticationAttributes authAttribtues,
|
IAuthenticationAttributes authAttributes,
|
||||||
AuthorizationInfo auth)
|
AuthorizationInfo auth)
|
||||||
{
|
{
|
||||||
if (user.Policy.IsDisabled)
|
if (user.HasPermission(PermissionKind.IsDisabled))
|
||||||
{
|
{
|
||||||
throw new SecurityException("User account has been disabled.");
|
throw new SecurityException("User account has been disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
||||||
{
|
{
|
||||||
throw new SecurityException("User account has been disabled.");
|
throw new SecurityException("User account has been disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.IsAdministrator
|
if (!user.HasPermission(PermissionKind.IsAdministrator)
|
||||||
&& !authAttribtues.EscapeParentalControl
|
&& !authAttributes.EscapeParentalControl
|
||||||
&& !user.IsParentalScheduleAllowed())
|
&& !user.IsParentalScheduleAllowed())
|
||||||
{
|
{
|
||||||
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
|
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
|
||||||
|
@ -138,6 +156,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authAttribtues.AllowLocalOnly && request.IsLocal)
|
if (authAttribtues.AllowLocalOnly && request.IsLocal)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -180,7 +199,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
|
if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (user == null || !user.Policy.IsAdministrator)
|
if (user == null || !user.HasPermission(PermissionKind.IsAdministrator))
|
||||||
{
|
{
|
||||||
throw new SecurityException("User does not have admin access.");
|
throw new SecurityException("User does not have admin access.");
|
||||||
}
|
}
|
||||||
|
@ -188,7 +207,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
|
|
||||||
if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
|
if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (user == null || !user.Policy.EnableContentDeletion)
|
if (user == null || !user.HasPermission(PermissionKind.EnableContentDeletion))
|
||||||
{
|
{
|
||||||
throw new SecurityException("User does not have delete access.");
|
throw new SecurityException("User does not have delete access.");
|
||||||
}
|
}
|
||||||
|
@ -196,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
|
|
||||||
if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
|
if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (user == null || !user.Policy.EnableContentDownloading)
|
if (user == null || !user.HasPermission(PermissionKind.EnableContentDownloading))
|
||||||
{
|
{
|
||||||
throw new SecurityException("User does not have download access.");
|
throw new SecurityException("User does not have download access.");
|
||||||
}
|
}
|
||||||
|
@ -223,7 +242,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
throw new AuthenticationException("Access token is invalid or expired.");
|
throw new AuthenticationException("Access token is invalid or expired.");
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (!string.IsNullOrEmpty(info.UserId))
|
// if (!string.IsNullOrEmpty(info.UserId))
|
||||||
//{
|
//{
|
||||||
// var user = _userManager.GetUserById(info.UserId);
|
// var user = _userManager.GetUserById(info.UserId);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.HttpServer.Security
|
namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
|
@ -38,6 +39,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
return GetAuthorization(requestContext);
|
return GetAuthorization(requestContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext)
|
||||||
|
{
|
||||||
|
var auth = GetAuthorizationDictionary(requestContext);
|
||||||
|
var (authInfo, _) =
|
||||||
|
GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query);
|
||||||
|
return authInfo;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the authorization.
|
/// Gets the authorization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -46,7 +55,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
private AuthorizationInfo GetAuthorization(IRequest httpReq)
|
private AuthorizationInfo GetAuthorization(IRequest httpReq)
|
||||||
{
|
{
|
||||||
var auth = GetAuthorizationDictionary(httpReq);
|
var auth = GetAuthorizationDictionary(httpReq);
|
||||||
|
var (authInfo, originalAuthInfo) =
|
||||||
|
GetAuthorizationInfoFromDictionary(auth, httpReq.Headers, httpReq.QueryString);
|
||||||
|
|
||||||
|
if (originalAuthInfo != null)
|
||||||
|
{
|
||||||
|
httpReq.Items["OriginalAuthenticationInfo"] = originalAuthInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq.Items["AuthorizationInfo"] = authInfo;
|
||||||
|
return authInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (AuthorizationInfo authInfo, AuthenticationInfo originalAuthenticationInfo) GetAuthorizationInfoFromDictionary(
|
||||||
|
in Dictionary<string, string> auth,
|
||||||
|
in IHeaderDictionary headers,
|
||||||
|
in IQueryCollection queryString)
|
||||||
|
{
|
||||||
string deviceId = null;
|
string deviceId = null;
|
||||||
string device = null;
|
string device = null;
|
||||||
string client = null;
|
string client = null;
|
||||||
|
@ -64,19 +89,20 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
token = httpReq.Headers["X-Emby-Token"];
|
token = headers["X-Emby-Token"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
token = httpReq.Headers["X-MediaBrowser-Token"];
|
token = headers["X-MediaBrowser-Token"];
|
||||||
}
|
|
||||||
if (string.IsNullOrEmpty(token))
|
|
||||||
{
|
|
||||||
token = httpReq.QueryString["api_key"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = new AuthorizationInfo
|
if (string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
token = queryString["api_key"];
|
||||||
|
}
|
||||||
|
|
||||||
|
var authInfo = new AuthorizationInfo
|
||||||
{
|
{
|
||||||
Client = client,
|
Client = client,
|
||||||
Device = device,
|
Device = device,
|
||||||
|
@ -85,6 +111,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
Token = token
|
Token = token
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AuthenticationInfo originalAuthenticationInfo = null;
|
||||||
if (!string.IsNullOrWhiteSpace(token))
|
if (!string.IsNullOrWhiteSpace(token))
|
||||||
{
|
{
|
||||||
var result = _authRepo.Get(new AuthenticationInfoQuery
|
var result = _authRepo.Get(new AuthenticationInfoQuery
|
||||||
|
@ -92,81 +119,77 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
AccessToken = token
|
AccessToken = token
|
||||||
});
|
});
|
||||||
|
|
||||||
var tokenInfo = result.Items.Count > 0 ? result.Items[0] : null;
|
originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null;
|
||||||
|
|
||||||
if (tokenInfo != null)
|
if (originalAuthenticationInfo != null)
|
||||||
{
|
{
|
||||||
var updateToken = false;
|
var updateToken = false;
|
||||||
|
|
||||||
// TODO: Remove these checks for IsNullOrWhiteSpace
|
// TODO: Remove these checks for IsNullOrWhiteSpace
|
||||||
if (string.IsNullOrWhiteSpace(info.Client))
|
if (string.IsNullOrWhiteSpace(authInfo.Client))
|
||||||
{
|
{
|
||||||
info.Client = tokenInfo.AppName;
|
authInfo.Client = originalAuthenticationInfo.AppName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(info.DeviceId))
|
if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
|
||||||
{
|
{
|
||||||
info.DeviceId = tokenInfo.DeviceId;
|
authInfo.DeviceId = originalAuthenticationInfo.DeviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary. TODO - allow clients to specify that the token has been shared with a casting device
|
// Temporary. TODO - allow clients to specify that the token has been shared with a casting device
|
||||||
var allowTokenInfoUpdate = info.Client == null || info.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
|
var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(info.Device))
|
if (string.IsNullOrWhiteSpace(authInfo.Device))
|
||||||
{
|
{
|
||||||
info.Device = tokenInfo.DeviceName;
|
authInfo.Device = originalAuthenticationInfo.DeviceName;
|
||||||
}
|
}
|
||||||
|
else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
|
||||||
else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
if (allowTokenInfoUpdate)
|
if (allowTokenInfoUpdate)
|
||||||
{
|
{
|
||||||
updateToken = true;
|
updateToken = true;
|
||||||
tokenInfo.DeviceName = info.Device;
|
originalAuthenticationInfo.DeviceName = authInfo.Device;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(info.Version))
|
if (string.IsNullOrWhiteSpace(authInfo.Version))
|
||||||
{
|
{
|
||||||
info.Version = tokenInfo.AppVersion;
|
authInfo.Version = originalAuthenticationInfo.AppVersion;
|
||||||
}
|
}
|
||||||
else if (!string.Equals(info.Version, tokenInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
|
else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (allowTokenInfoUpdate)
|
if (allowTokenInfoUpdate)
|
||||||
{
|
{
|
||||||
updateToken = true;
|
updateToken = true;
|
||||||
tokenInfo.AppVersion = info.Version;
|
originalAuthenticationInfo.AppVersion = authInfo.Version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((DateTime.UtcNow - tokenInfo.DateLastActivity).TotalMinutes > 3)
|
if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3)
|
||||||
{
|
{
|
||||||
tokenInfo.DateLastActivity = DateTime.UtcNow;
|
originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow;
|
||||||
updateToken = true;
|
updateToken = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tokenInfo.UserId.Equals(Guid.Empty))
|
if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty))
|
||||||
{
|
{
|
||||||
info.User = _userManager.GetUserById(tokenInfo.UserId);
|
authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId);
|
||||||
|
|
||||||
if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase))
|
if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
tokenInfo.UserName = info.User.Name;
|
originalAuthenticationInfo.UserName = authInfo.User.Username;
|
||||||
updateToken = true;
|
updateToken = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateToken)
|
if (updateToken)
|
||||||
{
|
{
|
||||||
_authRepo.Update(tokenInfo);
|
_authRepo.Update(originalAuthenticationInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
httpReq.Items["AuthorizationInfo"] = info;
|
return (authInfo, originalAuthenticationInfo);
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -186,6 +209,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
return GetAuthorization(auth);
|
return GetAuthorization(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the auth.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpReq">The HTTP req.</param>
|
||||||
|
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||||
|
private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq)
|
||||||
|
{
|
||||||
|
var auth = httpReq.Headers["X-Emby-Authorization"];
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(auth))
|
||||||
|
{
|
||||||
|
auth = httpReq.Headers[HeaderNames.Authorization];
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetAuthorization(auth);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the authorization.
|
/// Gets the authorization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Controller.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger.
|
/// The logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<WebSocketConnection> _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The json serializer options.
|
/// The json serializer options.
|
||||||
|
@ -78,6 +78,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// <value>The last activity date.</value>
|
/// <value>The last activity date.</value>
|
||||||
public DateTime LastActivityDate { get; private set; }
|
public DateTime LastActivityDate { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public DateTime LastKeepAliveDate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the query string.
|
/// Gets or sets the query string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -218,7 +221,44 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
Connection = this
|
Connection = this
|
||||||
};
|
};
|
||||||
|
|
||||||
await OnReceive(info).ConfigureAwait(false);
|
if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
await SendKeepAliveResponse();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await OnReceive(info).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task SendKeepAliveResponse()
|
||||||
|
{
|
||||||
|
LastKeepAliveDate = DateTime.UtcNow;
|
||||||
|
return SendAsync(
|
||||||
|
new WebSocketMessage<string>
|
||||||
|
{
|
||||||
|
MessageId = Guid.NewGuid(),
|
||||||
|
MessageType = "KeepAlive"
|
||||||
|
}, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases unmanaged and - optionally - managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||||
|
protected virtual void Dispose(bool dispose)
|
||||||
|
{
|
||||||
|
if (dispose)
|
||||||
|
{
|
||||||
|
_socket.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,14 @@ using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using Emby.Server.Implementations.Library;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.IO
|
namespace Emby.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
public class LibraryMonitor : ILibraryMonitor
|
public class LibraryMonitor : ILibraryMonitor
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<LibraryMonitor> _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IServerConfigurationManager _configurationManager;
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
@ -37,38 +38,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ConcurrentDictionary<string, string> _tempIgnoredPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, string> _tempIgnoredPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Any file name ending in any of these will be ignored by the watchers.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
||||||
{
|
|
||||||
"small.jpg",
|
|
||||||
"albumart.jpg",
|
|
||||||
|
|
||||||
// WMC temp recording directories that will constantly be written to
|
|
||||||
"TempRec",
|
|
||||||
"TempSBE"
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly string[] _alwaysIgnoreSubstrings = new string[]
|
|
||||||
{
|
|
||||||
// Synology
|
|
||||||
"eaDir",
|
|
||||||
"#recycle",
|
|
||||||
".wd_tv",
|
|
||||||
".actors"
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
||||||
{
|
|
||||||
// thumbs.db
|
|
||||||
".db",
|
|
||||||
|
|
||||||
// bts sync files
|
|
||||||
".bts",
|
|
||||||
".sync"
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
|
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -297,7 +266,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
DisposeWatcher(newWatcher, false);
|
DisposeWatcher(newWatcher, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -395,12 +363,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename = Path.GetFileName(path);
|
var monitorPath = !IgnorePatterns.ShouldIgnore(path);
|
||||||
|
|
||||||
var monitorPath = !string.IsNullOrEmpty(filename) &&
|
|
||||||
!_alwaysIgnoreFiles.Contains(filename) &&
|
|
||||||
!_alwaysIgnoreExtensions.Contains(Path.GetExtension(path)) &&
|
|
||||||
_alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1);
|
|
||||||
|
|
||||||
// Ignore certain files
|
// Ignore certain files
|
||||||
var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList();
|
var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList();
|
||||||
|
@ -429,7 +392,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
monitorPath = false;
|
monitorPath = false;
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ManagedFileSystem : IFileSystem
|
public class ManagedFileSystem : IFileSystem
|
||||||
{
|
{
|
||||||
protected ILogger Logger;
|
protected ILogger<ManagedFileSystem> Logger;
|
||||||
|
|
||||||
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
|
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
|
||||||
private readonly string _tempPath;
|
private readonly string _tempPath;
|
||||||
|
@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
|
result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
|
||||||
|
|
||||||
//if (!result.IsDirectory)
|
// if (!result.IsDirectory)
|
||||||
//{
|
//{
|
||||||
// result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
// result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||||
//}
|
//}
|
||||||
|
@ -628,6 +628,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -682,6 +683,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations
|
namespace Emby.Server.Implementations
|
||||||
{
|
{
|
||||||
public interface IStartupOptions
|
public interface IStartupOptions
|
||||||
|
@ -33,8 +37,8 @@ namespace Emby.Server.Implementations
|
||||||
string RestartArgs { get; }
|
string RestartArgs { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value of the --plugin-manifest-url command line option.
|
/// Gets the value of the --published-server-url command line option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string PluginManifestUrl { get; }
|
Uri PublishedServerUrl { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
60
Emby.Server.Implementations/Images/ArtistImageProvider.cs
Normal file
60
Emby.Server.Implementations/Images/ArtistImageProvider.cs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Emby.Server.Implementations.Images;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
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.Playlists;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
|
namespace Emby.Server.Implementations.Images
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class ArtistImageProvider.
|
||||||
|
/// </summary>
|
||||||
|
public class ArtistImageProvider : BaseDynamicImageProvider<MusicArtist>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get children objects used to create an artist image.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The artist used to create the image.</param>
|
||||||
|
/// <returns>Any relevant children objects.</returns>
|
||||||
|
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||||
|
{
|
||||||
|
return Array.Empty<BaseItem>();
|
||||||
|
|
||||||
|
// TODO enable this when BaseDynamicImageProvider objects are configurable
|
||||||
|
// return _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
|
// {
|
||||||
|
// ArtistIds = new[] { item.Id },
|
||||||
|
// IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
|
||||||
|
// OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||||
|
// Limit = 4,
|
||||||
|
// Recursive = true,
|
||||||
|
// ImageTypes = new[] { ImageType.Primary },
|
||||||
|
// DtoOptions = new DtoOptions(false)
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -194,7 +194,8 @@ namespace Emby.Server.Implementations.Images
|
||||||
return outputPath;
|
return outputPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual string CreateImage(BaseItem item,
|
protected virtual string CreateImage(
|
||||||
|
BaseItem item,
|
||||||
IReadOnlyCollection<BaseItem> itemsWithImages,
|
IReadOnlyCollection<BaseItem> itemsWithImages,
|
||||||
string outputPathWithoutExtension,
|
string outputPathWithoutExtension,
|
||||||
ImageType imageType,
|
ImageType imageType,
|
||||||
|
@ -214,7 +215,12 @@ namespace Emby.Server.Implementations.Images
|
||||||
|
|
||||||
if (imageType == ImageType.Primary)
|
if (imageType == ImageType.Primary)
|
||||||
{
|
{
|
||||||
if (item is UserView || item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum)
|
if (item is UserView
|
||||||
|
|| item is Playlist
|
||||||
|
|| item is MusicGenre
|
||||||
|
|| item is Genre
|
||||||
|
|| item is PhotoAlbum
|
||||||
|
|| item is MusicArtist)
|
||||||
{
|
{
|
||||||
return CreateSquareCollage(item, itemsWithImages, outputPath);
|
return CreateSquareCollage(item, itemsWithImages, outputPath);
|
||||||
}
|
}
|
||||||
|
@ -225,7 +231,7 @@ namespace Emby.Server.Implementations.Images
|
||||||
throw new ArgumentException("Unexpected image type", nameof(imageType));
|
throw new ArgumentException("Unexpected image type", nameof(imageType));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasChanged(BaseItem item, IDirectoryService directoryServicee)
|
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
if (!Supports(item))
|
if (!Supports(item))
|
||||||
{
|
{
|
||||||
|
@ -236,6 +242,7 @@ namespace Emby.Server.Implementations.Images
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb))
|
if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -11,7 +13,7 @@ using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.UserViews
|
namespace Emby.Server.Implementations.Images
|
||||||
{
|
{
|
||||||
public class CollectionFolderImageProvider : BaseDynamicImageProvider<CollectionFolder>
|
public class CollectionFolderImageProvider : BaseDynamicImageProvider<CollectionFolder>
|
||||||
{
|
{
|
||||||
|
@ -69,7 +71,6 @@ namespace Emby.Server.Implementations.UserViews
|
||||||
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
|
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
|
||||||
},
|
},
|
||||||
IncludeItemTypes = includeItemTypes
|
IncludeItemTypes = includeItemTypes
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -14,18 +16,16 @@ using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.UserViews
|
namespace Emby.Server.Implementations.Images
|
||||||
{
|
{
|
||||||
public class DynamicImageProvider : BaseDynamicImageProvider<UserView>
|
public class DynamicImageProvider : BaseDynamicImageProvider<UserView>
|
||||||
{
|
{
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
|
|
||||||
public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager)
|
public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager)
|
||||||
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||||
|
@ -78,7 +78,6 @@ namespace Emby.Server.Implementations.UserViews
|
||||||
}
|
}
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
|
|
||||||
}).GroupBy(x => x.Id)
|
}).GroupBy(x => x.Id)
|
||||||
.Select(x => x.First());
|
.Select(x => x.First());
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Server.Implementations.Images;
|
using Emby.Server.Implementations.Images;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
@ -11,7 +13,7 @@ using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.UserViews
|
namespace Emby.Server.Implementations.Images
|
||||||
{
|
{
|
||||||
public abstract class BaseFolderImageProvider<T> : BaseDynamicImageProvider<T>
|
public abstract class BaseFolderImageProvider<T> : BaseDynamicImageProvider<T>
|
||||||
where T : Folder, new()
|
where T : Folder, new()
|
||||||
|
@ -75,16 +77,12 @@ namespace Emby.Server.Implementations.UserViews
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var folder = item as Folder;
|
if (item is Folder && item.IsTopParent)
|
||||||
if (folder != null)
|
|
||||||
{
|
{
|
||||||
if (folder.IsTopParent)
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
//return item.SourceType == SourceType.Library;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Server.Implementations.Images;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
|
@ -9,66 +9,21 @@ using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Playlists;
|
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Playlists
|
namespace Emby.Server.Implementations.Images
|
||||||
{
|
{
|
||||||
public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist>
|
/// <summary>
|
||||||
{
|
/// Class MusicGenreImageProvider.
|
||||||
public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
/// </summary>
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
|
||||||
{
|
|
||||||
var playlist = (Playlist)item;
|
|
||||||
|
|
||||||
return playlist.GetManageableItems()
|
|
||||||
.Select(i =>
|
|
||||||
{
|
|
||||||
var subItem = i.Item2;
|
|
||||||
|
|
||||||
var episode = subItem as Episode;
|
|
||||||
|
|
||||||
if (episode != null)
|
|
||||||
{
|
|
||||||
var series = episode.Series;
|
|
||||||
if (series != null && series.HasImage(ImageType.Primary))
|
|
||||||
{
|
|
||||||
return series;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subItem.HasImage(ImageType.Primary))
|
|
||||||
{
|
|
||||||
return subItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parent = subItem.GetOwner() ?? subItem.GetParent();
|
|
||||||
|
|
||||||
if (parent != null && parent.HasImage(ImageType.Primary))
|
|
||||||
{
|
|
||||||
if (parent is MusicAlbum)
|
|
||||||
{
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.Where(i => i != null)
|
|
||||||
.GroupBy(x => x.Id)
|
|
||||||
.Select(x => x.First())
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MusicGenreImageProvider : BaseDynamicImageProvider<MusicGenre>
|
public class MusicGenreImageProvider : BaseDynamicImageProvider<MusicGenre>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager.
|
||||||
|
/// </summary>
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||||
|
@ -76,6 +31,11 @@ namespace Emby.Server.Implementations.Playlists
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get children objects used to create an music genre image.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The music genre used to create the image.</param>
|
||||||
|
/// <returns>Any relevant children objects.</returns>
|
||||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return _libraryManager.GetItemList(new InternalItemsQuery
|
return _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
|
@ -91,8 +51,14 @@ namespace Emby.Server.Implementations.Playlists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class GenreImageProvider.
|
||||||
|
/// </summary>
|
||||||
public class GenreImageProvider : BaseDynamicImageProvider<Genre>
|
public class GenreImageProvider : BaseDynamicImageProvider<Genre>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager.
|
||||||
|
/// </summary>
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||||
|
@ -100,6 +66,11 @@ namespace Emby.Server.Implementations.Playlists
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get children objects used to create an genre image.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The genre used to create the image.</param>
|
||||||
|
/// <returns>Any relevant children objects.</returns>
|
||||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return _libraryManager.GetItemList(new InternalItemsQuery
|
return _libraryManager.GetItemList(new InternalItemsQuery
|
66
Emby.Server.Implementations/Images/PlaylistImageProvider.cs
Normal file
66
Emby.Server.Implementations/Images/PlaylistImageProvider.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
|
using MediaBrowser.Controller.Playlists;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
|
namespace Emby.Server.Implementations.Images
|
||||||
|
{
|
||||||
|
public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist>
|
||||||
|
{
|
||||||
|
public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||||
|
{
|
||||||
|
var playlist = (Playlist)item;
|
||||||
|
|
||||||
|
return playlist.GetManageableItems()
|
||||||
|
.Select(i =>
|
||||||
|
{
|
||||||
|
var subItem = i.Item2;
|
||||||
|
|
||||||
|
var episode = subItem as Episode;
|
||||||
|
|
||||||
|
if (episode != null)
|
||||||
|
{
|
||||||
|
var series = episode.Series;
|
||||||
|
if (series != null && series.HasImage(ImageType.Primary))
|
||||||
|
{
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subItem.HasImage(ImageType.Primary))
|
||||||
|
{
|
||||||
|
return subItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = subItem.GetOwner() ?? subItem.GetParent();
|
||||||
|
|
||||||
|
if (parent != null && parent.HasImage(ImageType.Primary))
|
||||||
|
{
|
||||||
|
if (parent is MusicAlbum)
|
||||||
|
{
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.Where(i => i != null)
|
||||||
|
.GroupBy(x => x.Id)
|
||||||
|
.Select(x => x.First())
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using MediaBrowser.Controller;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
|
@ -10,73 +9,48 @@ using MediaBrowser.Model.IO;
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides the core resolver ignore rules
|
/// Provides the core resolver ignore rules.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
|
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IServerApplicationPaths _serverApplicationPaths;
|
||||||
/// <summary>
|
|
||||||
/// Any folder named in this list will be ignored
|
|
||||||
/// </summary>
|
|
||||||
private static readonly string[] _ignoreFolders =
|
|
||||||
{
|
|
||||||
"metadata",
|
|
||||||
"ps3_update",
|
|
||||||
"ps3_vprm",
|
|
||||||
"extrafanart",
|
|
||||||
"extrathumbs",
|
|
||||||
".actors",
|
|
||||||
".wd_tv",
|
|
||||||
|
|
||||||
// Synology
|
|
||||||
"@eaDir",
|
|
||||||
"eaDir",
|
|
||||||
"#recycle",
|
|
||||||
|
|
||||||
// Qnap
|
|
||||||
"@Recycle",
|
|
||||||
".@__thumb",
|
|
||||||
"$RECYCLE.BIN",
|
|
||||||
"System Volume Information",
|
|
||||||
".grab",
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
|
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
|
/// <param name="serverApplicationPaths">The server application paths.</param>
|
||||||
|
public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
_serverApplicationPaths = serverApplicationPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
|
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
|
||||||
{
|
{
|
||||||
|
// Don't ignore application folders
|
||||||
|
if (fileInfo.FullName.Contains(_serverApplicationPaths.RootFolderPath, StringComparison.InvariantCulture))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't ignore top level folders
|
// Don't ignore top level folders
|
||||||
if (fileInfo.IsDirectory && parent is AggregateFolder)
|
if (fileInfo.IsDirectory && parent is AggregateFolder)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename = fileInfo.Name;
|
if (IgnorePatterns.ShouldIgnore(fileInfo.FullName))
|
||||||
|
|
||||||
// Ignore hidden files on UNIX
|
|
||||||
if (Environment.OSVersion.Platform != PlatformID.Win32NT
|
|
||||||
&& filename[0] == '.')
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var filename = fileInfo.Name;
|
||||||
|
|
||||||
if (fileInfo.IsDirectory)
|
if (fileInfo.IsDirectory)
|
||||||
{
|
{
|
||||||
// Ignore any folders in our list
|
|
||||||
if (_ignoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
// Ignore trailer folders but allow it at the collection level
|
// Ignore trailer folders but allow it at the collection level
|
||||||
|
@ -109,11 +83,6 @@ namespace Emby.Server.Implementations.Library
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore samples
|
|
||||||
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
|
|
||||||
|
|
||||||
return m.Success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -12,11 +12,13 @@ namespace Emby.Server.Implementations.Library
|
||||||
public class ExclusiveLiveStream : ILiveStream
|
public class ExclusiveLiveStream : ILiveStream
|
||||||
{
|
{
|
||||||
public int ConsumerCount { get; set; }
|
public int ConsumerCount { get; set; }
|
||||||
|
|
||||||
public string OriginalStreamId { get; set; }
|
public string OriginalStreamId { get; set; }
|
||||||
|
|
||||||
public string TunerHostId => null;
|
public string TunerHostId => null;
|
||||||
|
|
||||||
public bool EnableStreamSharing { get; set; }
|
public bool EnableStreamSharing { get; set; }
|
||||||
|
|
||||||
public MediaSourceInfo MediaSource { get; set; }
|
public MediaSourceInfo MediaSource { get; set; }
|
||||||
|
|
||||||
public string UniqueId { get; private set; }
|
public string UniqueId { get; private set; }
|
||||||
|
|
74
Emby.Server.Implementations/Library/IgnorePatterns.cs
Normal file
74
Emby.Server.Implementations/Library/IgnorePatterns.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System.Linq;
|
||||||
|
using DotNet.Globbing;
|
||||||
|
|
||||||
|
namespace Emby.Server.Implementations.Library
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Glob patterns for files to ignore.
|
||||||
|
/// </summary>
|
||||||
|
public static class IgnorePatterns
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Files matching these glob patterns will be ignored.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string[] Patterns = new string[]
|
||||||
|
{
|
||||||
|
"**/small.jpg",
|
||||||
|
"**/albumart.jpg",
|
||||||
|
"**/*sample*",
|
||||||
|
|
||||||
|
// Directories
|
||||||
|
"**/metadata/**",
|
||||||
|
"**/ps3_update/**",
|
||||||
|
"**/ps3_vprm/**",
|
||||||
|
"**/extrafanart/**",
|
||||||
|
"**/extrathumbs/**",
|
||||||
|
"**/.actors/**",
|
||||||
|
"**/.wd_tv/**",
|
||||||
|
"**/lost+found/**",
|
||||||
|
|
||||||
|
// WMC temp recording directories that will constantly be written to
|
||||||
|
"**/TempRec/**",
|
||||||
|
"**/TempSBE/**",
|
||||||
|
|
||||||
|
// Synology
|
||||||
|
"**/eaDir/**",
|
||||||
|
"**/@eaDir/**",
|
||||||
|
"**/#recycle/**",
|
||||||
|
|
||||||
|
// Qnap
|
||||||
|
"**/@Recycle/**",
|
||||||
|
"**/.@__thumb/**",
|
||||||
|
"**/$RECYCLE.BIN/**",
|
||||||
|
"**/System Volume Information/**",
|
||||||
|
"**/.grab/**",
|
||||||
|
|
||||||
|
// Unix hidden files and directories
|
||||||
|
"**/.*/**",
|
||||||
|
|
||||||
|
// thumbs.db
|
||||||
|
"**/thumbs.db",
|
||||||
|
|
||||||
|
// bts sync files
|
||||||
|
"**/*.bts",
|
||||||
|
"**/*.sync",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly GlobOptions _globOptions = new GlobOptions
|
||||||
|
{
|
||||||
|
Evaluation = {
|
||||||
|
CaseInsensitive = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the supplied path should be ignored.
|
||||||
|
/// </summary>
|
||||||
|
public static bool ShouldIgnore(string path)
|
||||||
|
{
|
||||||
|
return _globs.Any(g => g.IsMatch(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,14 +17,16 @@ using Emby.Server.Implementations.Library.Resolvers;
|
||||||
using Emby.Server.Implementations.Library.Validators;
|
using Emby.Server.Implementations.Library.Validators;
|
||||||
using Emby.Server.Implementations.Playlists;
|
using Emby.Server.Implementations.Playlists;
|
||||||
using Emby.Server.Implementations.ScheduledTasks;
|
using Emby.Server.Implementations.ScheduledTasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Progress;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.IO;
|
using MediaBrowser.Controller.IO;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
@ -35,6 +37,7 @@ using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Controller.Sorting;
|
using MediaBrowser.Controller.Sorting;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
@ -44,17 +47,20 @@ using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using MediaBrowser.Providers.MediaInfo;
|
using MediaBrowser.Providers.MediaInfo;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||||
|
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||||
|
using Person = MediaBrowser.Controller.Entities.Person;
|
||||||
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
|
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
|
||||||
using VideoResolver = Emby.Naming.Video.VideoResolver;
|
using VideoResolver = Emby.Naming.Video.VideoResolver;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class LibraryManager
|
/// Class LibraryManager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LibraryManager : ILibraryManager
|
public class LibraryManager : ILibraryManager
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<LibraryManager> _logger;
|
||||||
private readonly ITaskManager _taskManager;
|
private readonly ITaskManager _taskManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IUserDataManager _userDataRepository;
|
private readonly IUserDataManager _userDataRepository;
|
||||||
|
@ -67,6 +73,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IItemRepository _itemRepository;
|
private readonly IItemRepository _itemRepository;
|
||||||
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
|
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
|
||||||
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
|
||||||
private NamingOptions _namingOptions;
|
private NamingOptions _namingOptions;
|
||||||
private string[] _videoFileExtensions;
|
private string[] _videoFileExtensions;
|
||||||
|
@ -90,13 +97,13 @@ namespace Emby.Server.Implementations.Library
|
||||||
private IIntroProvider[] IntroProviders { get; set; }
|
private IIntroProvider[] IntroProviders { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the list of entity resolution ignore rules
|
/// Gets or sets the list of entity resolution ignore rules.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The entity resolution ignore rules.</value>
|
/// <value>The entity resolution ignore rules.</value>
|
||||||
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
|
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the list of currently registered entity resolvers
|
/// Gets or sets the list of currently registered entity resolvers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The entity resolvers enumerable.</value>
|
/// <value>The entity resolvers enumerable.</value>
|
||||||
private IItemResolver[] EntityResolvers { get; set; }
|
private IItemResolver[] EntityResolvers { get; set; }
|
||||||
|
@ -129,12 +136,19 @@ namespace Emby.Server.Implementations.Library
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appHost">The application host</param>
|
/// <param name="appHost">The application host.</param>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="taskManager">The task manager.</param>
|
/// <param name="taskManager">The task manager.</param>
|
||||||
/// <param name="userManager">The user manager.</param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
/// <param name="configurationManager">The configuration manager.</param>
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
/// <param name="userDataRepository">The user data repository.</param>
|
/// <param name="userDataRepository">The user data repository.</param>
|
||||||
|
/// <param name="libraryMonitorFactory">The library monitor.</param>
|
||||||
|
/// <param name="fileSystem">The file system.</param>
|
||||||
|
/// <param name="providerManagerFactory">The provider manager.</param>
|
||||||
|
/// <param name="userviewManagerFactory">The userview manager.</param>
|
||||||
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
|
/// <param name="itemRepository">The item repository.</param>
|
||||||
|
/// <param name="imageProcessor">The image processor.</param>
|
||||||
public LibraryManager(
|
public LibraryManager(
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
ILogger<LibraryManager> logger,
|
ILogger<LibraryManager> logger,
|
||||||
|
@ -147,7 +161,8 @@ namespace Emby.Server.Implementations.Library
|
||||||
Lazy<IProviderManager> providerManagerFactory,
|
Lazy<IProviderManager> providerManagerFactory,
|
||||||
Lazy<IUserViewManager> userviewManagerFactory,
|
Lazy<IUserViewManager> userviewManagerFactory,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IItemRepository itemRepository)
|
IItemRepository itemRepository,
|
||||||
|
IImageProcessor imageProcessor)
|
||||||
{
|
{
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -161,6 +176,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
_userviewManagerFactory = userviewManagerFactory;
|
_userviewManagerFactory = userviewManagerFactory;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_itemRepository = itemRepository;
|
_itemRepository = itemRepository;
|
||||||
|
_imageProcessor = imageProcessor;
|
||||||
|
|
||||||
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
|
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
|
||||||
|
|
||||||
|
@ -193,12 +209,12 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _root folder
|
/// The _root folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private volatile AggregateFolder _rootFolder;
|
private volatile AggregateFolder _rootFolder;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _root folder sync lock
|
/// The _root folder sync lock.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _rootFolderSyncLock = new object();
|
private readonly object _rootFolderSyncLock = new object();
|
||||||
|
|
||||||
|
@ -610,7 +626,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether a path should be ignored based on its contents - called after the contents have been read
|
/// Determines whether a path should be ignored based on its contents - called after the contents have been read.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="args">The args.</param>
|
/// <param name="args">The args.</param>
|
||||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||||
|
@ -695,7 +711,9 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
Directory.CreateDirectory(rootFolderPath);
|
Directory.CreateDirectory(rootFolderPath);
|
||||||
|
|
||||||
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))).DeepCopy<Folder, AggregateFolder>();
|
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
|
||||||
|
((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)))
|
||||||
|
.DeepCopy<Folder, AggregateFolder>();
|
||||||
|
|
||||||
// In case program data folder was moved
|
// In case program data folder was moved
|
||||||
if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal))
|
if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal))
|
||||||
|
@ -890,7 +908,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a Genre
|
/// Gets a Genre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name.</param>
|
/// <param name="name">The name.</param>
|
||||||
/// <returns>Task{Genre}.</returns>
|
/// <returns>Task{Genre}.</returns>
|
||||||
|
@ -971,7 +989,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reloads the root media folder
|
/// Reloads the root media folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="progress">The progress.</param>
|
/// <param name="progress">The progress.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
@ -1524,7 +1542,8 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle grouping
|
// Handle grouping
|
||||||
if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0)
|
if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType)
|
||||||
|
&& user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
|
||||||
{
|
{
|
||||||
return GetUserRootFolder()
|
return GetUserRootFolder()
|
||||||
.GetChildren(user, true)
|
.GetChildren(user, true)
|
||||||
|
@ -1773,7 +1792,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
/// Creates the items.
|
/// Creates the items.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="items">The items.</param>
|
/// <param name="items">The items.</param>
|
||||||
/// <param name="parent">The parent item</param>
|
/// <param name="parent">The parent item.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
|
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
@ -1815,10 +1834,100 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateImages(BaseItem item)
|
private bool ImageNeedsRefresh(ItemImageInfo image)
|
||||||
{
|
{
|
||||||
_itemRepository.SaveImages(item);
|
if (image.Path != null && image.IsLocalFile)
|
||||||
|
{
|
||||||
|
if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _fileSystem.GetLastWriteTimeUtc(image.Path) != image.DateModified;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Cannot get file info for {0}", image.Path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return image.Path != null && !image.IsLocalFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateImages(BaseItem item, bool forceUpdate = false)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
||||||
|
if (outdated.Length == 0)
|
||||||
|
{
|
||||||
|
RegisterItem(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var img in outdated)
|
||||||
|
{
|
||||||
|
var image = img;
|
||||||
|
if (!img.IsLocalFile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var index = item.GetImageIndex(img);
|
||||||
|
image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Cannot get image index for {0}", img.Path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Cannot fetch image from {0}", img.Path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
|
||||||
|
image.Width = size.Width;
|
||||||
|
image.Height = size.Height;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Cannnot get image dimensions for {0}", image.Path);
|
||||||
|
image.Width = 0;
|
||||||
|
image.Height = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path);
|
||||||
|
image.BlurHash = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Cannot update DateModified for {0}", image.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_itemRepository.SaveImages(item);
|
||||||
RegisterItem(item);
|
RegisterItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1839,7 +1948,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
item.DateLastSaved = DateTime.UtcNow;
|
item.DateLastSaved = DateTime.UtcNow;
|
||||||
|
|
||||||
RegisterItem(item);
|
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
_itemRepository.SaveItems(itemsList, cancellationToken);
|
_itemRepository.SaveItems(itemsList, cancellationToken);
|
||||||
|
@ -2495,7 +2604,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
Anime series don't generally have a season in their file name, however,
|
Anime series don't generally have a season in their file name, however,
|
||||||
tvdb needs a season to correctly get the metadata.
|
tvdb needs a season to correctly get the metadata.
|
||||||
Hence, a null season needs to be filled with something. */
|
Hence, a null season needs to be filled with something. */
|
||||||
//FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
|
// FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
|
||||||
episode.ParentIndexNumber = 1;
|
episode.ParentIndexNumber = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2684,10 +2793,12 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(from))
|
if (string.IsNullOrWhiteSpace(from))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(from));
|
throw new ArgumentNullException(nameof(from));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(to))
|
if (string.IsNullOrWhiteSpace(to))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(to));
|
throw new ArgumentNullException(nameof(to));
|
||||||
|
@ -2761,7 +2872,6 @@ namespace Emby.Server.Implementations.Library
|
||||||
_logger.LogError(ex, "Error getting person");
|
_logger.LogError(ex, "Error getting person");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).Where(i => i != null).ToList();
|
}).Where(i => i != null).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2796,7 +2906,8 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
|
if (ex.StatusCode.HasValue
|
||||||
|
&& (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2891,7 +3002,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
private static bool ValidateNetworkPath(string path)
|
private static bool ValidateNetworkPath(string path)
|
||||||
{
|
{
|
||||||
//if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
// if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||||
//{
|
//{
|
||||||
// // We can't validate protocol-based paths, so just allow them
|
// // We can't validate protocol-based paths, so just allow them
|
||||||
// if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
|
// if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
|
||||||
|
|
|
@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath);
|
mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath);
|
||||||
|
|
||||||
//_logger.LogDebug("Found cached media info");
|
// _logger.LogDebug("Found cached media info");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
||||||
_json.SerializeToFile(mediaInfo, cacheFilePath);
|
_json.SerializeToFile(mediaInfo, cacheFilePath);
|
||||||
|
|
||||||
//_logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,17 +148,14 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 30000000;
|
videoStream.BitRate = 30000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width >= 1900)
|
else if (width >= 1900)
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 20000000;
|
videoStream.BitRate = 20000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width >= 1200)
|
else if (width >= 1200)
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 8000000;
|
videoStream.BitRate = 8000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width >= 700)
|
else if (width >= 700)
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 2000000;
|
videoStream.BitRate = 2000000;
|
||||||
|
|
|
@ -7,6 +7,8 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -14,7 +16,6 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
@ -33,7 +34,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<MediaSourceManager> _logger;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly ILocalizationManager _localizationManager;
|
private readonly ILocalizationManager _localizationManager;
|
||||||
|
@ -190,10 +191,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (!user.Policy.EnableAudioPlaybackTranscoding)
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
||||||
{
|
|
||||||
source.SupportsTranscoding = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,22 +205,27 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
return MediaProtocol.Rtsp;
|
return MediaProtocol.Rtsp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return MediaProtocol.Rtmp;
|
return MediaProtocol.Rtmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return MediaProtocol.Http;
|
return MediaProtocol.Http;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return MediaProtocol.Rtp;
|
return MediaProtocol.Rtp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return MediaProtocol.Ftp;
|
return MediaProtocol.Ftp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return MediaProtocol.Udp;
|
return MediaProtocol.Udp;
|
||||||
|
@ -352,7 +355,9 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
|
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
|
||||||
{
|
{
|
||||||
if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
|
if (userData.SubtitleStreamIndex.HasValue
|
||||||
|
&& user.RememberSubtitleSelections
|
||||||
|
&& user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
|
||||||
{
|
{
|
||||||
var index = userData.SubtitleStreamIndex.Value;
|
var index = userData.SubtitleStreamIndex.Value;
|
||||||
// Make sure the saved index is still valid
|
// Make sure the saved index is still valid
|
||||||
|
@ -363,26 +368,27 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
|
|
||||||
? Array.Empty<string>() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference);
|
var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
|
||||||
|
? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
|
||||||
|
|
||||||
var defaultAudioIndex = source.DefaultAudioStreamIndex;
|
var defaultAudioIndex = source.DefaultAudioStreamIndex;
|
||||||
var audioLangage = defaultAudioIndex == null
|
var audioLangage = defaultAudioIndex == null
|
||||||
? null
|
? null
|
||||||
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
|
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
|
||||||
|
|
||||||
source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams,
|
source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(
|
||||||
|
source.MediaStreams,
|
||||||
preferredSubs,
|
preferredSubs,
|
||||||
user.Configuration.SubtitleMode,
|
user.SubtitleMode,
|
||||||
audioLangage);
|
audioLangage);
|
||||||
|
|
||||||
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs,
|
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage);
|
||||||
user.Configuration.SubtitleMode, audioLangage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
|
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
|
||||||
{
|
{
|
||||||
if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection)
|
if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection)
|
||||||
{
|
{
|
||||||
var index = userData.AudioStreamIndex.Value;
|
var index = userData.AudioStreamIndex.Value;
|
||||||
// Make sure the saved index is still valid
|
// Make sure the saved index is still valid
|
||||||
|
@ -393,11 +399,11 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
|
var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference)
|
||||||
? Array.Empty<string>()
|
? Array.Empty<string>()
|
||||||
: NormalizeLanguage(user.Configuration.AudioLanguagePreference);
|
: NormalizeLanguage(user.AudioLanguagePreference);
|
||||||
|
|
||||||
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
|
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user)
|
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user)
|
||||||
|
@ -435,7 +441,6 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
|
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
|
||||||
.ThenByDescending(i =>
|
.ThenByDescending(i =>
|
||||||
{
|
{
|
||||||
|
@ -521,11 +526,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
|
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse
|
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);
|
||||||
{
|
|
||||||
MediaSource = clone
|
|
||||||
|
|
||||||
}, liveStream as IDirectStreamProvider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio)
|
private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio)
|
||||||
|
@ -538,7 +539,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
mediaSource.RunTimeTicks = null;
|
mediaSource.RunTimeTicks = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
|
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||||
|
|
||||||
if (audioStream == null || audioStream.Index == -1)
|
if (audioStream == null || audioStream.Index == -1)
|
||||||
{
|
{
|
||||||
|
@ -549,7 +550,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
|
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
|
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
||||||
if (videoStream != null)
|
if (videoStream != null)
|
||||||
{
|
{
|
||||||
if (!videoStream.BitRate.HasValue)
|
if (!videoStream.BitRate.HasValue)
|
||||||
|
@ -560,17 +561,14 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 30000000;
|
videoStream.BitRate = 30000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width >= 1900)
|
else if (width >= 1900)
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 20000000;
|
videoStream.BitRate = 20000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width >= 1200)
|
else if (width >= 1200)
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 8000000;
|
videoStream.BitRate = 8000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width >= 700)
|
else if (width >= 700)
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 2000000;
|
videoStream.BitRate = 2000000;
|
||||||
|
@ -626,7 +624,6 @@ namespace Emby.Server.Implementations.Library
|
||||||
MediaSource = mediaSource,
|
MediaSource = mediaSource,
|
||||||
ExtractChapters = false,
|
ExtractChapters = false,
|
||||||
MediaType = DlnaProfileType.Video
|
MediaType = DlnaProfileType.Video
|
||||||
|
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
mediaSource.MediaStreams = info.MediaStreams;
|
mediaSource.MediaStreams = info.MediaStreams;
|
||||||
|
@ -652,7 +649,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath);
|
mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath);
|
||||||
|
|
||||||
//_logger.LogDebug("Found cached media info");
|
// _logger.LogDebug("Found cached media info");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -674,20 +671,21 @@ namespace Emby.Server.Implementations.Library
|
||||||
mediaSource.AnalyzeDurationMs = 3000;
|
mediaSource.AnalyzeDurationMs = 3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
mediaInfo = await _mediaEncoder.GetMediaInfo(
|
||||||
|
new MediaInfoRequest
|
||||||
{
|
{
|
||||||
MediaSource = mediaSource,
|
MediaSource = mediaSource,
|
||||||
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
||||||
ExtractChapters = false
|
ExtractChapters = false
|
||||||
|
},
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (cacheFilePath != null)
|
if (cacheFilePath != null)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
||||||
_jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
|
_jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
|
||||||
|
|
||||||
//_logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -753,17 +751,14 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 30000000;
|
videoStream.BitRate = 30000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width >= 1900)
|
else if (width >= 1900)
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 20000000;
|
videoStream.BitRate = 20000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width >= 1200)
|
else if (width >= 1200)
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 8000000;
|
videoStream.BitRate = 8000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (width >= 700)
|
else if (width >= 700)
|
||||||
{
|
{
|
||||||
videoStream.BitRate = 2000000;
|
videoStream.BitRate = 2000000;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user