Merge remote-tracking branch 'upstream/api-migration' into api-playlist
This commit is contained in:
commit
3329b08b40
|
@ -1,13 +1,13 @@
|
||||||
parameters:
|
parameters:
|
||||||
- name: Packages
|
- name: Packages
|
||||||
type: object
|
type: object
|
||||||
default: {}
|
default: {}
|
||||||
- name: LinuxImage
|
- name: LinuxImage
|
||||||
type: string
|
type: string
|
||||||
default: "ubuntu-latest"
|
default: "ubuntu-latest"
|
||||||
- name: DotNetSdkVersion
|
- name: DotNetSdkVersion
|
||||||
type: string
|
type: string
|
||||||
default: 3.1.100
|
default: 3.1.100
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: CompatibilityCheck
|
- job: CompatibilityCheck
|
||||||
|
@ -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,88 +13,81 @@ 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
|
||||||
submodules: true
|
submodules: true
|
||||||
persistCredentials: true
|
persistCredentials: true
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Clone Web Branch"
|
displayName: 'Download Web Branch'
|
||||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
|
||||||
inputs:
|
inputs:
|
||||||
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
path: '$(Agent.TempDirectory)'
|
||||||
|
artifact: 'jellyfin-web-production'
|
||||||
|
source: 'specific'
|
||||||
|
project: 'jellyfin'
|
||||||
|
pipeline: 'Jellyfin Web'
|
||||||
|
runBranch: variables['Build.SourceBranch']
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Clone Web Target"
|
displayName: 'Download Web Target'
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
|
condition: eq(variables['Build.Reason'], 'PullRequest')
|
||||||
inputs:
|
inputs:
|
||||||
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
path: '$(Agent.TempDirectory)'
|
||||||
|
artifact: 'jellyfin-web-production'
|
||||||
|
source: 'specific'
|
||||||
|
project: 'jellyfin'
|
||||||
|
pipeline: 'Jellyfin Web'
|
||||||
|
runBranch: variables['System.PullRequest.TargetBranch']
|
||||||
|
|
||||||
- task: NodeTool@0
|
- task: ExtractFiles@1
|
||||||
displayName: "Install Node"
|
displayName: 'Extract Web Client'
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: "12.x"
|
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
|
||||||
|
destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
|
||||||
- task: CmdLine@2
|
cleanDestinationFolder: false
|
||||||
displayName: "Build Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: yarn install
|
|
||||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: "Copy Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
|
|
||||||
contents: "**"
|
|
||||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: false
|
|
||||||
|
|
||||||
- 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'
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
parameters:
|
parameters:
|
||||||
- name: ImageNames
|
- name: ImageNames
|
||||||
type: object
|
type: object
|
||||||
default:
|
default:
|
||||||
Linux: "ubuntu-latest"
|
Linux: "ubuntu-latest"
|
||||||
Windows: "windows-latest"
|
Windows: "windows-latest"
|
||||||
macOS: "macos-latest"
|
macOS: "macos-latest"
|
||||||
- name: TestProjects
|
- name: TestProjects
|
||||||
type: string
|
type: string
|
||||||
default: "tests/**/*Tests.csproj"
|
default: "tests/**/*Tests.csproj"
|
||||||
- name: DotNetSdkVersion
|
- name: DotNetSdkVersion
|
||||||
type: string
|
type: string
|
||||||
default: 3.1.100
|
default: 3.1.100
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: MainTest
|
- job: Test
|
||||||
displayName: Main Test
|
displayName: Test
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
${{ each imageName in parameters.ImageNames }}:
|
${{ each imageName in parameters.ImageNames }}:
|
||||||
${{ imageName.key }}:
|
${{ imageName.key }}:
|
||||||
ImageName: ${{ imageName.value }}
|
ImageName: ${{ imageName.value }}
|
||||||
maxParallel: 3
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: "$(ImageName)"
|
vmImage: "$(ImageName)"
|
||||||
steps:
|
steps:
|
||||||
|
@ -29,14 +28,31 @@ jobs:
|
||||||
submodules: true
|
submodules: true
|
||||||
persistCredentials: false
|
persistCredentials: false
|
||||||
|
|
||||||
|
# This is required for the SonarCloud analyzer
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: "Install .NET Core SDK 2.1"
|
||||||
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
inputs:
|
||||||
|
packageType: sdk
|
||||||
|
version: '2.1.805'
|
||||||
|
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: "Update DotNet"
|
displayName: "Update DotNet"
|
||||||
inputs:
|
inputs:
|
||||||
packageType: sdk
|
packageType: sdk
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
|
- task: SonarCloudPrepare@1
|
||||||
|
displayName: 'Prepare analysis on SonarCloud'
|
||||||
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
enabled: false
|
||||||
|
inputs:
|
||||||
|
SonarCloud: 'Sonarcloud for Jellyfin'
|
||||||
|
organization: 'jellyfin'
|
||||||
|
projectKey: 'jellyfin_jellyfin'
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: Run .NET Core CLI tests
|
displayName: 'Run CLI Tests'
|
||||||
inputs:
|
inputs:
|
||||||
command: "test"
|
command: "test"
|
||||||
projects: ${{ parameters.TestProjects }}
|
projects: ${{ parameters.TestProjects }}
|
||||||
|
@ -45,9 +61,20 @@ jobs:
|
||||||
testRunTitle: $(Agent.JobName)
|
testRunTitle: $(Agent.JobName)
|
||||||
workingDirectory: "$(Build.SourcesDirectory)"
|
workingDirectory: "$(Build.SourcesDirectory)"
|
||||||
|
|
||||||
|
- task: SonarCloudAnalyze@1
|
||||||
|
displayName: 'Run Code Analysis'
|
||||||
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
- task: SonarCloudPublish@1
|
||||||
|
displayName: 'Publish Quality Gate Result'
|
||||||
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
enabled: false
|
||||||
|
|
||||||
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
- 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: ReportGenerator (merge)
|
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/"
|
||||||
|
@ -56,7 +83,8 @@ jobs:
|
||||||
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
|
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
|
||||||
- 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
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
name: $(Date:yyyyMMdd)$(Rev:.r)
|
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
|
||||||
|
|
||||||
pr:
|
pr:
|
||||||
autoCancel: true
|
autoCancel: true
|
||||||
|
@ -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,4 @@ jobs:
|
||||||
Common:
|
Common:
|
||||||
NugetPackageName: Jellyfin.Common
|
NugetPackageName: Jellyfin.Common
|
||||||
AssemblyFileName: MediaBrowser.Common.dll
|
AssemblyFileName: MediaBrowser.Common.dll
|
||||||
LinuxImage: "ubuntu-latest"
|
LinuxImage: 'ubuntu-latest'
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
|
|
||||||
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
|
|
||||||
|
|
||||||
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
|
|
||||||
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
|
||||||
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|
|
||||||
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
|
||||||
https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
|
|
||||||
|
|
||||||
srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
|
|
||||||
cd deployment/fedora-package-x64; \
|
|
||||||
SOURCE_DIR=../.. \
|
|
||||||
WORKDIR="$${PWD}"; \
|
|
||||||
package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
|
|
||||||
pkg_src_dir="$${WORKDIR}/pkg-src"; \
|
|
||||||
GNU_TAR=1; \
|
|
||||||
tar \
|
|
||||||
--transform "s,^\.,jellyfin-$(VERSION)," \
|
|
||||||
--exclude='.git*' \
|
|
||||||
--exclude='**/.git' \
|
|
||||||
--exclude='**/.hg' \
|
|
||||||
--exclude='**/.vs' \
|
|
||||||
--exclude='**/.vscode' \
|
|
||||||
--exclude='deployment' \
|
|
||||||
--exclude='**/bin' \
|
|
||||||
--exclude='**/obj' \
|
|
||||||
--exclude='**/.nuget' \
|
|
||||||
--exclude='*.deb' \
|
|
||||||
--exclude='*.rpm' \
|
|
||||||
-czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
|
|
||||||
-C $${SOURCE_DIR} ./ || GNU_TAR=0; \
|
|
||||||
if [ $${GNU_TAR} -eq 0 ]; then \
|
|
||||||
package_temporary_dir="$$(mktemp -d)"; \
|
|
||||||
mkdir -p "$${package_temporary_dir}/jellyfin"; \
|
|
||||||
tar \
|
|
||||||
--exclude='.git*' \
|
|
||||||
--exclude='**/.git' \
|
|
||||||
--exclude='**/.hg' \
|
|
||||||
--exclude='**/.vs' \
|
|
||||||
--exclude='**/.vscode' \
|
|
||||||
--exclude='deployment' \
|
|
||||||
--exclude='**/bin' \
|
|
||||||
--exclude='**/obj' \
|
|
||||||
--exclude='**/.nuget' \
|
|
||||||
--exclude='*.deb' \
|
|
||||||
--exclude='*.rpm' \
|
|
||||||
-czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
|
|
||||||
-C $${SOURCE_DIR} ./; \
|
|
||||||
mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
|
|
||||||
tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
|
|
||||||
-C "$${package_temporary_dir}/jellyfin-$(VERSION); \
|
|
||||||
rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
|
|
||||||
tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
|
|
||||||
-C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
|
|
||||||
rm -rf $${package_temporary_dir}; \
|
|
||||||
fi; \
|
|
||||||
rpmbuild -bs pkg-src/jellyfin.spec \
|
|
||||||
--define "_sourcedir $$PWD/pkg-src/" \
|
|
||||||
--define "_srcrpmdir $(outdir)"
|
|
1
.copr/Makefile
Symbolic link
1
.copr/Makefile
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../fedora/Makefile
|
|
@ -13,7 +13,7 @@ charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
max_line_length = null
|
max_line_length = off
|
||||||
|
|
||||||
# YAML indentation
|
# YAML indentation
|
||||||
[*.{yml,yaml}]
|
[*.{yml,yaml}]
|
||||||
|
@ -22,6 +22,7 @@ indent_size = 2
|
||||||
# XML indentation
|
# XML indentation
|
||||||
[*.{csproj,xml}]
|
[*.{csproj,xml}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# .NET Coding Conventions #
|
# .NET Coding Conventions #
|
||||||
###############################
|
###############################
|
||||||
|
@ -51,11 +52,12 @@ dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
dotnet_style_null_propagation = true:suggestion
|
dotnet_style_null_propagation = true:suggestion
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||||
dotnet_prefer_inferred_tuple_names = true:suggestion
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
dotnet_style_prefer_auto_properties = true:silent
|
dotnet_style_prefer_auto_properties = true:silent
|
||||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# Naming Conventions #
|
# Naming Conventions #
|
||||||
###############################
|
###############################
|
||||||
|
@ -67,7 +69,7 @@ dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non
|
||||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
||||||
|
|
||||||
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
|
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
|
||||||
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
||||||
|
|
||||||
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
||||||
|
@ -159,6 +161,7 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||||
csharp_prefer_simple_default_expression = true:suggestion
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||||
csharp_style_inlined_variable_declaration = true:suggestion
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# C# Formatting Rules #
|
# C# Formatting Rules #
|
||||||
###############################
|
###############################
|
||||||
|
@ -189,9 +192,3 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
# Wrapping preferences
|
# Wrapping preferences
|
||||||
csharp_preserve_single_line_statements = true
|
csharp_preserve_single_line_statements = true
|
||||||
csharp_preserve_single_line_blocks = true
|
csharp_preserve_single_line_blocks = true
|
||||||
###############################
|
|
||||||
# VB Coding Conventions #
|
|
||||||
###############################
|
|
||||||
[*.vb]
|
|
||||||
# Modifier preferences
|
|
||||||
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
|
|
||||||
|
|
3
.github/CODEOWNERS
vendored
Normal file
3
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Joshua must review all changes to deployment and build.sh
|
||||||
|
deployment/* @joshuaboniface
|
||||||
|
build.sh @joshuaboniface
|
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
|
||||||
|
|
19
.gitignore
vendored
19
.gitignore
vendored
|
@ -245,14 +245,14 @@ pip-log.txt
|
||||||
#########################
|
#########################
|
||||||
|
|
||||||
# Artifacts for debian-x64
|
# Artifacts for debian-x64
|
||||||
deployment/debian-package-x64/pkg-src/.debhelper/
|
debian/.debhelper/
|
||||||
deployment/debian-package-x64/pkg-src/*.debhelper
|
debian/*.debhelper
|
||||||
deployment/debian-package-x64/pkg-src/debhelper-build-stamp
|
debian/debhelper-build-stamp
|
||||||
deployment/debian-package-x64/pkg-src/files
|
debian/files
|
||||||
deployment/debian-package-x64/pkg-src/jellyfin.substvars
|
debian/jellyfin.substvars
|
||||||
deployment/debian-package-x64/pkg-src/jellyfin/
|
debian/jellyfin/
|
||||||
# Don't ignore the debian/bin folder
|
# Don't ignore the debian/bin folder
|
||||||
!deployment/debian-package-x64/pkg-src/bin/
|
!debian/bin/
|
||||||
|
|
||||||
deployment/**/dist/
|
deployment/**/dist/
|
||||||
deployment/**/pkg-dist/
|
deployment/**/pkg-dist/
|
||||||
|
@ -272,3 +272,8 @@ dist
|
||||||
|
|
||||||
# BenchmarkDotNet artifacts
|
# BenchmarkDotNet artifacts
|
||||||
BenchmarkDotNet.Artifacts
|
BenchmarkDotNet.Artifacts
|
||||||
|
|
||||||
|
# Ignore web artifacts from native builds
|
||||||
|
web/
|
||||||
|
web-src.*
|
||||||
|
MediaBrowser.WebDashboard/jellyfin-web/
|
||||||
|
|
12
.vscode/tasks.json
vendored
12
.vscode/tasks.json
vendored
|
@ -10,6 +10,16 @@
|
||||||
"${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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 && \
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\SharedVersion.cs" />
|
<Compile Include="..\SharedVersion.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -8,6 +13,7 @@
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
{
|
{
|
||||||
public class Chapter
|
public class Chapter
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -421,61 +427,102 @@ 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is Episode episode && context is Season season)
|
return item is Episode episode
|
||||||
|
? GetEpisodeDisplayName(episode, context)
|
||||||
|
: item.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets episode display name appropriate for the given context.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If context is a season, this will return a string containing just episode number and name.
|
||||||
|
/// Otherwise the result will include series nams and season number.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="episode">The episode.</param>
|
||||||
|
/// <param name="context">Current context.</param>
|
||||||
|
/// <returns>Formatted name of the episode.</returns>
|
||||||
|
private string GetEpisodeDisplayName(Episode episode, BaseItem context)
|
||||||
|
{
|
||||||
|
string[] components;
|
||||||
|
|
||||||
|
if (context is Season season)
|
||||||
{
|
{
|
||||||
// This is a special embedded within a season
|
// This is a special embedded within a season
|
||||||
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
|
if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0
|
||||||
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
|
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
|
||||||
{
|
{
|
||||||
return string.Format(
|
return string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("ValueSpecialEpisodeName"),
|
_localization.GetLocalizedString("ValueSpecialEpisodeName"),
|
||||||
item.Name);
|
episode.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.IndexNumber.HasValue)
|
// inside a season use simple format (ex. '12 - Episode Name')
|
||||||
{
|
var epNumberName = GetEpisodeIndexFullName(episode);
|
||||||
var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
components = new[] { epNumberName, episode.Name };
|
||||||
|
|
||||||
if (episode.IndexNumberEnd.HasValue)
|
|
||||||
{
|
|
||||||
number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
return number + " - " + item.Name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (item is Episode ep)
|
else
|
||||||
{
|
{
|
||||||
var parent = ep.GetParent();
|
// outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
|
||||||
var name = parent.Name + " - ";
|
var epNumberName = GetEpisodeNumberDisplayName(episode);
|
||||||
|
components = new[] { episode.SeriesName, epNumberName, episode.Name };
|
||||||
if (ep.ParentIndexNumber.HasValue)
|
|
||||||
{
|
|
||||||
name += "S" + ep.ParentIndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
else if (!item.IndexNumber.HasValue)
|
|
||||||
{
|
|
||||||
return name + " - " + item.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
name += "E" + ep.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
|
||||||
if (ep.IndexNumberEnd.HasValue)
|
|
||||||
{
|
|
||||||
name += "-" + ep.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
name += " - " + item.Name;
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.Name;
|
return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets complete episode number.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode.</param>
|
||||||
|
/// <returns>For single episodes returns just the number. For double episodes - current and ending numbers.</returns>
|
||||||
|
private string GetEpisodeIndexFullName(Episode episode)
|
||||||
|
{
|
||||||
|
var name = string.Empty;
|
||||||
|
if (episode.IndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
if (episode.IndexNumberEnd.HasValue)
|
||||||
|
{
|
||||||
|
name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets episode number formatted as 'S##E##'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode.</param>
|
||||||
|
/// <returns>Formatted episode number.</returns>
|
||||||
|
private string GetEpisodeNumberDisplayName(Episode episode)
|
||||||
|
{
|
||||||
|
var name = string.Empty;
|
||||||
|
var seasonNumber = episode.Season?.IndexNumber;
|
||||||
|
|
||||||
|
if (seasonNumber.HasValue)
|
||||||
|
{
|
||||||
|
name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexName = GetEpisodeIndexFullName(episode);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(indexName))
|
||||||
|
{
|
||||||
|
name += "E" + indexName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
|
||||||
|
|
||||||
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
||||||
|
@ -628,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))
|
||||||
|
@ -953,7 +1000,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(
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{805844AB-E92F-45E6-9D99-4F6D48D129A5}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\SharedVersion.cs" />
|
<Compile Include="..\SharedVersion.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -33,7 +33,7 @@ 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 PlayToManager _manager;
|
||||||
|
@ -65,7 +65,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 +100,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 +134,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();
|
||||||
|
|
||||||
|
@ -347,7 +348,8 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_manager = new PlayToManager(_logger,
|
_manager = new PlayToManager(
|
||||||
|
_logger,
|
||||||
_sessionManager,
|
_sessionManager,
|
||||||
_libraryManager,
|
_libraryManager,
|
||||||
_userManager,
|
_userManager,
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
RefreshVolumeIfNeeded();
|
RefreshVolumeIfNeeded().GetAwaiter().GetResult();
|
||||||
return _volume;
|
return _volume;
|
||||||
}
|
}
|
||||||
set => _volume = value;
|
set => _volume = value;
|
||||||
|
@ -76,24 +76,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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -441,7 +448,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;
|
||||||
|
|
||||||
|
@ -908,7 +921,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
|
/// <inheritdoc />
|
||||||
|
public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
|
@ -924,10 +938,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
||||||
|
|
|
@ -184,7 +184,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,
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Name = "Generic Device";
|
Name = "Generic Device";
|
||||||
|
|
||||||
ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*";
|
ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";
|
||||||
|
|
||||||
Manufacturer = "Jellyfin";
|
Manufacturer = "Jellyfin";
|
||||||
ModelDescription = "UPnP/AV 1.0 Compliant Media Server";
|
ModelDescription = "UPnP/AV 1.0 Compliant Media Server";
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>true</RequiresPlainFolders>
|
<RequiresPlainFolders>true</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>true</RequiresPlainFolders>
|
<RequiresPlainFolders>true</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>5</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>5</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>40</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>40</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{08FFF49B-F175-4807-A2B5-73B0EBD9F716}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -4,17 +4,18 @@ 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;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Entities;
|
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
|
||||||
{
|
{
|
||||||
|
@ -29,12 +30,11 @@ 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;
|
||||||
private readonly Func<ILibraryManager> _libraryManager;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly Func<IMediaEncoder> _mediaEncoder;
|
|
||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
|
@ -45,20 +45,17 @@ namespace Emby.Drawing
|
||||||
/// <param name="appPaths">The server application paths.</param>
|
/// <param name="appPaths">The server application paths.</param>
|
||||||
/// <param name="fileSystem">The filesystem.</param>
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
/// <param name="imageEncoder">The image encoder.</param>
|
/// <param name="imageEncoder">The image encoder.</param>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
|
||||||
/// <param name="mediaEncoder">The media encoder.</param>
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
public ImageProcessor(
|
public ImageProcessor(
|
||||||
ILogger<ImageProcessor> logger,
|
ILogger<ImageProcessor> logger,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IImageEncoder imageEncoder,
|
IImageEncoder imageEncoder,
|
||||||
Func<ILibraryManager> libraryManager,
|
IMediaEncoder mediaEncoder)
|
||||||
Func<IMediaEncoder> mediaEncoder)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_imageEncoder = imageEncoder;
|
_imageEncoder = imageEncoder;
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
}
|
}
|
||||||
|
@ -119,28 +116,11 @@ 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)
|
||||||
{
|
{
|
||||||
if (options == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(options));
|
|
||||||
}
|
|
||||||
|
|
||||||
var libraryManager = _libraryManager();
|
|
||||||
|
|
||||||
ItemImageInfo originalImage = options.Image;
|
ItemImageInfo originalImage = options.Image;
|
||||||
BaseItem item = options.Item;
|
BaseItem item = options.Item;
|
||||||
|
|
||||||
if (!originalImage.IsLocalFile)
|
|
||||||
{
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
item = libraryManager.GetItemById(options.ItemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
originalImage = await libraryManager.ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
string originalImagePath = originalImage.Path;
|
string originalImagePath = originalImage.Path;
|
||||||
DateTime dateModified = originalImage.DateModified;
|
DateTime dateModified = originalImage.DateModified;
|
||||||
ImageDimensions? originalImageSize = null;
|
ImageDimensions? originalImageSize = null;
|
||||||
|
@ -252,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"),
|
||||||
|
@ -312,10 +292,6 @@ namespace Emby.Drawing
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
|
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
|
||||||
=> GetImageDimensions(item, info, true);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
|
|
||||||
{
|
{
|
||||||
int width = info.Width;
|
int width = info.Width;
|
||||||
int height = info.Height;
|
int height = info.Height;
|
||||||
|
@ -326,17 +302,12 @@ 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;
|
||||||
info.Height = size.Height;
|
info.Height = size.Height;
|
||||||
|
|
||||||
if (updateItem)
|
|
||||||
{
|
|
||||||
_libraryManager().UpdateImages(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,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);
|
||||||
|
@ -351,19 +343,19 @@ namespace Emby.Drawing
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
|
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
|
||||||
{
|
{
|
||||||
try
|
return GetImageCacheTag(item, new ItemImageInfo
|
||||||
{
|
{
|
||||||
return GetImageCacheTag(item, new ItemImageInfo
|
Path = chapter.ImagePath,
|
||||||
{
|
Type = ImageType.Chapter,
|
||||||
Path = chapter.ImagePath,
|
DateModified = chapter.ImageDateModified
|
||||||
Type = ImageType.Chapter,
|
});
|
||||||
DateModified = chapter.ImageDateModified
|
}
|
||||||
});
|
|
||||||
}
|
/// <inheritdoc />
|
||||||
catch
|
public string GetImageCacheTag(User user)
|
||||||
{
|
{
|
||||||
return null;
|
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)
|
||||||
|
@ -384,13 +376,13 @@ namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
|
string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
|
||||||
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
||||||
|
|
||||||
var file = _fileSystem.GetFileInfo(outputPath);
|
var file = _fileSystem.GetFileInfo(outputPath);
|
||||||
if (!file.Exists)
|
if (!file.Exists)
|
||||||
{
|
{
|
||||||
await _mediaEncoder().ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
|
await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
|
||||||
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
|
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
|
||||||
|
@ -21,8 +21,7 @@ namespace Emby.Naming.Audio
|
||||||
public bool IsMultiPart(string path)
|
public bool IsMultiPart(string path)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileName(path);
|
var filename = Path.GetFileName(path);
|
||||||
|
if (filename.Length == 0)
|
||||||
if (string.IsNullOrEmpty(filename))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -39,18 +38,22 @@ namespace Emby.Naming.Audio
|
||||||
filename = filename.Replace(')', ' ');
|
filename = filename.Replace(')', ' ');
|
||||||
filename = Regex.Replace(filename, @"\s+", " ");
|
filename = Regex.Replace(filename, @"\s+", " ");
|
||||||
|
|
||||||
filename = filename.TrimStart();
|
ReadOnlySpan<char> trimmedFilename = filename.TrimStart();
|
||||||
|
|
||||||
foreach (var prefix in _options.AlbumStackingPrefixes)
|
foreach (var prefix in _options.AlbumStackingPrefixes)
|
||||||
{
|
{
|
||||||
if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0)
|
if (!trimmedFilename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp = filename.Substring(prefix.Length);
|
var tmp = trimmedFilename.Slice(prefix.Length).Trim();
|
||||||
|
|
||||||
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
|
int index = tmp.IndexOf(' ');
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
tmp = tmp.Slice(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -11,7 +12,7 @@ namespace Emby.Naming.Audio
|
||||||
{
|
{
|
||||||
public static bool IsAudioFile(string path, NamingOptions options)
|
public static bool IsAudioFile(string path, NamingOptions options)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path) ?? string.Empty;
|
var extension = Path.GetExtension(path);
|
||||||
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,6 @@ namespace Emby.Naming.Common
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodeExpression()
|
|
||||||
: this(null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Expression
|
public string Expression
|
||||||
{
|
{
|
||||||
get => _expression;
|
get => _expression;
|
||||||
|
@ -48,6 +43,6 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
public string[] DateTimeFormats { get; set; }
|
public string[] DateTimeFormats { get; set; }
|
||||||
|
|
||||||
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
|
public Regex Regex => _regex ??= new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
||||||
@"(\[.*\])"
|
@"(\[.*\])"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -16,11 +17,11 @@ namespace Emby.Naming.Subtitles
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubtitleInfo ParseFile(string path)
|
public SubtitleInfo? ParseFile(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (path.Length == 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentException("File path can't be empty.", nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
@ -52,11 +53,6 @@ namespace Emby.Naming.Subtitles
|
||||||
|
|
||||||
private string[] GetFlags(string path)
|
private string[] GetFlags(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
|
// Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
|
||||||
|
|
||||||
var file = Path.GetFileName(path);
|
var file = Path.GetFileName(path);
|
||||||
|
|
|
@ -227,7 +227,7 @@ namespace Emby.Naming.Video
|
||||||
}
|
}
|
||||||
|
|
||||||
return remainingFiles
|
return remainingFiles
|
||||||
.Where(i => i.ExtraType == null)
|
.Where(i => i.ExtraType != null)
|
||||||
.Where(i => baseNames.Any(b =>
|
.Where(i => baseNames.Any(b =>
|
||||||
i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
|
i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
|
@ -89,14 +89,14 @@ namespace Emby.Naming.Video
|
||||||
if (parseName)
|
if (parseName)
|
||||||
{
|
{
|
||||||
var cleanDateTimeResult = CleanDateTime(name);
|
var cleanDateTimeResult = CleanDateTime(name);
|
||||||
|
name = cleanDateTimeResult.Name;
|
||||||
|
year = cleanDateTimeResult.Year;
|
||||||
|
|
||||||
if (extraResult.ExtraType == null
|
if (extraResult.ExtraType == null
|
||||||
&& TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan<char> newName))
|
&& TryCleanString(name, out ReadOnlySpan<char> newName))
|
||||||
{
|
{
|
||||||
name = newName.ToString();
|
name = newName.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
year = cleanDateTimeResult.Year;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new VideoFileInfo
|
return new VideoFileInfo
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{2E030C33-6923-4530-9E54-FA29FA6AD1A9}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -143,7 +143,7 @@ namespace Emby.Notifications
|
||||||
|
|
||||||
var notification = new NotificationRequest
|
var notification = new NotificationRequest
|
||||||
{
|
{
|
||||||
Description = "Please see jellyfin.media for details.",
|
Description = "Please see jellyfin.org for details.",
|
||||||
NotificationType = type,
|
NotificationType = type,
|
||||||
Name = _localization.GetLocalizedString("NewVersionIsAvailable")
|
Name = _localization.GetLocalizedString("NewVersionIsAvailable")
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{89AB4548-770D-41FD-A891-8DAFF44F452C}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ namespace Emby.Photos
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var size = _imageProcessor.GetImageDimensions(item, img, false);
|
var size = _imageProcessor.GetImageDimensions(item, img);
|
||||||
|
|
||||||
if (size.Width > 0 && size.Height > 0)
|
if (size.Width > 0 && size.Height > 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,11 +4,10 @@ using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
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.Authentication;
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Devices;
|
|
||||||
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;
|
||||||
|
@ -30,7 +29,7 @@ namespace Emby.Server.Implementations.Activity
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<ActivityLogEntryPoint> _logger;
|
||||||
private readonly IInstallationManager _installationManager;
|
private readonly IInstallationManager _installationManager;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly ITaskManager _taskManager;
|
private readonly ITaskManager _taskManager;
|
||||||
|
@ -38,14 +37,12 @@ namespace Emby.Server.Implementations.Activity
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly ISubtitleManager _subManager;
|
private readonly ISubtitleManager _subManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IDeviceManager _deviceManager;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
|
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="sessionManager">The session manager.</param>
|
/// <param name="sessionManager">The session manager.</param>
|
||||||
/// <param name="deviceManager">The device manager.</param>
|
|
||||||
/// <param name="taskManager">The task manager.</param>
|
/// <param name="taskManager">The task manager.</param>
|
||||||
/// <param name="activityManager">The activity manager.</param>
|
/// <param name="activityManager">The activity manager.</param>
|
||||||
/// <param name="localization">The localization manager.</param>
|
/// <param name="localization">The localization manager.</param>
|
||||||
|
@ -55,7 +52,6 @@ namespace Emby.Server.Implementations.Activity
|
||||||
public ActivityLogEntryPoint(
|
public ActivityLogEntryPoint(
|
||||||
ILogger<ActivityLogEntryPoint> logger,
|
ILogger<ActivityLogEntryPoint> logger,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
IDeviceManager deviceManager,
|
|
||||||
ITaskManager taskManager,
|
ITaskManager taskManager,
|
||||||
IActivityManager activityManager,
|
IActivityManager activityManager,
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
|
@ -65,7 +61,6 @@ namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
_deviceManager = deviceManager;
|
|
||||||
_taskManager = taskManager;
|
_taskManager = taskManager;
|
||||||
_activityManager = activityManager;
|
_activityManager = activityManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
|
@ -93,58 +88,45 @@ 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;
|
|
||||||
|
|
||||||
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
_localization.GetLocalizedString("UserLockedOutWithName"),
|
||||||
|
e.Argument.Username),
|
||||||
|
NotificationType.UserLockedOut.ToString(),
|
||||||
|
e.Argument.Id)
|
||||||
{
|
{
|
||||||
Name = string.Format(
|
LogSeverity = LogLevel.Error
|
||||||
CultureInfo.InvariantCulture,
|
}).ConfigureAwait(false);
|
||||||
_localization.GetLocalizedString("CameraImageUploadedFrom"),
|
|
||||||
e.Argument.Device.Name),
|
|
||||||
Type = NotificationType.CameraImageUploaded.ToString()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserLockedOut(object sender, GenericEventArgs<User> e)
|
private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserLockedOutWithName"),
|
|
||||||
e.Argument.Name),
|
|
||||||
Type = NotificationType.UserLockedOut.ToString(),
|
|
||||||
UserId = e.Argument.Id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
|
||||||
{
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
|
||||||
{
|
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
|
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
|
||||||
e.Provider,
|
e.Provider,
|
||||||
Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
|
Notifications.NotificationEntryPoint.GetItemName(e.Item)),
|
||||||
Type = "SubtitleDownloadFailure",
|
"SubtitleDownloadFailure",
|
||||||
|
Guid.Empty)
|
||||||
|
{
|
||||||
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||||
ShortOverview = e.Exception.Message
|
ShortOverview = e.Exception.Message
|
||||||
});
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
|
private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||||
{
|
{
|
||||||
var item = e.MediaInfo;
|
var item = e.MediaInfo;
|
||||||
|
|
||||||
|
@ -167,20 +149,19 @@ namespace Emby.Server.Implementations.Activity
|
||||||
|
|
||||||
var user = e.Users[0];
|
var user = e.Users[0];
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
||||||
user.Name,
|
user.Username,
|
||||||
GetItemName(item),
|
GetItemName(item),
|
||||||
e.DeviceName),
|
e.DeviceName),
|
||||||
Type = GetPlaybackStoppedNotificationType(item.MediaType),
|
GetPlaybackStoppedNotificationType(item.MediaType),
|
||||||
UserId = user.Id
|
user.Id))
|
||||||
});
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
|
private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||||
{
|
{
|
||||||
var item = e.MediaInfo;
|
var item = e.MediaInfo;
|
||||||
|
|
||||||
|
@ -203,17 +184,16 @@ namespace Emby.Server.Implementations.Activity
|
||||||
|
|
||||||
var user = e.Users.First();
|
var user = e.Users.First();
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
|
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
|
||||||
user.Name,
|
user.Username,
|
||||||
GetItemName(item),
|
GetItemName(item),
|
||||||
e.DeviceName),
|
e.DeviceName),
|
||||||
Type = GetPlaybackNotificationType(item.MediaType),
|
GetPlaybackNotificationType(item.MediaType),
|
||||||
UserId = user.Id
|
user.Id))
|
||||||
});
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetItemName(BaseItemDto item)
|
private static string GetItemName(BaseItemDto item)
|
||||||
|
@ -263,230 +243,197 @@ namespace Emby.Server.Implementations.Activity
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSessionEnded(object sender, SessionEventArgs e)
|
private async void OnSessionEnded(object sender, SessionEventArgs e)
|
||||||
{
|
{
|
||||||
string name;
|
|
||||||
var session = e.SessionInfo;
|
var session = e.SessionInfo;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(session.UserName))
|
if (string.IsNullOrEmpty(session.UserName))
|
||||||
{
|
{
|
||||||
name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("DeviceOfflineWithName"),
|
|
||||||
session.DeviceName);
|
|
||||||
|
|
||||||
// Causing too much spam for now
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
await CreateLogEntry(new ActivityLog(
|
||||||
name = string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserOfflineFromDevice"),
|
_localization.GetLocalizedString("UserOfflineFromDevice"),
|
||||||
session.UserName,
|
session.UserName,
|
||||||
session.DeviceName);
|
session.DeviceName),
|
||||||
}
|
"SessionEnded",
|
||||||
|
session.UserId)
|
||||||
CreateLogEntry(new ActivityLogEntry
|
|
||||||
{
|
{
|
||||||
Name = name,
|
|
||||||
Type = "SessionEnded",
|
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
_localization.GetLocalizedString("LabelIpAddressValue"),
|
||||||
session.RemoteEndPoint),
|
session.RemoteEndPoint),
|
||||||
UserId = session.UserId
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
||||||
{
|
{
|
||||||
var user = e.Argument.User;
|
var user = e.Argument.User;
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
|
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
|
||||||
user.Name),
|
user.Name),
|
||||||
Type = "AuthenticationSucceeded",
|
"AuthenticationSucceeded",
|
||||||
|
user.Id)
|
||||||
|
{
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
_localization.GetLocalizedString("LabelIpAddressValue"),
|
||||||
e.Argument.SessionInfo.RemoteEndPoint),
|
e.Argument.SessionInfo.RemoteEndPoint),
|
||||||
UserId = user.Id
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
|
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
|
||||||
e.Argument.Username),
|
e.Argument.Username),
|
||||||
Type = "AuthenticationFailed",
|
"AuthenticationFailed",
|
||||||
|
Guid.Empty)
|
||||||
|
{
|
||||||
|
LogSeverity = LogLevel.Error,
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
_localization.GetLocalizedString("LabelIpAddressValue"),
|
||||||
e.Argument.RemoteEndPoint),
|
e.Argument.RemoteEndPoint),
|
||||||
Severity = LogLevel.Error
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
|
|
||||||
e.Argument.Name),
|
|
||||||
Type = "UserPolicyUpdated",
|
|
||||||
UserId = e.Argument.Id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
|
||||||
{
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
|
||||||
{
|
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserDeletedWithName"),
|
_localization.GetLocalizedString("UserDeletedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Username),
|
||||||
Type = "UserDeleted"
|
"UserDeleted",
|
||||||
});
|
Guid.Empty))
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
|
private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserPasswordChangedWithName"),
|
_localization.GetLocalizedString("UserPasswordChangedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Username),
|
||||||
Type = "UserPasswordChanged",
|
"UserPasswordChanged",
|
||||||
UserId = e.Argument.Id
|
e.Argument.Id))
|
||||||
});
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserCreated(object sender, GenericEventArgs<User> e)
|
private async void OnUserCreated(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserCreatedWithName"),
|
_localization.GetLocalizedString("UserCreatedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Username),
|
||||||
Type = "UserCreated",
|
"UserCreated",
|
||||||
UserId = e.Argument.Id
|
e.Argument.Id))
|
||||||
});
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSessionStarted(object sender, SessionEventArgs e)
|
private async void OnSessionStarted(object sender, SessionEventArgs e)
|
||||||
{
|
{
|
||||||
string name;
|
|
||||||
var session = e.SessionInfo;
|
var session = e.SessionInfo;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(session.UserName))
|
if (string.IsNullOrEmpty(session.UserName))
|
||||||
{
|
{
|
||||||
name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("DeviceOnlineWithName"),
|
|
||||||
session.DeviceName);
|
|
||||||
|
|
||||||
// Causing too much spam for now
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
await CreateLogEntry(new ActivityLog(
|
||||||
name = string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserOnlineFromDevice"),
|
_localization.GetLocalizedString("UserOnlineFromDevice"),
|
||||||
session.UserName,
|
session.UserName,
|
||||||
session.DeviceName);
|
session.DeviceName),
|
||||||
}
|
"SessionStarted",
|
||||||
|
session.UserId)
|
||||||
CreateLogEntry(new ActivityLogEntry
|
|
||||||
{
|
{
|
||||||
Name = name,
|
|
||||||
Type = "SessionStarted",
|
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
_localization.GetLocalizedString("LabelIpAddressValue"),
|
||||||
session.RemoteEndPoint),
|
session.RemoteEndPoint)
|
||||||
UserId = session.UserId
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
|
private async void OnPluginUpdated(object sender, InstallationInfo e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("PluginUpdatedWithName"),
|
_localization.GetLocalizedString("PluginUpdatedWithName"),
|
||||||
e.Argument.Item1.Name),
|
e.Name),
|
||||||
Type = NotificationType.PluginUpdateInstalled.ToString(),
|
NotificationType.PluginUpdateInstalled.ToString(),
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
private async void OnPluginUninstalled(object sender, IPlugin e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("PluginUninstalledWithName"),
|
_localization.GetLocalizedString("PluginUninstalledWithName"),
|
||||||
e.Argument.Name),
|
e.Name),
|
||||||
Type = NotificationType.PluginUninstalled.ToString()
|
NotificationType.PluginUninstalled.ToString(),
|
||||||
});
|
Guid.Empty))
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
|
private async void OnPluginInstalled(object sender, InstallationInfo e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("PluginInstalledWithName"),
|
_localization.GetLocalizedString("PluginInstalledWithName"),
|
||||||
e.Argument.name),
|
e.Name),
|
||||||
Type = NotificationType.PluginInstalled.ToString(),
|
NotificationType.PluginInstalled.ToString(),
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||||
{
|
{
|
||||||
var installationInfo = e.InstallationInfo;
|
var installationInfo = e.InstallationInfo;
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("NameInstallFailed"),
|
_localization.GetLocalizedString("NameInstallFailed"),
|
||||||
installationInfo.Name),
|
installationInfo.Name),
|
||||||
Type = NotificationType.InstallationFailed.ToString(),
|
NotificationType.InstallationFailed.ToString(),
|
||||||
|
Guid.Empty)
|
||||||
|
{
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
installationInfo.Version),
|
installationInfo.Version),
|
||||||
Overview = e.Exception.Message
|
Overview = e.Exception.Message
|
||||||
});
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||||
{
|
{
|
||||||
var result = e.Result;
|
var result = e.Result;
|
||||||
var task = e.Task;
|
var task = e.Task;
|
||||||
|
@ -517,22 +464,20 @@ namespace Emby.Server.Implementations.Activity
|
||||||
vals.Add(e.Result.LongErrorMessage);
|
vals.Add(e.Result.LongErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
|
string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
|
||||||
|
NotificationType.TaskFailed.ToString(),
|
||||||
|
Guid.Empty)
|
||||||
{
|
{
|
||||||
Name = string.Format(
|
LogSeverity = LogLevel.Error,
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("ScheduledTaskFailedWithName"),
|
|
||||||
task.Name),
|
|
||||||
Type = NotificationType.TaskFailed.ToString(),
|
|
||||||
Overview = string.Join(Environment.NewLine, vals),
|
Overview = string.Join(Environment.NewLine, vals),
|
||||||
ShortOverview = runningTime,
|
ShortOverview = runningTime
|
||||||
Severity = LogLevel.Error
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateLogEntry(ActivityLogEntry entry)
|
private async Task CreateLogEntry(ActivityLog entry)
|
||||||
=> _activityManager.Create(entry);
|
=> await _activityManager.CreateAsync(entry).ConfigureAwait(false);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -554,13 +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;
|
|
||||||
|
|
||||||
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -580,7 +522,7 @@ namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
int years = days / DaysInYear;
|
int years = days / DaysInYear;
|
||||||
values.Add(CreateValueString(years, "year"));
|
values.Add(CreateValueString(years, "year"));
|
||||||
days = days % DaysInYear;
|
days %= DaysInYear;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number of months
|
// Number of months
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Model.Activity;
|
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
|
||||||
{
|
|
||||||
public class ActivityManager : IActivityManager
|
|
||||||
{
|
|
||||||
private readonly IActivityRepository _repo;
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
|
|
||||||
public ActivityManager(IActivityRepository repo, IUserManager userManager)
|
|
||||||
{
|
|
||||||
_repo = repo;
|
|
||||||
_userManager = userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
|
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
|
||||||
{
|
|
||||||
entry.Date = DateTime.UtcNow;
|
|
||||||
|
|
||||||
_repo.Create(entry);
|
|
||||||
|
|
||||||
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
|
||||||
{
|
|
||||||
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
|
|
||||||
|
|
||||||
foreach (var item in result.Items)
|
|
||||||
{
|
|
||||||
if (item.UserId == Guid.Empty)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = _userManager.GetUserById(item.UserId);
|
|
||||||
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
var dto = _userManager.GetUserDto(user);
|
|
||||||
item.UserPrimaryImageTag = dto.PrimaryImageTag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
|
|
||||||
{
|
|
||||||
return GetActivityLogEntries(minDate, null, startIndex, limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,317 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Emby.Server.Implementations.Data;
|
|
||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Model.Activity;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using SQLitePCL.pretty;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
|
||||||
{
|
|
||||||
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
|
|
||||||
{
|
|
||||||
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
|
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
|
||||||
: base(logger)
|
|
||||||
{
|
|
||||||
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InitializeInternal();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
|
|
||||||
|
|
||||||
_fileSystem.DeleteFile(DbFilePath);
|
|
||||||
|
|
||||||
InitializeInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeInternal()
|
|
||||||
{
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
connection.RunQueries(new[]
|
|
||||||
{
|
|
||||||
"create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
|
|
||||||
"drop index if exists idx_ActivityLogEntries"
|
|
||||||
});
|
|
||||||
|
|
||||||
TryMigrate(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryMigrate(ManagedConnection connection)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (TableExists(connection, "ActivityLogEntries"))
|
|
||||||
{
|
|
||||||
connection.RunQueries(new[]
|
|
||||||
{
|
|
||||||
"INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries",
|
|
||||||
"drop table if exists ActivityLogEntries"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Error migrating activity log database");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
|
||||||
{
|
|
||||||
if (entry == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
connection.RunInTransaction(
|
|
||||||
db =>
|
|
||||||
{
|
|
||||||
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
|
|
||||||
{
|
|
||||||
statement.TryBind("@Name", entry.Name);
|
|
||||||
|
|
||||||
statement.TryBind("@Overview", entry.Overview);
|
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
|
||||||
{
|
|
||||||
statement.TryBindNull("@UserId");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
|
||||||
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
|
||||||
|
|
||||||
statement.MoveNext();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
TransactionMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(ActivityLogEntry entry)
|
|
||||||
{
|
|
||||||
if (entry == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
connection.RunInTransaction(
|
|
||||||
db =>
|
|
||||||
{
|
|
||||||
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
|
|
||||||
{
|
|
||||||
statement.TryBind("@Id", entry.Id);
|
|
||||||
|
|
||||||
statement.TryBind("@Name", entry.Name);
|
|
||||||
statement.TryBind("@Overview", entry.Overview);
|
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
|
||||||
{
|
|
||||||
statement.TryBindNull("@UserId");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
|
||||||
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
|
||||||
|
|
||||||
statement.MoveNext();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
TransactionMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
|
||||||
{
|
|
||||||
var commandText = BaseActivitySelectText;
|
|
||||||
var whereClauses = new List<string>();
|
|
||||||
|
|
||||||
if (minDate.HasValue)
|
|
||||||
{
|
|
||||||
whereClauses.Add("DateCreated>=@DateCreated");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasUserId.HasValue)
|
|
||||||
{
|
|
||||||
if (hasUserId.Value)
|
|
||||||
{
|
|
||||||
whereClauses.Add("UserId not null");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
whereClauses.Add("UserId is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var whereTextWithoutPaging = whereClauses.Count == 0 ?
|
|
||||||
string.Empty :
|
|
||||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
|
||||||
|
|
||||||
if (startIndex.HasValue && startIndex.Value > 0)
|
|
||||||
{
|
|
||||||
var pagingWhereText = whereClauses.Count == 0 ?
|
|
||||||
string.Empty :
|
|
||||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
|
||||||
|
|
||||||
whereClauses.Add(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
|
|
||||||
pagingWhereText,
|
|
||||||
startIndex.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
var whereText = whereClauses.Count == 0 ?
|
|
||||||
string.Empty :
|
|
||||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
|
||||||
|
|
||||||
commandText += whereText;
|
|
||||||
|
|
||||||
commandText += " ORDER BY DateCreated DESC";
|
|
||||||
|
|
||||||
if (limit.HasValue)
|
|
||||||
{
|
|
||||||
commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
var statementTexts = new[]
|
|
||||||
{
|
|
||||||
commandText,
|
|
||||||
"select count (Id) from ActivityLog" + whereTextWithoutPaging
|
|
||||||
};
|
|
||||||
|
|
||||||
var list = new List<ActivityLogEntry>();
|
|
||||||
var result = new QueryResult<ActivityLogEntry>();
|
|
||||||
|
|
||||||
using (var connection = GetConnection(true))
|
|
||||||
{
|
|
||||||
connection.RunInTransaction(
|
|
||||||
db =>
|
|
||||||
{
|
|
||||||
var statements = PrepareAll(db, statementTexts).ToList();
|
|
||||||
|
|
||||||
using (var statement = statements[0])
|
|
||||||
{
|
|
||||||
if (minDate.HasValue)
|
|
||||||
{
|
|
||||||
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
|
||||||
{
|
|
||||||
list.Add(GetEntry(row));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var statement = statements[1])
|
|
||||||
{
|
|
||||||
if (minDate.HasValue)
|
|
||||||
{
|
|
||||||
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ReadTransactionMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Items = list;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader)
|
|
||||||
{
|
|
||||||
var index = 0;
|
|
||||||
|
|
||||||
var info = new ActivityLogEntry
|
|
||||||
{
|
|
||||||
Id = reader[index].ToInt64()
|
|
||||||
};
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.Name = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.Overview = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.ShortOverview = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.Type = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.ItemId = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.UserId = new Guid(reader[index].ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
info.Date = reader[index].ReadDateTime();
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,6 +15,11 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="programDataPath">The program data path.</param>
|
||||||
|
/// <param name="logDirectoryPath">The log directory path.</param>
|
||||||
|
/// <param name="configurationDirectoryPath">The configuration directory path.</param>
|
||||||
|
/// <param name="cacheDirectoryPath">The cache directory path.</param>
|
||||||
|
/// <param name="webDirectoryPath">The web directory path.</param>
|
||||||
protected BaseApplicationPaths(
|
protected BaseApplicationPaths(
|
||||||
string programDataPath,
|
string programDataPath,
|
||||||
string logDirectoryPath,
|
string logDirectoryPath,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -36,24 +36,22 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
configuration = Activator.CreateInstance(type);
|
configuration = Activator.CreateInstance(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
using var stream = new MemoryStream();
|
||||||
|
xmlSerializer.SerializeToStream(configuration, stream);
|
||||||
|
|
||||||
|
// Take the object we just got and serialize it back to bytes
|
||||||
|
var newBytes = stream.ToArray();
|
||||||
|
|
||||||
|
// If the file didn't exist before, or if something has changed, re-save
|
||||||
|
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
||||||
{
|
{
|
||||||
xmlSerializer.SerializeToStream(configuration, stream);
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
// Take the object we just got and serialize it back to bytes
|
// Save it after load in case we got new items
|
||||||
var newBytes = stream.ToArray();
|
File.WriteAllBytes(path, newBytes);
|
||||||
|
|
||||||
// If the file didn't exist before, or if something has changed, re-save
|
|
||||||
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
|
||||||
|
|
||||||
// Save it after load in case we got new items
|
|
||||||
File.WriteAllBytes(path, newBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return configuration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return configuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,10 +22,8 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var fileStream = File.OpenRead(sourceFile))
|
using var fileStream = File.OpenRead(sourceFile);
|
||||||
{
|
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
|
||||||
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -36,70 +34,61 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var reader = ReaderFactory.Open(source))
|
using var reader = ReaderFactory.Open(source);
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
var options = new ExtractionOptions();
|
ExtractFullPath = true
|
||||||
options.ExtractFullPath = true;
|
};
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
if (overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
options.Overwrite = true;
|
options.Overwrite = true;
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var reader = ZipReader.Open(source))
|
using var reader = ZipReader.Open(source);
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
var options = new ExtractionOptions();
|
ExtractFullPath = true,
|
||||||
options.ExtractFullPath = true;
|
Overwrite = overwriteExistingFiles
|
||||||
|
};
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
{
|
|
||||||
options.Overwrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var reader = GZipReader.Open(source))
|
using var reader = GZipReader.Open(source);
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
var options = new ExtractionOptions();
|
ExtractFullPath = true,
|
||||||
options.ExtractFullPath = true;
|
Overwrite = overwriteExistingFiles
|
||||||
|
};
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
{
|
|
||||||
options.Overwrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
|
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
|
||||||
{
|
{
|
||||||
using (var reader = GZipReader.Open(source))
|
using var reader = GZipReader.Open(source);
|
||||||
|
if (reader.MoveToNextEntry())
|
||||||
{
|
{
|
||||||
if (reader.MoveToNextEntry())
|
var entry = reader.Entry;
|
||||||
{
|
|
||||||
var entry = reader.Entry;
|
|
||||||
|
|
||||||
var filename = entry.Key;
|
var filename = entry.Key;
|
||||||
if (string.IsNullOrWhiteSpace(filename))
|
if (string.IsNullOrWhiteSpace(filename))
|
||||||
{
|
{
|
||||||
filename = defaultFileName;
|
filename = defaultFileName;
|
||||||
}
|
|
||||||
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,10 +100,8 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var fileStream = File.OpenRead(sourceFile))
|
using var fileStream = File.OpenRead(sourceFile);
|
||||||
{
|
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
|
||||||
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -125,21 +112,15 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var archive = SevenZipArchive.Open(source))
|
using var archive = SevenZipArchive.Open(source);
|
||||||
|
using var reader = archive.ExtractAllEntries();
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
using (var reader = archive.ExtractAllEntries())
|
ExtractFullPath = true,
|
||||||
{
|
Overwrite = overwriteExistingFiles
|
||||||
var options = new ExtractionOptions();
|
};
|
||||||
options.ExtractFullPath = true;
|
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
{
|
|
||||||
options.Overwrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -150,10 +131,8 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var fileStream = File.OpenRead(sourceFile))
|
using var fileStream = File.OpenRead(sourceFile);
|
||||||
{
|
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
|
||||||
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -164,21 +143,15 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var archive = TarArchive.Open(source))
|
using var archive = TarArchive.Open(source);
|
||||||
|
using var reader = archive.ExtractAllEntries();
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
using (var reader = archive.ExtractAllEntries())
|
ExtractFullPath = true,
|
||||||
{
|
Overwrite = overwriteExistingFiles
|
||||||
var options = new ExtractionOptions();
|
};
|
||||||
options.ExtractFullPath = true;
|
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
{
|
|
||||||
options.Overwrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Model.Branding;
|
using MediaBrowser.Model.Branding;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Branding
|
namespace Emby.Server.Implementations.Branding
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A configuration factory for <see cref="BrandingOptions"/>.
|
||||||
|
/// </summary>
|
||||||
public class BrandingConfigurationFactory : IConfigurationFactory
|
public class BrandingConfigurationFactory : IConfigurationFactory
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
|
|
|
@ -33,18 +33,18 @@ namespace Emby.Server.Implementations.Browser
|
||||||
/// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
|
/// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appHost">The application host.</param>
|
/// <param name="appHost">The application host.</param>
|
||||||
/// <param name="url">The URL.</param>
|
/// <param name="relativeUrl">The URL to open, relative to the server base URL.</param>
|
||||||
private static void TryOpenUrl(IServerApplicationHost appHost, string url)
|
private static void TryOpenUrl(IServerApplicationHost appHost, string relativeUrl)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string baseUrl = appHost.GetLocalApiUrl("localhost");
|
string baseUrl = appHost.GetLocalApiUrl("localhost");
|
||||||
appHost.LaunchUrl(baseUrl + url);
|
appHost.LaunchUrl(baseUrl + relativeUrl);
|
||||||
}
|
}
|
||||||
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}", url);
|
logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
|
@ -11,6 +10,9 @@ using MediaBrowser.Model.Dto;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A media source provider for channels.
|
||||||
|
/// </summary>
|
||||||
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
|
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
|
||||||
{
|
{
|
||||||
private readonly ChannelManager _channelManager;
|
private readonly ChannelManager _channelManager;
|
||||||
|
@ -27,12 +29,9 @@ namespace Emby.Server.Implementations.Channels
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
|
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (item.SourceType == SourceType.Channel)
|
return item.SourceType == SourceType.Channel
|
||||||
{
|
? _channelManager.GetDynamicMediaSources(item, cancellationToken)
|
||||||
return _channelManager.GetDynamicMediaSources(item, cancellationToken);
|
: Task.FromResult(Enumerable.Empty<MediaSourceInfo>());
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -11,22 +9,32 @@ using MediaBrowser.Model.Entities;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An image provider for channels.
|
||||||
|
/// </summary>
|
||||||
public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
|
public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
|
||||||
{
|
{
|
||||||
private readonly IChannelManager _channelManager;
|
private readonly IChannelManager _channelManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChannelImageProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelManager">The channel manager.</param>
|
||||||
public ChannelImageProvider(IChannelManager channelManager)
|
public ChannelImageProvider(IChannelManager channelManager)
|
||||||
{
|
{
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Name => "Channel Image Provider";
|
public string Name => "Channel Image Provider";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return GetChannel(item).GetSupportedChannelImages();
|
return GetChannel(item).GetSupportedChannelImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
|
public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var channel = GetChannel(item);
|
var channel = GetChannel(item);
|
||||||
|
@ -34,6 +42,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return channel.GetChannelImage(type, cancellationToken);
|
return channel.GetChannelImage(type, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool Supports(BaseItem item)
|
public bool Supports(BaseItem item)
|
||||||
{
|
{
|
||||||
return item is Channel;
|
return item is Channel;
|
||||||
|
@ -46,6 +55,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return ((ChannelManager)_channelManager).GetChannelProvider(channel);
|
return ((ChannelManager)_channelManager).GetChannelProvider(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
|
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
return GetSupportedImages(item).Any(i => !item.HasImage(i));
|
return GetSupportedImages(item).Any(i => !item.HasImage(i));
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -8,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;
|
||||||
|
@ -15,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;
|
||||||
|
@ -26,16 +23,24 @@ 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
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The LiveTV channel manager.
|
||||||
|
/// </summary>
|
||||||
public class ChannelManager : IChannelManager
|
public class ChannelManager : IChannelManager
|
||||||
{
|
{
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
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,6 +51,18 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
/// <param name="dtoService">The dto service.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="config">The server configuration manager.</param>
|
||||||
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
|
/// <param name="userDataManager">The user data manager.</param>
|
||||||
|
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||||
|
/// <param name="providerManager">The provider manager.</param>
|
||||||
public ChannelManager(
|
public ChannelManager(
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
IDtoService dtoService,
|
IDtoService dtoService,
|
||||||
|
@ -72,11 +89,13 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
|
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void AddParts(IEnumerable<IChannel> channels)
|
public void AddParts(IEnumerable<IChannel> channels)
|
||||||
{
|
{
|
||||||
Channels = channels.ToArray();
|
Channels = channels.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool EnableMediaSourceDisplay(BaseItem item)
|
public bool EnableMediaSourceDisplay(BaseItem item)
|
||||||
{
|
{
|
||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
|
@ -85,6 +104,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return !(channel is IDisableMediaSourceDisplay);
|
return !(channel is IDisableMediaSourceDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool CanDelete(BaseItem item)
|
public bool CanDelete(BaseItem item)
|
||||||
{
|
{
|
||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
|
@ -93,6 +113,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
|
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool EnableMediaProbe(BaseItem item)
|
public bool EnableMediaProbe(BaseItem item)
|
||||||
{
|
{
|
||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
|
@ -101,6 +122,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return channel is ISupportsMediaProbe;
|
return channel is ISupportsMediaProbe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Task DeleteItem(BaseItem item)
|
public Task DeleteItem(BaseItem item)
|
||||||
{
|
{
|
||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
|
@ -127,11 +149,16 @@ namespace Emby.Server.Implementations.Channels
|
||||||
.OrderBy(i => i.Name);
|
.OrderBy(i => i.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the installed channel IDs.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An <see cref="IEnumerable{T}"/> containing installed channel IDs.</returns>
|
||||||
public IEnumerable<Guid> GetInstalledChannelIds()
|
public IEnumerable<Guid> GetInstalledChannelIds()
|
||||||
{
|
{
|
||||||
return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
|
return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
|
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
|
||||||
{
|
{
|
||||||
var user = query.UserId.Equals(Guid.Empty)
|
var user = query.UserId.Equals(Guid.Empty)
|
||||||
|
@ -249,6 +276,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
|
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
|
||||||
{
|
{
|
||||||
var user = query.UserId.Equals(Guid.Empty)
|
var user = query.UserId.Equals(Guid.Empty)
|
||||||
|
@ -271,6 +299,12 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes the associated channels.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||||
|
/// <returns>The completed task.</returns>
|
||||||
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var allChannelsList = GetAllChannels().ToList();
|
var allChannelsList = GetAllChannels().ToList();
|
||||||
|
@ -304,14 +338,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
private Channel GetChannelEntity(IChannel channel)
|
private Channel GetChannelEntity(IChannel channel)
|
||||||
{
|
{
|
||||||
var item = GetChannel(GetInternalChannelId(channel.Name));
|
return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
|
||||||
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
item = GetChannel(channel, CancellationToken.None).Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item)
|
private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item)
|
||||||
|
@ -350,6 +377,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
_jsonSerializer.SerializeToFile(mediaSources, path);
|
_jsonSerializer.SerializeToFile(mediaSources, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
|
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item);
|
IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item);
|
||||||
|
@ -359,6 +387,12 @@ namespace Emby.Server.Implementations.Channels
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the dynamic media sources based on the provided item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||||
|
/// <returns>The task representing the operation to get the media sources.</returns>
|
||||||
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken)
|
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var channel = GetChannel(item.ChannelId);
|
var channel = GetChannel(item.ChannelId);
|
||||||
|
@ -403,7 +437,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info)
|
private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info)
|
||||||
{
|
{
|
||||||
info.RunTimeTicks = info.RunTimeTicks ?? item.RunTimeTicks;
|
info.RunTimeTicks ??= item.RunTimeTicks;
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -481,31 +515,33 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
private static string GetOfficialRating(ChannelParentalRating rating)
|
private static string GetOfficialRating(ChannelParentalRating rating)
|
||||||
{
|
{
|
||||||
switch (rating)
|
return rating switch
|
||||||
{
|
{
|
||||||
case ChannelParentalRating.Adult:
|
ChannelParentalRating.Adult => "XXX",
|
||||||
return "XXX";
|
ChannelParentalRating.UsR => "R",
|
||||||
case ChannelParentalRating.UsR:
|
ChannelParentalRating.UsPG13 => "PG-13",
|
||||||
return "R";
|
ChannelParentalRating.UsPG => "PG",
|
||||||
case ChannelParentalRating.UsPG13:
|
_ => null
|
||||||
return "PG-13";
|
};
|
||||||
case ChannelParentalRating.UsPG:
|
|
||||||
return "PG";
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a channel with the provided Guid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The Guid.</param>
|
||||||
|
/// <returns>The corresponding channel.</returns>
|
||||||
public Channel GetChannel(Guid id)
|
public Channel GetChannel(Guid id)
|
||||||
{
|
{
|
||||||
return _libraryManager.GetItemById(id) as Channel;
|
return _libraryManager.GetItemById(id) as Channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Channel GetChannel(string id)
|
public Channel GetChannel(string id)
|
||||||
{
|
{
|
||||||
return _libraryManager.GetItemById(id) as Channel;
|
return _libraryManager.GetItemById(id) as Channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public ChannelFeatures[] GetAllChannelFeatures()
|
public ChannelFeatures[] GetAllChannelFeatures()
|
||||||
{
|
{
|
||||||
return _libraryManager.GetItemIds(
|
return _libraryManager.GetItemIds(
|
||||||
|
@ -516,6 +552,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
|
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public ChannelFeatures GetChannelFeatures(string id)
|
public ChannelFeatures GetChannelFeatures(string id)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(id))
|
if (string.IsNullOrEmpty(id))
|
||||||
|
@ -529,6 +566,11 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
|
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the provided Guid supports external transfer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelId">The Guid.</param>
|
||||||
|
/// <returns>Whether or not the provided Guid supports external transfer.</returns>
|
||||||
public bool SupportsExternalTransfer(Guid channelId)
|
public bool SupportsExternalTransfer(Guid channelId)
|
||||||
{
|
{
|
||||||
var channelProvider = GetChannelProvider(channelId);
|
var channelProvider = GetChannelProvider(channelId);
|
||||||
|
@ -536,6 +578,13 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
|
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the provided channel's supported features.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channel">The channel.</param>
|
||||||
|
/// <param name="provider">The provider.</param>
|
||||||
|
/// <param name="features">The features.</param>
|
||||||
|
/// <returns>The supported features.</returns>
|
||||||
public ChannelFeatures GetChannelFeaturesDto(
|
public ChannelFeatures GetChannelFeaturesDto(
|
||||||
Channel channel,
|
Channel channel,
|
||||||
IChannel provider,
|
IChannel provider,
|
||||||
|
@ -570,6 +619,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
|
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
|
public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
|
var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -588,6 +638,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken)
|
public async Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
|
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
|
||||||
|
@ -666,6 +717,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Get the internal channel entity
|
// Get the internal channel entity
|
||||||
|
@ -727,6 +779,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return _libraryManager.GetItemsResult(query);
|
return _libraryManager.GetItemsResult(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
|
public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -742,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,
|
||||||
|
@ -796,7 +850,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
var query = new InternalChannelItemQuery
|
var query = new InternalChannelItemQuery
|
||||||
{
|
{
|
||||||
UserId = user == null ? Guid.Empty : user.Id,
|
UserId = user?.Id ?? Guid.Empty,
|
||||||
SortBy = sortField,
|
SortBy = sortField,
|
||||||
SortDescending = sortDescending,
|
SortDescending = sortDescending,
|
||||||
FolderId = externalFolderId
|
FolderId = externalFolderId
|
||||||
|
@ -923,60 +977,32 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
if (info.Type == ChannelItemType.Folder)
|
if (info.Type == ChannelItemType.Folder)
|
||||||
{
|
{
|
||||||
if (info.FolderType == ChannelFolderType.MusicAlbum)
|
item = info.FolderType switch
|
||||||
{
|
{
|
||||||
item = GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew);
|
ChannelFolderType.MusicAlbum => GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew),
|
||||||
}
|
ChannelFolderType.MusicArtist => GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew),
|
||||||
else if (info.FolderType == ChannelFolderType.MusicArtist)
|
ChannelFolderType.PhotoAlbum => GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew),
|
||||||
{
|
ChannelFolderType.Series => GetItemById<Series>(info.Id, channelProvider.Name, out isNew),
|
||||||
item = GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew);
|
ChannelFolderType.Season => GetItemById<Season>(info.Id, channelProvider.Name, out isNew),
|
||||||
}
|
_ => GetItemById<Folder>(info.Id, channelProvider.Name, out isNew)
|
||||||
else if (info.FolderType == ChannelFolderType.PhotoAlbum)
|
};
|
||||||
{
|
|
||||||
item = GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
else if (info.FolderType == ChannelFolderType.Series)
|
|
||||||
{
|
|
||||||
item = GetItemById<Series>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
else if (info.FolderType == ChannelFolderType.Season)
|
|
||||||
{
|
|
||||||
item = GetItemById<Season>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item = GetItemById<Folder>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (info.MediaType == ChannelMediaType.Audio)
|
else if (info.MediaType == ChannelMediaType.Audio)
|
||||||
{
|
{
|
||||||
if (info.ContentType == ChannelMediaContentType.Podcast)
|
item = info.ContentType == ChannelMediaContentType.Podcast
|
||||||
{
|
? GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew)
|
||||||
item = GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew);
|
: GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item = GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (info.ContentType == ChannelMediaContentType.Episode)
|
item = info.ContentType switch
|
||||||
{
|
{
|
||||||
item = GetItemById<Episode>(info.Id, channelProvider.Name, out isNew);
|
ChannelMediaContentType.Episode => GetItemById<Episode>(info.Id, channelProvider.Name, out isNew),
|
||||||
}
|
ChannelMediaContentType.Movie => GetItemById<Movie>(info.Id, channelProvider.Name, out isNew),
|
||||||
else if (info.ContentType == ChannelMediaContentType.Movie)
|
var x when x == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer
|
||||||
{
|
=> GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew),
|
||||||
item = GetItemById<Movie>(info.Id, channelProvider.Name, out isNew);
|
_ => GetItemById<Video>(info.Id, channelProvider.Name, out isNew)
|
||||||
}
|
};
|
||||||
else if (info.ContentType == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer)
|
|
||||||
{
|
|
||||||
item = GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item = GetItemById<Video>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var enableMediaProbe = channelProvider is ISupportsMediaProbe;
|
var enableMediaProbe = channelProvider is ISupportsMediaProbe;
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -11,12 +9,21 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A task to remove all non-installed channels from the database.
|
||||||
|
/// </summary>
|
||||||
public class ChannelPostScanTask
|
public class ChannelPostScanTask
|
||||||
{
|
{
|
||||||
private readonly IChannelManager _channelManager;
|
private readonly IChannelManager _channelManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChannelPostScanTask"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelManager">The channel manager.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
|
public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
|
@ -24,6 +31,12 @@ namespace Emby.Server.Implementations.Channels
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs this task.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>The completed task.</returns>
|
||||||
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CleanDatabase(cancellationToken);
|
CleanDatabase(cancellationToken);
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -13,13 +11,23 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The "Refresh Channels" scheduled task.
|
||||||
|
/// </summary>
|
||||||
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RefreshChannelsScheduledTask"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelManager">The channel manager.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="localization">The localization manager.</param>
|
||||||
public RefreshChannelsScheduledTask(
|
public RefreshChannelsScheduledTask(
|
||||||
IChannelManager channelManager,
|
IChannelManager channelManager,
|
||||||
ILogger<RefreshChannelsScheduledTask> logger,
|
ILogger<RefreshChannelsScheduledTask> logger,
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Server.Implementations.Images;
|
using Emby.Server.Implementations.Images;
|
||||||
|
@ -15,8 +13,18 @@ using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Collections
|
namespace Emby.Server.Implementations.Collections
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A collection image provider.
|
||||||
|
/// </summary>
|
||||||
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
|
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CollectionImageProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
|
/// <param name="providerManager">The provider manager.</param>
|
||||||
|
/// <param name="applicationPaths">The application paths.</param>
|
||||||
|
/// <param name="imageProcessor">The image processor.</param>
|
||||||
public CollectionImageProvider(
|
public CollectionImageProvider(
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IProviderManager providerManager,
|
IProviderManager providerManager,
|
||||||
|
@ -26,6 +34,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
protected override bool Supports(BaseItem item)
|
protected override bool Supports(BaseItem item)
|
||||||
{
|
{
|
||||||
// Right now this is the only way to prevent this image from getting created ahead of internet image providers
|
// Right now this is the only way to prevent this image from getting created ahead of internet image providers
|
||||||
|
@ -37,6 +46,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
return base.Supports(item);
|
return base.Supports(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||||
{
|
{
|
||||||
var playlist = (BoxSet)item;
|
var playlist = (BoxSet)item;
|
||||||
|
@ -46,13 +56,12 @@ namespace Emby.Server.Implementations.Collections
|
||||||
{
|
{
|
||||||
var subItem = i;
|
var subItem = i;
|
||||||
|
|
||||||
if (subItem is Episode episode)
|
var episode = subItem as Episode;
|
||||||
|
|
||||||
|
var series = episode?.Series;
|
||||||
|
if (series != null && series.HasImage(ImageType.Primary))
|
||||||
{
|
{
|
||||||
var series = episode.Series;
|
return series;
|
||||||
if (series != null && series.HasImage(ImageType.Primary))
|
|
||||||
{
|
|
||||||
return series;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subItem.HasImage(ImageType.Primary))
|
if (subItem.HasImage(ImageType.Primary))
|
||||||
|
@ -78,6 +87,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||||
{
|
{
|
||||||
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
|
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
@ -7,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;
|
||||||
|
@ -23,16 +22,29 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Collections
|
namespace Emby.Server.Implementations.Collections
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The collection manager.
|
||||||
|
/// </summary>
|
||||||
public class CollectionManager : ICollectionManager
|
public class CollectionManager : ICollectionManager
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CollectionManager"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="appPaths">The application paths.</param>
|
||||||
|
/// <param name="localizationManager">The localization manager.</param>
|
||||||
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
|
/// <param name="iLibraryMonitor">The library monitor.</param>
|
||||||
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
|
/// <param name="providerManager">The provider manager.</param>
|
||||||
public CollectionManager(
|
public CollectionManager(
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IApplicationPaths appPaths,
|
IApplicationPaths appPaths,
|
||||||
|
@ -45,16 +57,19 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
|
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
|
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
|
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
|
||||||
|
|
||||||
private IEnumerable<Folder> FindFolders(string path)
|
private IEnumerable<Folder> FindFolders(string path)
|
||||||
|
@ -116,6 +131,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
: folder.GetChildren(user, true).OfType<BoxSet>();
|
: folder.GetChildren(user, true).OfType<BoxSet>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public BoxSet CreateCollection(CollectionCreationOptions options)
|
public BoxSet CreateCollection(CollectionCreationOptions options)
|
||||||
{
|
{
|
||||||
var name = options.Name;
|
var name = options.Name;
|
||||||
|
@ -180,11 +196,13 @@ namespace Emby.Server.Implementations.Collections
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
|
public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
|
||||||
{
|
{
|
||||||
AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
|
AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
|
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
|
||||||
{
|
{
|
||||||
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
|
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
|
||||||
|
@ -247,11 +265,13 @@ namespace Emby.Server.Implementations.Collections
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void RemoveFromCollection(Guid collectionId, IEnumerable<string> itemIds)
|
public void RemoveFromCollection(Guid collectionId, IEnumerable<string> itemIds)
|
||||||
{
|
{
|
||||||
RemoveFromCollection(collectionId, itemIds.Select(i => new Guid(i)));
|
RemoveFromCollection(collectionId, itemIds.Select(i => new Guid(i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
|
public void RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
|
||||||
{
|
{
|
||||||
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
|
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
|
||||||
|
@ -305,6 +325,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
|
public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
|
||||||
{
|
{
|
||||||
var results = new Dictionary<Guid, BaseItem>();
|
var results = new Dictionary<Guid, BaseItem>();
|
||||||
|
@ -313,9 +334,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
var grouping = item as ISupportsBoxSetGrouping;
|
if (!(item is ISupportsBoxSetGrouping))
|
||||||
|
|
||||||
if (grouping == null)
|
|
||||||
{
|
{
|
||||||
results[item.Id] = item;
|
results[item.Id] = item;
|
||||||
}
|
}
|
||||||
|
@ -345,12 +364,21 @@ namespace Emby.Server.Implementations.Collections
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The collection manager entry point.
|
||||||
|
/// </summary>
|
||||||
public sealed class CollectionManagerEntryPoint : IServerEntryPoint
|
public sealed class CollectionManagerEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
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>
|
||||||
|
/// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="collectionManager">The collection manager.</param>
|
||||||
|
/// <param name="config">The server configuration manager.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
public CollectionManagerEntryPoint(
|
public CollectionManagerEntryPoint(
|
||||||
ICollectionManager collectionManager,
|
ICollectionManager collectionManager,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
|
|
|
@ -67,23 +67,22 @@ namespace Emby.Server.Implementations.Configuration
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the metadata path.
|
/// Updates the metadata path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <exception cref="UnauthorizedAccessException">If the directory does not exist, and the caller does not have the required permission to create it.</exception>
|
||||||
|
/// <exception cref="NotSupportedException">If there is a custom path transcoding path specified, but it is invalid.</exception>
|
||||||
|
/// <exception cref="IOException">If the directory does not exist, and it also could not be created.</exception>
|
||||||
private void UpdateMetadataPath()
|
private void UpdateMetadataPath()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(Configuration.MetadataPath))
|
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = string.IsNullOrWhiteSpace(Configuration.MetadataPath)
|
||||||
{
|
? ApplicationPaths.DefaultInternalMetadataPath
|
||||||
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
|
: Configuration.MetadataPath;
|
||||||
}
|
Directory.CreateDirectory(ApplicationPaths.InternalMetadataPath);
|
||||||
else
|
|
||||||
{
|
|
||||||
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Configuration.MetadataPath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replaces the configuration.
|
/// Replaces the configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="newConfiguration">The new configuration.</param>
|
/// <param name="newConfiguration">The new configuration.</param>
|
||||||
/// <exception cref="DirectoryNotFoundException"></exception>
|
/// <exception cref="DirectoryNotFoundException">If the configuration path doesn't exist.</exception>
|
||||||
public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
|
public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
|
||||||
{
|
{
|
||||||
var newConfig = (ServerConfiguration)newConfiguration;
|
var newConfig = (ServerConfiguration)newConfiguration;
|
||||||
|
@ -91,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);
|
||||||
}
|
}
|
||||||
|
@ -194,12 +193,6 @@ namespace Emby.Server.Implementations.Configuration
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.CameraUploadUpgraded)
|
|
||||||
{
|
|
||||||
config.CameraUploadUpgraded = true;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.CollectionsUpgraded)
|
if (!config.CollectionsUpgraded)
|
||||||
{
|
{
|
||||||
config.CollectionsUpgraded = true;
|
config.CollectionsUpgraded = true;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Server.Implementations.HttpServer;
|
using Emby.Server.Implementations.HttpServer;
|
||||||
using Emby.Server.Implementations.Updates;
|
using Emby.Server.Implementations.Updates;
|
||||||
using MediaBrowser.Providers.Music;
|
|
||||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations
|
namespace Emby.Server.Implementations
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -31,7 +33,7 @@ namespace Emby.Server.Implementations.Cryptography
|
||||||
|
|
||||||
private RandomNumberGenerator _randomNumberGenerator;
|
private RandomNumberGenerator _randomNumberGenerator;
|
||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CryptographyProvider"/> class.
|
/// Initializes a new instance of the <see cref="CryptographyProvider"/> class.
|
||||||
|
@ -56,15 +58,13 @@ namespace Emby.Server.Implementations.Cryptography
|
||||||
{
|
{
|
||||||
// downgrading for now as we need this library to be dotnetstandard compliant
|
// downgrading for now as we need this library to be dotnetstandard compliant
|
||||||
// with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
|
// with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
|
||||||
if (method == DefaultHashMethod)
|
if (method != DefaultHashMethod)
|
||||||
{
|
{
|
||||||
using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
|
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
|
||||||
{
|
|
||||||
return r.GetBytes(32);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
|
using var r = new Rfc2898DeriveBytes(bytes, salt, iterations);
|
||||||
|
return r.GetBytes(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -74,25 +74,22 @@ namespace Emby.Server.Implementations.Cryptography
|
||||||
{
|
{
|
||||||
return PBKDF2(hashMethod, bytes, salt, DefaultIterations);
|
return PBKDF2(hashMethod, bytes, salt, DefaultIterations);
|
||||||
}
|
}
|
||||||
else if (_supportedHashMethods.Contains(hashMethod))
|
|
||||||
|
if (!_supportedHashMethods.Contains(hashMethod))
|
||||||
{
|
{
|
||||||
using (var h = HashAlgorithm.Create(hashMethod))
|
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
|
||||||
{
|
|
||||||
if (salt.Length == 0)
|
|
||||||
{
|
|
||||||
return h.ComputeHash(bytes);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
byte[] salted = new byte[bytes.Length + salt.Length];
|
|
||||||
Array.Copy(bytes, salted, bytes.Length);
|
|
||||||
Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
|
|
||||||
return h.ComputeHash(salted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
|
using var h = HashAlgorithm.Create(hashMethod);
|
||||||
|
if (salt.Length == 0)
|
||||||
|
{
|
||||||
|
return h.ComputeHash(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] salted = new byte[bytes.Length + salt.Length];
|
||||||
|
Array.Copy(bytes, salted, bytes.Length);
|
||||||
|
Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
|
||||||
|
return h.ComputeHash(salted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -134,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.
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
@ -33,18 +35,17 @@ using SQLitePCL.pretty;
|
||||||
namespace Emby.Server.Implementations.Data
|
namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class SQLiteItemRepository
|
/// Class SQLiteItemRepository.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SqliteItemRepository : BaseSqliteRepository, IItemRepository
|
public class SqliteItemRepository : BaseSqliteRepository, IItemRepository
|
||||||
{
|
{
|
||||||
private const string ChaptersTableName = "Chapters2";
|
private const string ChaptersTableName = "Chapters2";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _app paths
|
|
||||||
/// </summary>
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
// TODO: Remove this dependency. GetImageCacheTag() is the only method used and it can be converted to a static helper method
|
||||||
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
|
||||||
private readonly TypeMapper _typeMapper;
|
private readonly TypeMapper _typeMapper;
|
||||||
private readonly JsonSerializerOptions _jsonOptions;
|
private readonly JsonSerializerOptions _jsonOptions;
|
||||||
|
@ -71,7 +72,8 @@ namespace Emby.Server.Implementations.Data
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
ILogger<SqliteItemRepository> logger,
|
ILogger<SqliteItemRepository> logger,
|
||||||
ILocalizationManager localization)
|
ILocalizationManager localization,
|
||||||
|
IImageProcessor imageProcessor)
|
||||||
: base(logger)
|
: base(logger)
|
||||||
{
|
{
|
||||||
if (config == null)
|
if (config == null)
|
||||||
|
@ -82,6 +84,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
_config = config;
|
_config = config;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
|
_imageProcessor = imageProcessor;
|
||||||
|
|
||||||
_typeMapper = new TypeMapper();
|
_typeMapper = new TypeMapper();
|
||||||
_jsonOptions = JsonDefaults.GetOptions();
|
_jsonOptions = JsonDefaults.GetOptions();
|
||||||
|
@ -98,8 +101,6 @@ namespace Emby.Server.Implementations.Data
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override TempStoreMode TempStore => TempStoreMode.Memory;
|
protected override TempStoreMode TempStore => TempStoreMode.Memory;
|
||||||
|
|
||||||
public IImageProcessor ImageProcessor { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the connection to the database
|
/// Opens the connection to the database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1142,24 +1143,24 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
public string ToValueString(ItemImageInfo image)
|
public string ToValueString(ItemImageInfo image)
|
||||||
{
|
{
|
||||||
var delimeter = "*";
|
const string Delimeter = "*";
|
||||||
|
|
||||||
var path = image.Path;
|
var path = image.Path ?? string.Empty;
|
||||||
|
var hash = image.BlurHash ?? string.Empty;
|
||||||
if (path == null)
|
|
||||||
{
|
|
||||||
path = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetPathToSave(path) +
|
return GetPathToSave(path) +
|
||||||
delimeter +
|
Delimeter +
|
||||||
image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
|
image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
|
||||||
delimeter +
|
Delimeter +
|
||||||
image.Type +
|
image.Type +
|
||||||
delimeter +
|
Delimeter +
|
||||||
image.Width.ToString(CultureInfo.InvariantCulture) +
|
image.Width.ToString(CultureInfo.InvariantCulture) +
|
||||||
delimeter +
|
Delimeter +
|
||||||
image.Height.ToString(CultureInfo.InvariantCulture);
|
image.Height.ToString(CultureInfo.InvariantCulture) +
|
||||||
|
Delimeter +
|
||||||
|
// Replace delimiters with other characters.
|
||||||
|
// This can be removed when we migrate to a proper DB.
|
||||||
|
hash.Replace('*', '/').Replace('|', '\\');
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
||||||
|
@ -1193,6 +1194,11 @@ namespace Emby.Server.Implementations.Data
|
||||||
image.Width = width;
|
image.Width = width;
|
||||||
image.Height = height;
|
image.Height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parts.Length >= 6)
|
||||||
|
{
|
||||||
|
image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
|
@ -1620,11 +1626,11 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
if (!reader.IsDBNull(index))
|
if (!reader.IsDBNull(index))
|
||||||
{
|
{
|
||||||
IEnumerable<MetadataFields> GetLockedFields(string s)
|
IEnumerable<MetadataField> GetLockedFields(string s)
|
||||||
{
|
{
|
||||||
foreach (var i in s.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
|
foreach (var i in s.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
if (Enum.TryParse(i, true, out MetadataFields parsedValue))
|
if (Enum.TryParse(i, true, out MetadataField parsedValue))
|
||||||
{
|
{
|
||||||
yield return parsedValue;
|
yield return parsedValue;
|
||||||
}
|
}
|
||||||
|
@ -1972,6 +1978,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
/// Gets the chapter.
|
/// Gets the chapter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">The reader.</param>
|
/// <param name="reader">The reader.</param>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
/// <returns>ChapterInfo.</returns>
|
/// <returns>ChapterInfo.</returns>
|
||||||
private ChapterInfo GetChapter(IReadOnlyList<IResultSetValue> reader, BaseItem item)
|
private ChapterInfo GetChapter(IReadOnlyList<IResultSetValue> reader, BaseItem item)
|
||||||
{
|
{
|
||||||
|
@ -1991,7 +1998,14 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(chapter.ImagePath))
|
if (!string.IsNullOrEmpty(chapter.ImagePath))
|
||||||
{
|
{
|
||||||
chapter.ImageTag = ImageProcessor.GetImageCacheTag(item, chapter);
|
try
|
||||||
|
{
|
||||||
|
chapter.ImageTag = _imageProcessor.GetImageCacheTag(item, chapter);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Failed to create image cache tag.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2720,7 +2734,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
foreach (var providerId in newItem.ProviderIds)
|
foreach (var providerId in newItem.ProviderIds)
|
||||||
{
|
{
|
||||||
if (providerId.Key == MetadataProviders.TmdbCollection.ToString())
|
if (providerId.Key == MetadataProvider.TmdbCollection.ToString())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -4310,7 +4324,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var pair in query.ExcludeProviderIds)
|
foreach (var pair in query.ExcludeProviderIds)
|
||||||
{
|
{
|
||||||
if (string.Equals(pair.Key, MetadataProviders.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -4339,7 +4353,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var pair in query.HasAnyProviderId)
|
foreach (var pair in query.HasAnyProviderId)
|
||||||
{
|
{
|
||||||
if (string.Equals(pair.Key, MetadataProviders.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user