mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-24 02:02:29 -04:00
Merge remote-tracking branch 'upstream/master' into simplify-https-config
This commit is contained in:
commit
93649ad77b
@ -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
|
||||||
@ -23,7 +23,7 @@ jobs:
|
|||||||
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
||||||
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
||||||
maxParallel: 2
|
maxParallel: 2
|
||||||
dependsOn: MainBuild
|
dependsOn: Build
|
||||||
steps:
|
steps:
|
||||||
- checkout: none
|
- checkout: none
|
||||||
|
|
||||||
|
@ -4,15 +4,14 @@ parameters:
|
|||||||
DotNetSdkVersion: 3.1.100
|
DotNetSdkVersion: 3.1.100
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: MainBuild
|
- job: Build
|
||||||
displayName: Main Build
|
displayName: Build
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
Release:
|
Release:
|
||||||
BuildConfiguration: Release
|
BuildConfiguration: Release
|
||||||
Debug:
|
Debug:
|
||||||
BuildConfiguration: Debug
|
BuildConfiguration: Debug
|
||||||
maxParallel: 2
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: "${{ parameters.LinuxImage }}"
|
vmImage: "${{ parameters.LinuxImage }}"
|
||||||
steps:
|
steps:
|
||||||
@ -21,41 +20,34 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
persistCredentials: true
|
persistCredentials: true
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
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 Client (PR)"
|
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: "10.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"
|
||||||
@ -69,33 +61,33 @@ jobs:
|
|||||||
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,30 @@ 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')
|
||||||
|
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 +60,17 @@ 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')
|
||||||
|
|
||||||
|
- task: SonarCloudPublish@1
|
||||||
|
displayName: 'Publish Quality Gate Result'
|
||||||
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
|
||||||
- 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'
|
||||||
inputs:
|
inputs:
|
||||||
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
||||||
targetdir: "$(Agent.TempDirectory)/merged/"
|
targetdir: "$(Agent.TempDirectory)/merged/"
|
||||||
@ -56,10 +79,11 @@ 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'
|
||||||
inputs:
|
inputs:
|
||||||
codeCoverageTool: "cobertura"
|
codeCoverageTool: "cobertura"
|
||||||
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
||||||
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
||||||
pathToSources: $(Build.SourcesDirectory)
|
pathToSources: $(Build.SourcesDirectory)
|
||||||
failIfCoverageEmpty: true
|
failIfCoverageEmpty: true
|
||||||
|
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
parameters:
|
|
||||||
WindowsImage: "windows-latest"
|
|
||||||
TestProjects: "tests/**/*Tests.csproj"
|
|
||||||
DotNetSdkVersion: 3.1.100
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: PublishWindows
|
|
||||||
displayName: Publish Windows
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ parameters.WindowsImage }}
|
|
||||||
steps:
|
|
||||||
- checkout: self
|
|
||||||
clean: true
|
|
||||||
submodules: true
|
|
||||||
persistCredentials: true
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
|
||||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone Web Client (PR)"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest'))
|
|
||||||
inputs:
|
|
||||||
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
|
||||||
|
|
||||||
- task: NodeTool@0
|
|
||||||
displayName: "Install Node"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
versionSpec: "10.x"
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Build Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: yarn install
|
|
||||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: "Copy Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
|
|
||||||
contents: "**"
|
|
||||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: false
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone UX Repository"
|
|
||||||
inputs:
|
|
||||||
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
|
|
||||||
|
|
||||||
- task: PowerShell@2
|
|
||||||
displayName: "Build NSIS Installer"
|
|
||||||
inputs:
|
|
||||||
targetType: "filePath"
|
|
||||||
filePath: ./deployment/windows/build-jellyfin.ps1
|
|
||||||
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
|
|
||||||
errorActionPreference: "stop"
|
|
||||||
workingDirectory: $(Build.SourcesDirectory)
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: "Copy NSIS Installer"
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/
|
|
||||||
contents: "jellyfin*.exe"
|
|
||||||
targetFolder: $(System.ArtifactsDirectory)/setup
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: true
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
|
||||||
displayName: "Publish Artifact Setup"
|
|
||||||
condition: succeeded()
|
|
||||||
inputs:
|
|
||||||
targetPath: "$(build.artifactstagingdirectory)/setup"
|
|
||||||
artifactName: "Jellyfin Server Setup"
|
|
@ -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
|
||||||
@ -27,11 +27,6 @@ jobs:
|
|||||||
Windows: "windows-latest"
|
Windows: "windows-latest"
|
||||||
macOS: "macos-latest"
|
macOS: "macos-latest"
|
||||||
|
|
||||||
- template: azure-pipelines-windows.yml
|
|
||||||
parameters:
|
|
||||||
WindowsImage: "windows-latest"
|
|
||||||
TestProjects: $(TestProjects)
|
|
||||||
|
|
||||||
- template: azure-pipelines-compat.yml
|
- template: azure-pipelines-compat.yml
|
||||||
parameters:
|
parameters:
|
||||||
Packages:
|
Packages:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -39,6 +39,7 @@ ProgramData*/
|
|||||||
CorePlugins*/
|
CorePlugins*/
|
||||||
ProgramData-Server*/
|
ProgramData-Server*/
|
||||||
ProgramData-UI*/
|
ProgramData-UI*/
|
||||||
|
MediaBrowser.WebDashboard/jellyfin-web/**
|
||||||
|
|
||||||
#################
|
#################
|
||||||
## Visual Studio
|
## Visual Studio
|
||||||
|
14
.vscode/extensions.json
vendored
Normal file
14
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||||
|
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||||
|
|
||||||
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
|
"recommendations": [
|
||||||
|
"ms-dotnettools.csharp",
|
||||||
|
"editorconfig.editorconfig"
|
||||||
|
],
|
||||||
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
@ -22,6 +22,7 @@
|
|||||||
- [cvium](https://github.com/cvium)
|
- [cvium](https://github.com/cvium)
|
||||||
- [dannymichel](https://github.com/dannymichel)
|
- [dannymichel](https://github.com/dannymichel)
|
||||||
- [DaveChild](https://github.com/DaveChild)
|
- [DaveChild](https://github.com/DaveChild)
|
||||||
|
- [Delgan](https://github.com/Delgan)
|
||||||
- [dcrdev](https://github.com/dcrdev)
|
- [dcrdev](https://github.com/dcrdev)
|
||||||
- [dhartung](https://github.com/dhartung)
|
- [dhartung](https://github.com/dhartung)
|
||||||
- [dinki](https://github.com/dinki)
|
- [dinki](https://github.com/dinki)
|
||||||
@ -128,6 +129,7 @@
|
|||||||
- [xosdy](https://github.com/xosdy)
|
- [xosdy](https://github.com/xosdy)
|
||||||
- [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)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
28
Dockerfile
28
Dockerfile
@ -1,5 +1,4 @@
|
|||||||
ARG DOTNET_VERSION=3.1
|
ARG DOTNET_VERSION=3.1
|
||||||
ARG FFMPEG_VERSION=latest
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=master
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
@ -17,7 +16,6 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
|||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
|
|
||||||
FROM debian:buster-slim
|
FROM debian:buster-slim
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
@ -27,32 +25,26 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
|||||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||||
|
|
||||||
COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
|
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
# Install dependencies:
|
# Install dependencies:
|
||||||
# libfontconfig1: needed for Skia
|
# mesa-va-drivers: needed for AMD VAAPI
|
||||||
# libgomp1: needed for ffmpeg
|
|
||||||
# libva-drm2: needed for ffmpeg
|
|
||||||
# mesa-va-drivers: needed for VAAPI
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
|
||||||
|
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
|
||||||
|
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
|
||||||
|
&& apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||||
libfontconfig1 \
|
|
||||||
libgomp1 \
|
|
||||||
libva-drm2 \
|
|
||||||
mesa-va-drivers \
|
mesa-va-drivers \
|
||||||
|
jellyfin-ffmpeg \
|
||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
|
||||||
vainfo \
|
|
||||||
i965-va-driver \
|
|
||||||
locales \
|
locales \
|
||||||
&& apt-get clean autoclean -y\
|
&& apt-get remove gnupg wget apt-transport-https -y \
|
||||||
&& apt-get autoremove -y\
|
&& apt-get clean autoclean -y \
|
||||||
|
&& apt-get autoremove -y \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /cache /config /media \
|
&& mkdir -p /cache /config /media \
|
||||||
&& chmod 777 /cache /config /media \
|
&& chmod 777 /cache /config /media \
|
||||||
&& ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
|
|
||||||
&& ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin \
|
|
||||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||||
|
|
||||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||||
@ -65,4 +57,4 @@ VOLUME /cache /config /media
|
|||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
"--datadir", "/config", \
|
"--datadir", "/config", \
|
||||||
"--cachedir", "/cache", \
|
"--cachedir", "/cache", \
|
||||||
"--ffmpeg", "/usr/local/bin/ffmpeg"]
|
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||||
|
@ -74,4 +74,4 @@ VOLUME /cache /config /media
|
|||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
"--datadir", "/config", \
|
"--datadir", "/config", \
|
||||||
"--cachedir", "/cache", \
|
"--cachedir", "/cache", \
|
||||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
|
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System.Buffers.Binary;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib
|
namespace DvdLib
|
||||||
@ -12,19 +12,12 @@ namespace DvdLib
|
|||||||
|
|
||||||
public override ushort ReadUInt16()
|
public override ushort ReadUInt16()
|
||||||
{
|
{
|
||||||
return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
|
return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override uint ReadUInt32()
|
public override uint ReadUInt32()
|
||||||
{
|
{
|
||||||
return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
|
return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] ReadAndReverseBytes(int count)
|
|
||||||
{
|
|
||||||
byte[] val = base.ReadBytes(count);
|
|
||||||
Array.Reverse(val, 0, count);
|
|
||||||
return val;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
{
|
{
|
||||||
@ -13,13 +12,10 @@ namespace DvdLib.Ifo
|
|||||||
|
|
||||||
private ushort _titleCount;
|
private ushort _titleCount;
|
||||||
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
|
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
|
||||||
private readonly IFileSystem _fileSystem;
|
public Dvd(string path)
|
||||||
|
|
||||||
public Dvd(string path, IFileSystem fileSystem)
|
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
|
||||||
Titles = new List<Title>();
|
Titles = new List<Title>();
|
||||||
var allFiles = _fileSystem.GetFiles(path, true).ToList();
|
var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
|
||||||
|
|
||||||
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
|
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
|
||||||
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
|
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
|
||||||
@ -76,7 +72,7 @@ namespace DvdLib.Ifo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
|
private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles)
|
||||||
{
|
{
|
||||||
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -1018,19 +1018,58 @@ namespace Emby.Dlna.Didl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
|
// For audio tracks without art use album art if available.
|
||||||
|
if (item is Audio audioItem)
|
||||||
if (item != null)
|
|
||||||
{
|
{
|
||||||
if (item.HasImage(ImageType.Primary))
|
var album = audioItem.AlbumEntity;
|
||||||
{
|
return album != null && album.HasImage(ImageType.Primary)
|
||||||
return GetImageInfo(item, ImageType.Primary);
|
? GetImageInfo(album, ImageType.Primary)
|
||||||
}
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
|
||||||
|
if (item is MusicAlbum || item is Playlist)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item.
|
||||||
|
var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
|
||||||
|
if (parentWithImage != null)
|
||||||
|
{
|
||||||
|
return GetImageInfo(parentWithImage, ImageType.Primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.HasImage(ImageType.Primary))
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = item.GetParent();
|
||||||
|
if (parent is UserRootFolder)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminate in case we went past user root folder (unlikely?)
|
||||||
|
if (parent is Folder folder && folder.IsRoot)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetFirstParentWithImageBelowUserRoot(parent);
|
||||||
|
}
|
||||||
|
|
||||||
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
|
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
|
||||||
{
|
{
|
||||||
var imageInfo = item.GetImageInfo(type, 0);
|
var imageInfo = item.GetImageInfo(type, 0);
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -8,7 +8,6 @@ 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;
|
||||||
@ -33,8 +32,7 @@ namespace Emby.Drawing
|
|||||||
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 +43,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;
|
||||||
}
|
}
|
||||||
@ -121,26 +116,9 @@ namespace Emby.Drawing
|
|||||||
/// <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;
|
||||||
@ -312,10 +290,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;
|
||||||
@ -332,11 +306,6 @@ namespace Emby.Drawing
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,19 +320,12 @@ 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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +346,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
|
||||||
|
@ -136,7 +136,8 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
CleanDateTimes = new[]
|
CleanDateTimes = new[]
|
||||||
{
|
{
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*",
|
||||||
|
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
||||||
};
|
};
|
||||||
|
|
||||||
CleanStrings = new[]
|
CleanStrings = new[]
|
||||||
@ -505,7 +506,63 @@ namespace Emby.Naming.Common
|
|||||||
RuleType = ExtraRuleType.Suffix,
|
RuleType = ExtraRuleType.Suffix,
|
||||||
Token = "-short",
|
Token = "-short",
|
||||||
MediaType = MediaType.Video
|
MediaType = MediaType.Video
|
||||||
}
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.BehindTheScenes,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "behind the scenes",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.DeletedScene,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "deleted scenes",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Interview,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "interviews",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Scene,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "scenes",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Sample,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "samples",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Clip,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "shorts",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Clip,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "featurettes",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Unknown,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "extras",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Format3DRules = new[]
|
Format3DRules = new[]
|
||||||
|
@ -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>
|
||||||
|
@ -80,6 +80,15 @@ namespace Emby.Naming.Video
|
|||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (rule.RuleType == ExtraRuleType.DirectoryName)
|
||||||
|
{
|
||||||
|
var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
|
||||||
|
if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.ExtraType = rule.ExtraType;
|
||||||
|
result.Rule = rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -5,30 +5,29 @@ using MediaType = Emby.Naming.Common.MediaType;
|
|||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A rule used to match a file path with an <see cref="MediaBrowser.Model.Entities.ExtraType"/>.
|
||||||
|
/// </summary>
|
||||||
public class ExtraRule
|
public class ExtraRule
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the token.
|
/// Gets or sets the token to use for matching against the file path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The token.</value>
|
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the extra.
|
/// Gets or sets the type of the extra to return when matched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the extra.</value>
|
|
||||||
public ExtraType ExtraType { get; set; }
|
public ExtraType ExtraType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the rule.
|
/// Gets or sets the type of the rule.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the rule.</value>
|
|
||||||
public ExtraRuleType RuleType { get; set; }
|
public ExtraRuleType RuleType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the media.
|
/// Gets or sets the type of the media to return when matched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the media.</value>
|
|
||||||
public MediaType MediaType { get; set; }
|
public MediaType MediaType { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,18 +5,23 @@ namespace Emby.Naming.Video
|
|||||||
public enum ExtraRuleType
|
public enum ExtraRuleType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The suffix
|
/// Match <see cref="ExtraRule.Token"/> against a suffix in the file name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Suffix = 0,
|
Suffix = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The filename
|
/// Match <see cref="ExtraRule.Token"/> against the file name, excluding the file extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Filename = 1,
|
Filename = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The regex
|
/// Match <see cref="ExtraRule.Token"/> against the file name, including the file extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Regex = 2
|
Regex = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Match <see cref="ExtraRule.Token"/> against the name of the directory containing the file.
|
||||||
|
/// </summary>
|
||||||
|
DirectoryName = 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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" />
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
@ -27,6 +25,9 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Entry point for the activity logger.
|
||||||
|
/// </summary>
|
||||||
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
@ -42,16 +43,15 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
/// <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"></param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="sessionManager"></param>
|
/// <param name="sessionManager">The session manager.</param>
|
||||||
/// <param name="deviceManager"></param>
|
/// <param name="deviceManager">The device manager.</param>
|
||||||
/// <param name="taskManager"></param>
|
/// <param name="taskManager">The task manager.</param>
|
||||||
/// <param name="activityManager"></param>
|
/// <param name="activityManager">The activity manager.</param>
|
||||||
/// <param name="localization"></param>
|
/// <param name="localization">The localization manager.</param>
|
||||||
/// <param name="installationManager"></param>
|
/// <param name="installationManager">The installation manager.</param>
|
||||||
/// <param name="subManager"></param>
|
/// <param name="subManager">The subtitle manager.</param>
|
||||||
/// <param name="userManager"></param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
/// <param name="appHost"></param>
|
|
||||||
public ActivityLogEntryPoint(
|
public ActivityLogEntryPoint(
|
||||||
ILogger<ActivityLogEntryPoint> logger,
|
ILogger<ActivityLogEntryPoint> logger,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
@ -74,6 +74,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Task RunAsync()
|
public Task RunAsync()
|
||||||
{
|
{
|
||||||
_taskManager.TaskCompleted += OnTaskCompleted;
|
_taskManager.TaskCompleted += OnTaskCompleted;
|
||||||
@ -136,7 +137,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
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",
|
Type = "SubtitleDownloadFailure",
|
||||||
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||||
ShortOverview = e.Exception.Message
|
ShortOverview = e.Exception.Message
|
||||||
@ -168,7 +169,12 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
|
Name = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
||||||
|
user.Name,
|
||||||
|
GetItemName(item),
|
||||||
|
e.DeviceName),
|
||||||
Type = GetPlaybackStoppedNotificationType(item.MediaType),
|
Type = GetPlaybackStoppedNotificationType(item.MediaType),
|
||||||
UserId = user.Id
|
UserId = user.Id
|
||||||
});
|
});
|
||||||
@ -259,31 +265,20 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
private void OnSessionEnded(object sender, SessionEventArgs e)
|
private 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
|
|
||||||
{
|
|
||||||
name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserOfflineFromDevice"),
|
|
||||||
session.UserName,
|
|
||||||
session.DeviceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
_localization.GetLocalizedString("UserOfflineFromDevice"),
|
||||||
|
session.UserName,
|
||||||
|
session.DeviceName),
|
||||||
Type = "SessionEnded",
|
Type = "SessionEnded",
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
@ -382,31 +377,20 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
private void OnSessionStarted(object sender, SessionEventArgs e)
|
private 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
|
|
||||||
{
|
|
||||||
name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserOnlineFromDevice"),
|
|
||||||
session.UserName,
|
|
||||||
session.DeviceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
_localization.GetLocalizedString("UserOnlineFromDevice"),
|
||||||
|
session.UserName,
|
||||||
|
session.DeviceName),
|
||||||
Type = "SessionStarted",
|
Type = "SessionStarted",
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
@ -416,7 +400,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
|
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
@ -428,8 +412,8 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
e.Argument.Item2.versionStr),
|
e.Argument.Item2.version),
|
||||||
Overview = e.Argument.Item2.description
|
Overview = e.Argument.Item2.changelog
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,7 +429,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
@ -457,7 +441,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
e.Argument.versionStr)
|
e.Argument.version)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,8 +469,8 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
var result = e.Result;
|
var result = e.Result;
|
||||||
var task = e.Task;
|
var task = e.Task;
|
||||||
|
|
||||||
var activityTask = task.ScheduledTask as IConfigurableScheduledTask;
|
if (task.ScheduledTask is IConfigurableScheduledTask activityTask
|
||||||
if (activityTask != null && !activityTask.IsLogged)
|
&& !activityTask.IsLogged)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -560,7 +544,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a user-friendly string for this TimeSpan instance.
|
/// Constructs a user-friendly string for this TimeSpan instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ToUserFriendlyString(TimeSpan span)
|
private static string ToUserFriendlyString(TimeSpan span)
|
||||||
{
|
{
|
||||||
const int DaysInYear = 365;
|
const int DaysInYear = 365;
|
||||||
const int DaysInMonth = 30;
|
const int DaysInMonth = 30;
|
||||||
@ -574,7 +558,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
|
||||||
@ -582,7 +566,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
{
|
{
|
||||||
int months = days / DaysInMonth;
|
int months = days / DaysInMonth;
|
||||||
values.Add(CreateValueString(months, "month"));
|
values.Add(CreateValueString(months, "month"));
|
||||||
days = days % DaysInMonth;
|
days %= DaysInMonth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number of days
|
// Number of days
|
||||||
|
@ -1,32 +1,33 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The activity log manager.
|
||||||
|
/// </summary>
|
||||||
public class ActivityManager : IActivityManager
|
public class ActivityManager : IActivityManager
|
||||||
{
|
{
|
||||||
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
|
|
||||||
|
|
||||||
private readonly IActivityRepository _repo;
|
private readonly IActivityRepository _repo;
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
|
||||||
public ActivityManager(
|
/// <summary>
|
||||||
ILoggerFactory loggerFactory,
|
/// Initializes a new instance of the <see cref="ActivityManager"/> class.
|
||||||
IActivityRepository repo,
|
/// </summary>
|
||||||
IUserManager userManager)
|
/// <param name="repo">The activity repository.</param>
|
||||||
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
public ActivityManager(IActivityRepository repo, IUserManager userManager)
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger(nameof(ActivityManager));
|
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
public void Create(ActivityLogEntry entry)
|
||||||
{
|
{
|
||||||
entry.Date = DateTime.UtcNow;
|
entry.Date = DateTime.UtcNow;
|
||||||
@ -36,6 +37,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
|
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
||||||
{
|
{
|
||||||
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
|
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
|
||||||
@ -59,6 +61,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
|
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
|
||||||
{
|
{
|
||||||
return GetActivityLogEntries(minDate, null, startIndex, limit);
|
return GetActivityLogEntries(minDate, null, startIndex, limit);
|
||||||
|
@ -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;
|
||||||
@ -15,18 +13,31 @@ using SQLitePCL.pretty;
|
|||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The activity log repository.
|
||||||
|
/// </summary>
|
||||||
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
|
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
|
||||||
{
|
{
|
||||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
/// <summary>
|
||||||
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
|
/// Initializes a new instance of the <see cref="ActivityRepository"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="appPaths">The server application paths.</param>
|
||||||
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
|
public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
||||||
|
: base(logger)
|
||||||
{
|
{
|
||||||
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
|
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the <see cref="ActivityRepository"/>.
|
||||||
|
/// </summary>
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -45,16 +56,14 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
private void InitializeInternal()
|
private void InitializeInternal()
|
||||||
{
|
{
|
||||||
using (var connection = GetConnection())
|
using var connection = GetConnection();
|
||||||
|
connection.RunQueries(new[]
|
||||||
{
|
{
|
||||||
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"
|
||||||
"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);
|
TryMigrate(connection);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryMigrate(ManagedConnection connection)
|
private void TryMigrate(ManagedConnection connection)
|
||||||
@ -76,8 +85,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
|
/// <inheritdoc />
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
public void Create(ActivityLogEntry entry)
|
||||||
{
|
{
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
@ -85,37 +93,38 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
throw new ArgumentNullException(nameof(entry));
|
throw new ArgumentNullException(nameof(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
using var connection = GetConnection();
|
||||||
|
connection.RunInTransaction(db =>
|
||||||
{
|
{
|
||||||
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))
|
||||||
{
|
{
|
||||||
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.TryBindNull("@UserId");
|
||||||
{
|
}
|
||||||
statement.TryBind("@Name", entry.Name);
|
else
|
||||||
|
{
|
||||||
|
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
statement.TryBind("@Overview", entry.Overview);
|
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
statement.MoveNext();
|
||||||
{
|
}, TransactionMode);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the provided <see cref="ActivityLogEntry"/> to this repository.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entry">The activity log entry.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">If entry is null.</exception>
|
||||||
public void Update(ActivityLogEntry entry)
|
public void Update(ActivityLogEntry entry)
|
||||||
{
|
{
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
@ -123,38 +132,35 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
throw new ArgumentNullException(nameof(entry));
|
throw new ArgumentNullException(nameof(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
using var connection = GetConnection();
|
||||||
|
connection.RunInTransaction(db =>
|
||||||
{
|
{
|
||||||
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))
|
||||||
{
|
{
|
||||||
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.TryBindNull("@UserId");
|
||||||
{
|
}
|
||||||
statement.TryBind("@Id", entry.Id);
|
else
|
||||||
|
{
|
||||||
|
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
statement.TryBind("@Name", entry.Name);
|
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||||
statement.TryBind("@Overview", entry.Overview);
|
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
statement.MoveNext();
|
||||||
{
|
}, TransactionMode);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
||||||
{
|
{
|
||||||
var commandText = BaseActivitySelectText;
|
var commandText = BaseActivitySelectText;
|
||||||
@ -164,16 +170,10 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
{
|
{
|
||||||
whereClauses.Add("DateCreated>=@DateCreated");
|
whereClauses.Add("DateCreated>=@DateCreated");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserId.HasValue)
|
if (hasUserId.HasValue)
|
||||||
{
|
{
|
||||||
if (hasUserId.Value)
|
whereClauses.Add(hasUserId.Value ? "UserId not null" : "UserId is null");
|
||||||
{
|
|
||||||
whereClauses.Add("UserId not null");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
whereClauses.Add("UserId is null");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var whereTextWithoutPaging = whereClauses.Count == 0 ?
|
var whereTextWithoutPaging = whereClauses.Count == 0 ?
|
||||||
@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
if (limit.HasValue)
|
if (limit.HasValue)
|
||||||
{
|
{
|
||||||
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
|
commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
var statementTexts = new[]
|
var statementTexts = new[]
|
||||||
@ -216,38 +216,33 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
var list = new List<ActivityLogEntry>();
|
var list = new List<ActivityLogEntry>();
|
||||||
var result = new QueryResult<ActivityLogEntry>();
|
var result = new QueryResult<ActivityLogEntry>();
|
||||||
|
|
||||||
using (var connection = GetConnection(true))
|
using var connection = GetConnection(true);
|
||||||
{
|
connection.RunInTransaction(
|
||||||
connection.RunInTransaction(
|
db =>
|
||||||
db =>
|
{
|
||||||
|
var statements = PrepareAll(db, statementTexts).ToList();
|
||||||
|
|
||||||
|
using (var statement = statements[0])
|
||||||
{
|
{
|
||||||
var statements = PrepareAll(db, statementTexts).ToList();
|
if (minDate.HasValue)
|
||||||
|
|
||||||
using (var statement = statements[0])
|
|
||||||
{
|
{
|
||||||
if (minDate.HasValue)
|
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
||||||
{
|
|
||||||
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
|
||||||
{
|
|
||||||
list.Add(GetEntry(row));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var statement = statements[1])
|
list.AddRange(statement.ExecuteQuery().Select(GetEntry));
|
||||||
{
|
}
|
||||||
if (minDate.HasValue)
|
|
||||||
{
|
|
||||||
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
|
using (var statement = statements[1])
|
||||||
|
{
|
||||||
|
if (minDate.HasValue)
|
||||||
|
{
|
||||||
|
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
||||||
}
|
}
|
||||||
},
|
|
||||||
ReadTransactionMode);
|
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
ReadTransactionMode);
|
||||||
|
|
||||||
result.Items = list;
|
result.Items = list;
|
||||||
return result;
|
return result;
|
||||||
@ -304,7 +299,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
index++;
|
index++;
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||||
{
|
{
|
||||||
info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
|
info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ using Emby.Server.Implementations.Configuration;
|
|||||||
using Emby.Server.Implementations.Cryptography;
|
using Emby.Server.Implementations.Cryptography;
|
||||||
using Emby.Server.Implementations.Data;
|
using Emby.Server.Implementations.Data;
|
||||||
using Emby.Server.Implementations.Devices;
|
using Emby.Server.Implementations.Devices;
|
||||||
using Emby.Server.Implementations.Diagnostics;
|
|
||||||
using Emby.Server.Implementations.Dto;
|
using Emby.Server.Implementations.Dto;
|
||||||
using Emby.Server.Implementations.HttpServer;
|
using Emby.Server.Implementations.HttpServer;
|
||||||
using Emby.Server.Implementations.HttpServer.Security;
|
using Emby.Server.Implementations.HttpServer.Security;
|
||||||
@ -86,9 +85,7 @@ using MediaBrowser.MediaEncoding.BdInfo;
|
|||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
@ -106,7 +103,6 @@ using MediaBrowser.WebDashboard.Api;
|
|||||||
using MediaBrowser.XbmcMetadata.Providers;
|
using MediaBrowser.XbmcMetadata.Providers;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||||
@ -123,14 +119,20 @@ namespace Emby.Server.Implementations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
|
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
|
||||||
|
|
||||||
private SqliteUserRepository _userRepository;
|
private readonly IFileSystem _fileSystemManager;
|
||||||
private SqliteDisplayPreferencesRepository _displayPreferencesRepository;
|
private readonly INetworkManager _networkManager;
|
||||||
|
private readonly IXmlSerializer _xmlSerializer;
|
||||||
|
private readonly IStartupOptions _startupOptions;
|
||||||
|
|
||||||
|
private IMediaEncoder _mediaEncoder;
|
||||||
|
private ISessionManager _sessionManager;
|
||||||
|
private IHttpServer _httpServer;
|
||||||
|
private IHttpClient _httpClient;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance can self restart.
|
/// Gets a value indicating whether this instance can self restart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
|
public bool CanSelfRestart => _startupOptions.RestartPath != null;
|
||||||
public abstract bool CanSelfRestart { get; }
|
|
||||||
|
|
||||||
public virtual bool CanLaunchWebBrowser
|
public virtual bool CanLaunchWebBrowser
|
||||||
{
|
{
|
||||||
@ -141,7 +143,7 @@ namespace Emby.Server.Implementations
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StartupOptions.IsService)
|
if (_startupOptions.IsService)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -211,21 +213,6 @@ namespace Emby.Server.Implementations
|
|||||||
/// <value>The configuration manager.</value>
|
/// <value>The configuration manager.</value>
|
||||||
protected IConfigurationManager ConfigurationManager { get; set; }
|
protected IConfigurationManager ConfigurationManager { get; set; }
|
||||||
|
|
||||||
public IFileSystem FileSystemManager { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public PackageVersionClass SystemUpdateLevel
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
#if BETA
|
|
||||||
return PackageVersionClass.Beta;
|
|
||||||
#else
|
|
||||||
return PackageVersionClass.Release;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the service provider.
|
/// Gets or sets the service provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -247,112 +234,6 @@ namespace Emby.Server.Implementations
|
|||||||
/// <value>The server configuration manager.</value>
|
/// <value>The server configuration manager.</value>
|
||||||
public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager;
|
public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the user manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The user manager.</value>
|
|
||||||
public IUserManager UserManager { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the library manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The library manager.</value>
|
|
||||||
internal ILibraryManager LibraryManager { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the directory watchers.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The directory watchers.</value>
|
|
||||||
private ILibraryMonitor LibraryMonitor { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the provider manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The provider manager.</value>
|
|
||||||
private IProviderManager ProviderManager { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the HTTP server.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The HTTP server.</value>
|
|
||||||
private IHttpServer HttpServer { get; set; }
|
|
||||||
|
|
||||||
private IDtoService DtoService { get; set; }
|
|
||||||
|
|
||||||
public IImageProcessor ImageProcessor { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the media encoder.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The media encoder.</value>
|
|
||||||
private IMediaEncoder MediaEncoder { get; set; }
|
|
||||||
|
|
||||||
private ISubtitleEncoder SubtitleEncoder { get; set; }
|
|
||||||
|
|
||||||
private ISessionManager SessionManager { get; set; }
|
|
||||||
|
|
||||||
private ILiveTvManager LiveTvManager { get; set; }
|
|
||||||
|
|
||||||
public LocalizationManager LocalizationManager { get; set; }
|
|
||||||
|
|
||||||
private IEncodingManager EncodingManager { get; set; }
|
|
||||||
|
|
||||||
private IChannelManager ChannelManager { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the user data repository.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The user data repository.</value>
|
|
||||||
private IUserDataManager UserDataManager { get; set; }
|
|
||||||
|
|
||||||
internal SqliteItemRepository ItemRepository { get; set; }
|
|
||||||
|
|
||||||
private INotificationManager NotificationManager { get; set; }
|
|
||||||
|
|
||||||
private ISubtitleManager SubtitleManager { get; set; }
|
|
||||||
|
|
||||||
private IChapterManager ChapterManager { get; set; }
|
|
||||||
|
|
||||||
private IDeviceManager DeviceManager { get; set; }
|
|
||||||
|
|
||||||
internal IUserViewManager UserViewManager { get; set; }
|
|
||||||
|
|
||||||
private IAuthenticationRepository AuthenticationRepository { get; set; }
|
|
||||||
|
|
||||||
private ITVSeriesManager TVSeriesManager { get; set; }
|
|
||||||
|
|
||||||
private ICollectionManager CollectionManager { get; set; }
|
|
||||||
|
|
||||||
private IMediaSourceManager MediaSourceManager { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the installation manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The installation manager.</value>
|
|
||||||
protected IInstallationManager InstallationManager { get; private set; }
|
|
||||||
|
|
||||||
protected IAuthService AuthService { get; private set; }
|
|
||||||
|
|
||||||
public IStartupOptions StartupOptions { get; }
|
|
||||||
|
|
||||||
internal IImageEncoder ImageEncoder { get; private set; }
|
|
||||||
|
|
||||||
protected IProcessFactory ProcessFactory { get; private set; }
|
|
||||||
|
|
||||||
protected readonly IXmlSerializer XmlSerializer;
|
|
||||||
|
|
||||||
protected ISocketFactory SocketFactory { get; private set; }
|
|
||||||
|
|
||||||
protected ITaskManager TaskManager { get; private set; }
|
|
||||||
|
|
||||||
public IHttpClient HttpClient { get; private set; }
|
|
||||||
|
|
||||||
protected INetworkManager NetworkManager { get; set; }
|
|
||||||
|
|
||||||
public IJsonSerializer JsonSerializer { get; private set; }
|
|
||||||
|
|
||||||
protected IIsoManager IsoManager { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -361,29 +242,33 @@ namespace Emby.Server.Implementations
|
|||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStartupOptions options,
|
IStartupOptions options,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IImageEncoder imageEncoder,
|
|
||||||
INetworkManager networkManager)
|
INetworkManager networkManager)
|
||||||
{
|
{
|
||||||
XmlSerializer = new MyXmlSerializer();
|
_xmlSerializer = new MyXmlSerializer();
|
||||||
|
|
||||||
NetworkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
|
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
|
||||||
|
|
||||||
ApplicationPaths = applicationPaths;
|
ApplicationPaths = applicationPaths;
|
||||||
LoggerFactory = loggerFactory;
|
LoggerFactory = loggerFactory;
|
||||||
FileSystemManager = fileSystem;
|
_fileSystemManager = fileSystem;
|
||||||
|
|
||||||
ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager);
|
ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
|
||||||
|
|
||||||
Logger = LoggerFactory.CreateLogger("App");
|
Logger = LoggerFactory.CreateLogger<ApplicationHost>();
|
||||||
|
|
||||||
StartupOptions = options;
|
_startupOptions = options;
|
||||||
|
|
||||||
ImageEncoder = imageEncoder;
|
|
||||||
|
|
||||||
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
||||||
|
|
||||||
NetworkManager.NetworkChanged += OnNetworkChanged;
|
_networkManager.NetworkChanged += OnNetworkChanged;
|
||||||
|
|
||||||
|
CertificateInfo = new CertificateInfo
|
||||||
|
{
|
||||||
|
Path = ServerConfigurationManager.Configuration.CertificatePath,
|
||||||
|
Password = ServerConfigurationManager.Configuration.CertificatePassword
|
||||||
|
};
|
||||||
|
Certificate = GetCertificate(CertificateInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ExpandVirtualPath(string path)
|
public string ExpandVirtualPath(string path)
|
||||||
@ -451,10 +336,7 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name => ApplicationProductName;
|
public string Name => ApplicationProductName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -544,7 +426,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
|
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
|
||||||
|
|
||||||
MediaEncoder.SetFFmpegPath();
|
_mediaEncoder.SetFFmpegPath();
|
||||||
|
|
||||||
Logger.LogInformation("ServerId: {0}", SystemId);
|
Logger.LogInformation("ServerId: {0}", SystemId);
|
||||||
|
|
||||||
@ -556,7 +438,7 @@ namespace Emby.Server.Implementations
|
|||||||
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
|
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
|
||||||
|
|
||||||
Logger.LogInformation("Core startup complete");
|
Logger.LogInformation("Core startup complete");
|
||||||
HttpServer.GlobalResponse = null;
|
_httpServer.GlobalResponse = null;
|
||||||
|
|
||||||
stopWatch.Restart();
|
stopWatch.Restart();
|
||||||
await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
|
await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
|
||||||
@ -580,7 +462,7 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task InitAsync(IServiceCollection serviceCollection, IConfiguration startupConfig)
|
public void Init(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
||||||
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
||||||
@ -592,8 +474,6 @@ namespace Emby.Server.Implementations
|
|||||||
HttpsPort = ServerConfiguration.DefaultHttpsPort;
|
HttpsPort = ServerConfiguration.DefaultHttpsPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonSerializer = new JsonSerializer();
|
|
||||||
|
|
||||||
if (Plugins != null)
|
if (Plugins != null)
|
||||||
{
|
{
|
||||||
var pluginBuilder = new StringBuilder();
|
var pluginBuilder = new StringBuilder();
|
||||||
@ -613,7 +493,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
DiscoverTypes();
|
DiscoverTypes();
|
||||||
|
|
||||||
await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false);
|
RegisterServices(serviceCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
|
public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
|
||||||
@ -624,7 +504,7 @@ namespace Emby.Server.Implementations
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
|
await _httpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
|
public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
|
||||||
@ -640,14 +520,16 @@ namespace Emby.Server.Implementations
|
|||||||
var localPath = context.Request.Path.ToString();
|
var localPath = context.Request.Path.ToString();
|
||||||
|
|
||||||
var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger<WebSocketSharpRequest>());
|
var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger<WebSocketSharpRequest>());
|
||||||
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
|
await _httpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers services/resources with the service collection that will be available via DI.
|
/// Registers services/resources with the service collection that will be available via DI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected async Task RegisterServices(IServiceCollection serviceCollection, IConfiguration startupConfig)
|
protected virtual void RegisterServices(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
|
serviceCollection.AddSingleton(_startupOptions);
|
||||||
|
|
||||||
serviceCollection.AddMemoryCache();
|
serviceCollection.AddMemoryCache();
|
||||||
|
|
||||||
serviceCollection.AddSingleton(ConfigurationManager);
|
serviceCollection.AddSingleton(ConfigurationManager);
|
||||||
@ -655,236 +537,169 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||||
|
|
||||||
serviceCollection.AddSingleton(JsonSerializer);
|
serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
|
||||||
|
|
||||||
// TODO: Support for injecting ILogger should be deprecated in favour of ILogger<T> and this removed
|
// TODO: Remove support for injecting ILogger completely
|
||||||
serviceCollection.AddSingleton<ILogger>(Logger);
|
serviceCollection.AddSingleton((provider) =>
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Injecting ILogger directly is deprecated and should be replaced with ILogger<T>");
|
||||||
|
return Logger;
|
||||||
|
});
|
||||||
|
|
||||||
serviceCollection.AddSingleton(FileSystemManager);
|
serviceCollection.AddSingleton(_fileSystemManager);
|
||||||
serviceCollection.AddSingleton<TvdbClientManager>();
|
serviceCollection.AddSingleton<TvdbClientManager>();
|
||||||
|
|
||||||
HttpClient = new HttpClientManager.HttpClientManager(
|
serviceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>();
|
||||||
ApplicationPaths,
|
|
||||||
LoggerFactory.CreateLogger<HttpClientManager.HttpClientManager>(),
|
|
||||||
FileSystemManager,
|
|
||||||
() => ApplicationUserAgent);
|
|
||||||
serviceCollection.AddSingleton(HttpClient);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(NetworkManager);
|
serviceCollection.AddSingleton(_networkManager);
|
||||||
|
|
||||||
IsoManager = new IsoManager();
|
serviceCollection.AddSingleton<IIsoManager, IsoManager>();
|
||||||
serviceCollection.AddSingleton(IsoManager);
|
|
||||||
|
|
||||||
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
|
serviceCollection.AddSingleton<ITaskManager, TaskManager>();
|
||||||
serviceCollection.AddSingleton(TaskManager);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(XmlSerializer);
|
serviceCollection.AddSingleton(_xmlSerializer);
|
||||||
|
|
||||||
ProcessFactory = new ProcessFactory();
|
serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
|
||||||
serviceCollection.AddSingleton(ProcessFactory);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
|
||||||
|
|
||||||
var cryptoProvider = new CryptographyProvider();
|
serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
|
||||||
serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider);
|
|
||||||
|
|
||||||
SocketFactory = new SocketFactory();
|
serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
||||||
serviceCollection.AddSingleton(SocketFactory);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
|
serviceCollection.AddSingleton<IZipClient, ZipClient>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IZipClient), typeof(ZipClient));
|
serviceCollection.AddSingleton<IHttpResultFactory, HttpResultFactory>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IHttpResultFactory), typeof(HttpResultFactory));
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||||
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
||||||
|
|
||||||
serviceCollection.AddSingleton(ServerConfigurationManager);
|
serviceCollection.AddSingleton(ServerConfigurationManager);
|
||||||
|
|
||||||
LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory.CreateLogger<LocalizationManager>());
|
serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
|
||||||
await LocalizationManager.LoadAll().ConfigureAwait(false);
|
|
||||||
serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
|
serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
|
||||||
|
|
||||||
UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
|
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
||||||
serviceCollection.AddSingleton(UserDataManager);
|
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
||||||
|
|
||||||
_displayPreferencesRepository = new SqliteDisplayPreferencesRepository(
|
serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>();
|
||||||
LoggerFactory.CreateLogger<SqliteDisplayPreferencesRepository>(),
|
|
||||||
ApplicationPaths,
|
|
||||||
FileSystemManager);
|
|
||||||
serviceCollection.AddSingleton<IDisplayPreferencesRepository>(_displayPreferencesRepository);
|
|
||||||
|
|
||||||
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, LoggerFactory.CreateLogger<SqliteItemRepository>(), LocalizationManager);
|
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
||||||
serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
|
|
||||||
|
|
||||||
AuthenticationRepository = GetAuthenticationRepository();
|
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
|
||||||
serviceCollection.AddSingleton(AuthenticationRepository);
|
|
||||||
|
|
||||||
_userRepository = GetUserRepository();
|
serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>();
|
||||||
|
|
||||||
UserManager = new UserManager(
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
LoggerFactory.CreateLogger<UserManager>(),
|
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
|
||||||
_userRepository,
|
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
||||||
XmlSerializer,
|
|
||||||
NetworkManager,
|
|
||||||
() => ImageProcessor,
|
|
||||||
() => DtoService,
|
|
||||||
this,
|
|
||||||
JsonSerializer,
|
|
||||||
FileSystemManager,
|
|
||||||
cryptoProvider);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(UserManager);
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
|
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
|
||||||
|
serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
|
||||||
|
serviceCollection.AddSingleton<IMediaEncoder>(provider =>
|
||||||
|
ActivatorUtilities.CreateInstance<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(provider, _startupOptions.FFmpegPath ?? string.Empty));
|
||||||
|
|
||||||
MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
|
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
|
||||||
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
|
serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
||||||
ServerConfigurationManager,
|
serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
||||||
FileSystemManager,
|
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
||||||
ProcessFactory,
|
serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
||||||
LocalizationManager,
|
|
||||||
() => SubtitleEncoder,
|
|
||||||
startupConfig,
|
|
||||||
StartupOptions.FFmpegPath);
|
|
||||||
serviceCollection.AddSingleton(MediaEncoder);
|
|
||||||
|
|
||||||
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager, MediaEncoder);
|
serviceCollection.AddSingleton<IMusicManager, MusicManager>();
|
||||||
serviceCollection.AddSingleton(LibraryManager);
|
|
||||||
|
|
||||||
var musicManager = new MusicManager(LibraryManager);
|
serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
|
||||||
serviceCollection.AddSingleton<IMusicManager>(musicManager);
|
|
||||||
|
|
||||||
LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager);
|
serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
|
||||||
serviceCollection.AddSingleton(LibraryMonitor);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
|
|
||||||
|
|
||||||
CertificateInfo = GetCertificateInfo(true);
|
|
||||||
Certificate = GetCertificate(CertificateInfo);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ServiceController>();
|
serviceCollection.AddSingleton<ServiceController>();
|
||||||
serviceCollection.AddSingleton<IHttpListener, WebSocketSharpListener>();
|
serviceCollection.AddSingleton<IHttpListener, WebSocketSharpListener>();
|
||||||
serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
|
serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
|
||||||
|
|
||||||
ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger<ImageProcessor>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
|
serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
|
||||||
serviceCollection.AddSingleton(ImageProcessor);
|
|
||||||
|
|
||||||
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
|
serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
|
||||||
serviceCollection.AddSingleton(TVSeriesManager);
|
|
||||||
|
|
||||||
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager);
|
serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
||||||
serviceCollection.AddSingleton(DeviceManager);
|
|
||||||
|
|
||||||
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder);
|
serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
|
||||||
serviceCollection.AddSingleton(MediaSourceManager);
|
|
||||||
|
|
||||||
SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager);
|
serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
|
||||||
serviceCollection.AddSingleton(SubtitleManager);
|
|
||||||
|
|
||||||
ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
|
serviceCollection.AddSingleton<IProviderManager, ProviderManager>();
|
||||||
serviceCollection.AddSingleton(ProviderManager);
|
|
||||||
|
|
||||||
DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
serviceCollection.AddSingleton(DtoService);
|
serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
|
||||||
|
serviceCollection.AddSingleton<IDtoService, DtoService>();
|
||||||
|
|
||||||
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager);
|
serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
|
||||||
serviceCollection.AddSingleton(ChannelManager);
|
|
||||||
|
|
||||||
SessionManager = new SessionManager(
|
serviceCollection.AddSingleton<ISessionManager, SessionManager>();
|
||||||
LoggerFactory.CreateLogger<SessionManager>(),
|
|
||||||
UserDataManager,
|
|
||||||
LibraryManager,
|
|
||||||
UserManager,
|
|
||||||
musicManager,
|
|
||||||
DtoService,
|
|
||||||
ImageProcessor,
|
|
||||||
this,
|
|
||||||
AuthenticationRepository,
|
|
||||||
DeviceManager,
|
|
||||||
MediaSourceManager);
|
|
||||||
serviceCollection.AddSingleton(SessionManager);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDlnaManager>(
|
serviceCollection.AddSingleton<IDlnaManager, DlnaManager>();
|
||||||
new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this));
|
|
||||||
|
|
||||||
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
|
serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
|
||||||
serviceCollection.AddSingleton(CollectionManager);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager));
|
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
||||||
|
|
||||||
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
|
serviceCollection.AddSingleton<LiveTvDtoService>();
|
||||||
serviceCollection.AddSingleton(LiveTvManager);
|
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||||
|
|
||||||
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
|
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
||||||
serviceCollection.AddSingleton(UserViewManager);
|
|
||||||
|
|
||||||
NotificationManager = new NotificationManager(
|
serviceCollection.AddSingleton<INotificationManager, NotificationManager>();
|
||||||
LoggerFactory.CreateLogger<NotificationManager>(),
|
|
||||||
UserManager,
|
|
||||||
ServerConfigurationManager);
|
|
||||||
serviceCollection.AddSingleton(NotificationManager);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
|
serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
||||||
|
|
||||||
ChapterManager = new ChapterManager(ItemRepository);
|
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
||||||
serviceCollection.AddSingleton(ChapterManager);
|
|
||||||
|
|
||||||
EncodingManager = new MediaEncoder.EncodingManager(
|
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
||||||
LoggerFactory.CreateLogger<MediaEncoder.EncodingManager>(),
|
|
||||||
FileSystemManager,
|
|
||||||
MediaEncoder,
|
|
||||||
ChapterManager,
|
|
||||||
LibraryManager);
|
|
||||||
serviceCollection.AddSingleton(EncodingManager);
|
|
||||||
|
|
||||||
var activityLogRepo = GetActivityLogRepository();
|
serviceCollection.AddSingleton<IActivityRepository, ActivityRepository>();
|
||||||
serviceCollection.AddSingleton(activityLogRepo);
|
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
||||||
serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
|
|
||||||
|
|
||||||
var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
|
serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
|
||||||
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
|
serviceCollection.AddSingleton<ISessionContext, SessionContext>();
|
||||||
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
|
|
||||||
|
|
||||||
AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
|
serviceCollection.AddSingleton<IAuthService, AuthService>();
|
||||||
serviceCollection.AddSingleton(AuthService);
|
|
||||||
|
|
||||||
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(
|
serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
|
||||||
LibraryManager,
|
|
||||||
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(),
|
|
||||||
ApplicationPaths,
|
|
||||||
FileSystemManager,
|
|
||||||
MediaEncoder,
|
|
||||||
HttpClient,
|
|
||||||
MediaSourceManager,
|
|
||||||
ProcessFactory);
|
|
||||||
serviceCollection.AddSingleton(SubtitleEncoder);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
|
serviceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>();
|
||||||
serviceCollection.AddSingleton<EncodingHelper>();
|
serviceCollection.AddSingleton<EncodingHelper>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IAttachmentExtractor), typeof(MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor));
|
serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
|
||||||
|
|
||||||
_displayPreferencesRepository.Initialize();
|
|
||||||
|
|
||||||
var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger<SqliteUserDataRepository>(), ApplicationPaths);
|
|
||||||
|
|
||||||
SetStaticProperties();
|
|
||||||
|
|
||||||
((UserManager)UserManager).Initialize();
|
|
||||||
|
|
||||||
((UserDataManager)UserDataManager).Repository = userDataRepo;
|
|
||||||
ItemRepository.Initialize(userDataRepo, UserManager);
|
|
||||||
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create services registered with the service container that need to be initialized at application startup.
|
/// Create services registered with the service container that need to be initialized at application startup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void InitializeServices()
|
/// <returns>A task representing the service initialization operation.</returns>
|
||||||
|
public async Task InitializeServices()
|
||||||
{
|
{
|
||||||
HttpServer = Resolve<IHttpServer>();
|
var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
|
||||||
|
await localizationManager.LoadAll().ConfigureAwait(false);
|
||||||
|
|
||||||
|
_mediaEncoder = Resolve<IMediaEncoder>();
|
||||||
|
_sessionManager = Resolve<ISessionManager>();
|
||||||
|
_httpServer = Resolve<IHttpServer>();
|
||||||
|
_httpClient = Resolve<IHttpClient>();
|
||||||
|
|
||||||
|
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
|
||||||
|
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
||||||
|
((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
|
||||||
|
((ActivityRepository)Resolve<IActivityRepository>()).Initialize();
|
||||||
|
|
||||||
|
SetStaticProperties();
|
||||||
|
|
||||||
|
var userManager = (UserManager)Resolve<IUserManager>();
|
||||||
|
userManager.Initialize();
|
||||||
|
|
||||||
|
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
|
||||||
|
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager);
|
||||||
|
|
||||||
|
FindParts();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
|
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
|
||||||
@ -953,111 +768,38 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the user repository.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><see cref="Task{SqliteUserRepository}" />.</returns>
|
|
||||||
private SqliteUserRepository GetUserRepository()
|
|
||||||
{
|
|
||||||
var repo = new SqliteUserRepository(
|
|
||||||
LoggerFactory.CreateLogger<SqliteUserRepository>(),
|
|
||||||
ApplicationPaths);
|
|
||||||
|
|
||||||
repo.Initialize();
|
|
||||||
|
|
||||||
return repo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IAuthenticationRepository GetAuthenticationRepository()
|
|
||||||
{
|
|
||||||
var repo = new AuthenticationRepository(LoggerFactory, ServerConfigurationManager);
|
|
||||||
|
|
||||||
repo.Initialize();
|
|
||||||
|
|
||||||
return repo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IActivityRepository GetActivityLogRepository()
|
|
||||||
{
|
|
||||||
var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager);
|
|
||||||
|
|
||||||
repo.Initialize();
|
|
||||||
|
|
||||||
return repo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dirty hacks.
|
/// Dirty hacks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SetStaticProperties()
|
private void SetStaticProperties()
|
||||||
{
|
{
|
||||||
ItemRepository.ImageProcessor = ImageProcessor;
|
|
||||||
|
|
||||||
// For now there's no real way to inject these properly
|
// For now there's no real way to inject these properly
|
||||||
BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem");
|
BaseItem.Logger = Resolve<ILogger<BaseItem>>();
|
||||||
BaseItem.ConfigurationManager = ServerConfigurationManager;
|
BaseItem.ConfigurationManager = ServerConfigurationManager;
|
||||||
BaseItem.LibraryManager = LibraryManager;
|
BaseItem.LibraryManager = Resolve<ILibraryManager>();
|
||||||
BaseItem.ProviderManager = ProviderManager;
|
BaseItem.ProviderManager = Resolve<IProviderManager>();
|
||||||
BaseItem.LocalizationManager = LocalizationManager;
|
BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
|
||||||
BaseItem.ItemRepository = ItemRepository;
|
BaseItem.ItemRepository = Resolve<IItemRepository>();
|
||||||
User.UserManager = UserManager;
|
User.UserManager = Resolve<IUserManager>();
|
||||||
BaseItem.FileSystem = FileSystemManager;
|
BaseItem.FileSystem = _fileSystemManager;
|
||||||
BaseItem.UserDataManager = UserDataManager;
|
BaseItem.UserDataManager = Resolve<IUserDataManager>();
|
||||||
BaseItem.ChannelManager = ChannelManager;
|
BaseItem.ChannelManager = Resolve<IChannelManager>();
|
||||||
Video.LiveTvManager = LiveTvManager;
|
Video.LiveTvManager = Resolve<ILiveTvManager>();
|
||||||
Folder.UserViewManager = UserViewManager;
|
Folder.UserViewManager = Resolve<IUserViewManager>();
|
||||||
UserView.TVSeriesManager = TVSeriesManager;
|
UserView.TVSeriesManager = Resolve<ITVSeriesManager>();
|
||||||
UserView.CollectionManager = CollectionManager;
|
UserView.CollectionManager = Resolve<ICollectionManager>();
|
||||||
BaseItem.MediaSourceManager = MediaSourceManager;
|
BaseItem.MediaSourceManager = Resolve<IMediaSourceManager>();
|
||||||
CollectionFolder.XmlSerializer = XmlSerializer;
|
CollectionFolder.XmlSerializer = _xmlSerializer;
|
||||||
CollectionFolder.JsonSerializer = JsonSerializer;
|
CollectionFolder.JsonSerializer = Resolve<IJsonSerializer>();
|
||||||
CollectionFolder.ApplicationHost = this;
|
CollectionFolder.ApplicationHost = this;
|
||||||
AuthenticatedAttribute.AuthService = AuthService;
|
AuthenticatedAttribute.AuthService = Resolve<IAuthService>();
|
||||||
}
|
|
||||||
|
|
||||||
private async void PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> args)
|
|
||||||
{
|
|
||||||
string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name);
|
|
||||||
var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories)
|
|
||||||
.Select(Assembly.LoadFrom)
|
|
||||||
.SelectMany(x => x.ExportedTypes)
|
|
||||||
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
int oldLen = _allConcreteTypes.Length;
|
|
||||||
Array.Resize(ref _allConcreteTypes, oldLen + types.Length);
|
|
||||||
types.CopyTo(_allConcreteTypes, oldLen);
|
|
||||||
|
|
||||||
var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin)))
|
|
||||||
.Select(CreateInstanceSafe)
|
|
||||||
.Where(x => x != null)
|
|
||||||
.Cast<IPlugin>()
|
|
||||||
.Select(LoadPlugin)
|
|
||||||
.Where(x => x != null)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
oldLen = _plugins.Length;
|
|
||||||
Array.Resize(ref _plugins, oldLen + plugins.Length);
|
|
||||||
plugins.CopyTo(_plugins, oldLen);
|
|
||||||
|
|
||||||
var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint)))
|
|
||||||
.Select(CreateInstanceSafe)
|
|
||||||
.Where(x => x != null)
|
|
||||||
.Cast<IServerEntryPoint>()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false);
|
|
||||||
await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the parts.
|
/// Finds plugin components and register them with the appropriate services.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void FindParts()
|
private void FindParts()
|
||||||
{
|
{
|
||||||
InstallationManager = ServiceProvider.GetService<IInstallationManager>();
|
|
||||||
InstallationManager.PluginInstalled += PluginInstalled;
|
|
||||||
|
|
||||||
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
|
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
|
||||||
{
|
{
|
||||||
ServerConfigurationManager.Configuration.IsPortAuthorized = true;
|
ServerConfigurationManager.Configuration.IsPortAuthorized = true;
|
||||||
@ -1070,34 +812,34 @@ namespace Emby.Server.Implementations
|
|||||||
.Where(i => i != null)
|
.Where(i => i != null)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
HttpServer.Init(GetExportTypes<IService>(), GetExports<IWebSocketListener>(), GetUrlPrefixes());
|
_httpServer.Init(GetExportTypes<IService>(), GetExports<IWebSocketListener>(), GetUrlPrefixes());
|
||||||
|
|
||||||
LibraryManager.AddParts(
|
Resolve<ILibraryManager>().AddParts(
|
||||||
GetExports<IResolverIgnoreRule>(),
|
GetExports<IResolverIgnoreRule>(),
|
||||||
GetExports<IItemResolver>(),
|
GetExports<IItemResolver>(),
|
||||||
GetExports<IIntroProvider>(),
|
GetExports<IIntroProvider>(),
|
||||||
GetExports<IBaseItemComparer>(),
|
GetExports<IBaseItemComparer>(),
|
||||||
GetExports<ILibraryPostScanTask>());
|
GetExports<ILibraryPostScanTask>());
|
||||||
|
|
||||||
ProviderManager.AddParts(
|
Resolve<IProviderManager>().AddParts(
|
||||||
GetExports<IImageProvider>(),
|
GetExports<IImageProvider>(),
|
||||||
GetExports<IMetadataService>(),
|
GetExports<IMetadataService>(),
|
||||||
GetExports<IMetadataProvider>(),
|
GetExports<IMetadataProvider>(),
|
||||||
GetExports<IMetadataSaver>(),
|
GetExports<IMetadataSaver>(),
|
||||||
GetExports<IExternalId>());
|
GetExports<IExternalId>());
|
||||||
|
|
||||||
LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
|
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
|
||||||
|
|
||||||
SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
|
Resolve<ISubtitleManager>().AddParts(GetExports<ISubtitleProvider>());
|
||||||
|
|
||||||
ChannelManager.AddParts(GetExports<IChannel>());
|
Resolve<IChannelManager>().AddParts(GetExports<IChannel>());
|
||||||
|
|
||||||
MediaSourceManager.AddParts(GetExports<IMediaSourceProvider>());
|
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
||||||
|
|
||||||
NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
|
Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
|
||||||
UserManager.AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
|
Resolve<IUserManager>().AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
|
||||||
|
|
||||||
IsoManager.AddParts(GetExports<IIsoMounter>());
|
Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private IPlugin LoadPlugin(IPlugin plugin)
|
private IPlugin LoadPlugin(IPlugin plugin)
|
||||||
@ -1204,16 +946,6 @@ namespace Emby.Server.Implementations
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private CertificateInfo GetCertificateInfo(bool generateCertificate)
|
|
||||||
{
|
|
||||||
// Custom cert
|
|
||||||
return new CertificateInfo
|
|
||||||
{
|
|
||||||
Path = ServerConfigurationManager.Configuration.CertificatePath,
|
|
||||||
Password = ServerConfigurationManager.Configuration.CertificatePassword
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [configuration updated].
|
/// Called when [configuration updated].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1240,14 +972,13 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HttpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
|
if (!_httpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
requiresRestart = true;
|
requiresRestart = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentCertPath = CertificateInfo?.Path;
|
var currentCertPath = CertificateInfo?.Path;
|
||||||
var newCertInfo = GetCertificateInfo(false);
|
var newCertPath = ServerConfigurationManager.Configuration.CertificatePath;
|
||||||
var newCertPath = newCertInfo?.Path;
|
|
||||||
|
|
||||||
if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@ -1300,7 +1031,7 @@ namespace Emby.Server.Implementations
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await SessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false);
|
await _sessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -1404,7 +1135,7 @@ namespace Emby.Server.Implementations
|
|||||||
IsShuttingDown = IsShuttingDown,
|
IsShuttingDown = IsShuttingDown,
|
||||||
Version = ApplicationVersionString,
|
Version = ApplicationVersionString,
|
||||||
WebSocketPortNumber = HttpPort,
|
WebSocketPortNumber = HttpPort,
|
||||||
CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
|
CompletedInstallations = Resolve<IInstallationManager>().CompletedInstallations.ToArray(),
|
||||||
Id = SystemId,
|
Id = SystemId,
|
||||||
ProgramDataPath = ApplicationPaths.ProgramDataPath,
|
ProgramDataPath = ApplicationPaths.ProgramDataPath,
|
||||||
WebPath = ApplicationPaths.WebPath,
|
WebPath = ApplicationPaths.WebPath,
|
||||||
@ -1424,15 +1155,14 @@ namespace Emby.Server.Implementations
|
|||||||
ServerName = FriendlyName,
|
ServerName = FriendlyName,
|
||||||
LocalAddress = localAddress,
|
LocalAddress = localAddress,
|
||||||
SupportsLibraryMonitor = true,
|
SupportsLibraryMonitor = true,
|
||||||
EncoderLocation = MediaEncoder.EncoderLocation,
|
EncoderLocation = _mediaEncoder.EncoderLocation,
|
||||||
SystemArchitecture = RuntimeInformation.OSArchitecture,
|
SystemArchitecture = RuntimeInformation.OSArchitecture,
|
||||||
SystemUpdateLevel = SystemUpdateLevel,
|
PackageName = _startupOptions.PackageName
|
||||||
PackageName = StartupOptions.PackageName
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
|
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
|
||||||
=> NetworkManager.GetMacAddresses()
|
=> _networkManager.GetMacAddresses()
|
||||||
.Select(i => new WakeOnLanInfo(i))
|
.Select(i => new WakeOnLanInfo(i))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@ -1544,7 +1274,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
if (addresses.Count == 0)
|
if (addresses.Count == 0)
|
||||||
{
|
{
|
||||||
addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
|
addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultList = new List<IPAddress>();
|
var resultList = new List<IPAddress>();
|
||||||
@ -1611,7 +1341,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var response = await HttpClient.SendAsync(
|
using (var response = await _httpClient.SendAsync(
|
||||||
new HttpRequestOptions
|
new HttpRequestOptions
|
||||||
{
|
{
|
||||||
Url = apiUrl,
|
Url = apiUrl,
|
||||||
@ -1664,7 +1394,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await SessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false);
|
await _sessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -1714,15 +1444,17 @@ namespace Emby.Server.Implementations
|
|||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = ProcessFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
FileName = url,
|
StartInfo = new ProcessStartInfo
|
||||||
EnableRaisingEvents = true,
|
{
|
||||||
UseShellExecute = true,
|
FileName = url,
|
||||||
ErrorDialog = false
|
UseShellExecute = true,
|
||||||
});
|
ErrorDialog = false
|
||||||
|
},
|
||||||
process.Exited += ProcessExited;
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
process.Exited += (sender, args) => ((Process)sender).Dispose();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -1735,11 +1467,6 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessExited(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
((IProcess)sender).Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void EnableLoopback(string appName)
|
public virtual void EnableLoopback(string appName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -1788,14 +1515,8 @@ namespace Emby.Server.Implementations
|
|||||||
Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name);
|
Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_userRepository?.Dispose();
|
|
||||||
_displayPreferencesRepository?.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_userRepository = null;
|
|
||||||
_displayPreferencesRepository = null;
|
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,67 +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 />
|
||||||
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 />
|
||||||
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 />
|
||||||
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,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>
|
||||||
@ -122,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>
|
||||||
@ -147,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>
|
||||||
@ -161,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[]
|
||||||
|
@ -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,20 +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";
|
||||||
|
|
||||||
|
/// <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);
|
||||||
@ -32,8 +42,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
return channel.GetChannelImage(type, cancellationToken);
|
return channel.GetChannelImage(type, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Channel Image Provider";
|
/// <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;
|
||||||
@ -29,10 +27,11 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The LiveTV channel manager.
|
||||||
|
/// </summary>
|
||||||
public class ChannelManager : IChannelManager
|
public class ChannelManager : IChannelManager
|
||||||
{
|
{
|
||||||
internal IChannel[] Channels { get; private set; }
|
|
||||||
|
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
@ -43,11 +42,28 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
|
||||||
|
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
||||||
|
|
||||||
|
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="loggerFactory">The logger factory.</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,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<ChannelManager> logger,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
@ -57,7 +73,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(ChannelManager));
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
@ -65,13 +81,17 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal IChannel[] Channels { get; private set; }
|
||||||
|
|
||||||
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);
|
||||||
@ -80,15 +100,16 @@ 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);
|
||||||
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
||||||
|
|
||||||
var supportsDelete = channel as ISupportsDelete;
|
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
|
||||||
return supportsDelete != null && 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);
|
||||||
@ -97,6 +118,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);
|
||||||
@ -123,11 +145,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)
|
||||||
@ -146,15 +173,13 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes;
|
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
|
||||||
|
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
|
||||||
return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +196,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,9 +212,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsFavorite.HasValue)
|
if (query.IsFavorite.HasValue)
|
||||||
{
|
{
|
||||||
var val = query.IsFavorite.Value;
|
var val = query.IsFavorite.Value;
|
||||||
@ -215,7 +239,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +249,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
all = all.Skip(query.StartIndex.Value).ToList();
|
all = all.Skip(query.StartIndex.Value).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.Limit.HasValue)
|
if (query.Limit.HasValue)
|
||||||
{
|
{
|
||||||
all = all.Take(query.Limit.Value).ToList();
|
all = all.Take(query.Limit.Value).ToList();
|
||||||
@ -248,6 +272,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)
|
||||||
@ -256,11 +281,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var internalResult = GetChannelsInternal(query);
|
var internalResult = GetChannelsInternal(query);
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions()
|
var dtoOptions = new DtoOptions();
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
|
// TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
|
||||||
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
|
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
|
||||||
|
|
||||||
var result = new QueryResult<BaseItemDto>
|
var result = new QueryResult<BaseItemDto>
|
||||||
@ -272,6 +295,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();
|
||||||
@ -305,14 +334,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)
|
||||||
@ -341,8 +363,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,6 +373,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);
|
||||||
@ -360,16 +383,20 @@ 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);
|
||||||
var channelPlugin = GetChannelProvider(channel);
|
var channelPlugin = GetChannelProvider(channel);
|
||||||
|
|
||||||
var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
|
|
||||||
|
|
||||||
IEnumerable<MediaSourceInfo> results;
|
IEnumerable<MediaSourceInfo> results;
|
||||||
|
|
||||||
if (requiresCallback != null)
|
if (channelPlugin is IRequiresMediaInfoCallback requiresCallback)
|
||||||
{
|
{
|
||||||
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
|
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
@ -384,9 +411,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
|
|
||||||
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
|
||||||
|
|
||||||
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
|
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
|
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
|
||||||
@ -409,7 +433,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;
|
||||||
}
|
}
|
||||||
@ -444,18 +468,21 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Path = path;
|
item.Path = path;
|
||||||
|
|
||||||
if (!item.ChannelId.Equals(id))
|
if (!item.ChannelId.Equals(id))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ChannelId = id;
|
item.ChannelId = id;
|
||||||
|
|
||||||
if (item.ParentId != parentFolderId)
|
if (item.ParentId != parentFolderId)
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ParentId = parentFolderId;
|
item.ParentId = parentFolderId;
|
||||||
|
|
||||||
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
|
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
|
||||||
@ -472,51 +499,56 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
_libraryManager.CreateItem(item, null);
|
_libraryManager.CreateItem(item, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
await item.RefreshMetadata(
|
||||||
{
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
ForceSave = !isNew && forceUpdate
|
{
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
ForceSave = !isNew && forceUpdate
|
||||||
|
},
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
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(new InternalItemsQuery
|
return _libraryManager.GetItemIds(
|
||||||
{
|
new InternalItemsQuery
|
||||||
IncludeItemTypes = new[] { typeof(Channel).Name },
|
{
|
||||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
IncludeItemTypes = new[] { typeof(Channel).Name },
|
||||||
|
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
||||||
}).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))
|
||||||
@ -530,15 +562,27 @@ 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 channel = GetChannel(channelId);
|
|
||||||
var channelProvider = GetChannelProvider(channelId);
|
var channelProvider = GetChannelProvider(channelId);
|
||||||
|
|
||||||
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
|
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelFeatures GetChannelFeaturesDto(Channel channel,
|
/// <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(
|
||||||
|
Channel channel,
|
||||||
IChannel provider,
|
IChannel provider,
|
||||||
InternalChannelFeatures features)
|
InternalChannelFeatures features)
|
||||||
{
|
{
|
||||||
@ -567,9 +611,11 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(name));
|
throw new ArgumentNullException(nameof(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +634,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();
|
||||||
@ -614,7 +661,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
query.IsFolder = false;
|
query.IsFolder = false;
|
||||||
|
|
||||||
// hack for trailers, figure out a better way later
|
// hack for trailers, figure out a better way later
|
||||||
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1;
|
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.Contains("Trailer", StringComparison.Ordinal);
|
||||||
|
|
||||||
if (sortByPremiereDate)
|
if (sortByPremiereDate)
|
||||||
{
|
{
|
||||||
@ -640,10 +687,12 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
|
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var query = new InternalItemsQuery();
|
var query = new InternalItemsQuery
|
||||||
query.Parent = internalChannel;
|
{
|
||||||
query.EnableTotalRecordCount = false;
|
Parent = internalChannel,
|
||||||
query.ChannelIds = new Guid[] { internalChannel.Id };
|
EnableTotalRecordCount = false,
|
||||||
|
ChannelIds = new Guid[] { internalChannel.Id }
|
||||||
|
};
|
||||||
|
|
||||||
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@ -651,17 +700,20 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
if (item is Folder folder)
|
if (item is Folder folder)
|
||||||
{
|
{
|
||||||
await GetChannelItemsInternal(new InternalItemsQuery
|
await GetChannelItemsInternal(
|
||||||
{
|
new InternalItemsQuery
|
||||||
Parent = folder,
|
{
|
||||||
EnableTotalRecordCount = false,
|
Parent = folder,
|
||||||
ChannelIds = new Guid[] { internalChannel.Id }
|
EnableTotalRecordCount = false,
|
||||||
|
ChannelIds = new Guid[] { internalChannel.Id }
|
||||||
}, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
},
|
||||||
|
new SimpleProgress<double>(),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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
|
||||||
@ -672,7 +724,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
|
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
|
||||||
|
|
||||||
var itemsResult = await GetChannelItems(channelProvider,
|
var itemsResult = await GetChannelItems(
|
||||||
|
channelProvider,
|
||||||
query.User,
|
query.User,
|
||||||
parentItem is Channel ? null : parentItem.ExternalId,
|
parentItem is Channel ? null : parentItem.ExternalId,
|
||||||
null,
|
null,
|
||||||
@ -684,13 +737,12 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
query.Parent = channel;
|
query.Parent = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
query.ChannelIds = Array.Empty<Guid>();
|
query.ChannelIds = Array.Empty<Guid>();
|
||||||
|
|
||||||
// Not yet sure why this is causing a problem
|
// Not yet sure why this is causing a problem
|
||||||
query.GroupByPresentationUniqueKey = false;
|
query.GroupByPresentationUniqueKey = false;
|
||||||
|
|
||||||
//_logger.LogDebug("GetChannelItemsInternal");
|
|
||||||
|
|
||||||
// null if came from cache
|
// null if came from cache
|
||||||
if (itemsResult != null)
|
if (itemsResult != null)
|
||||||
{
|
{
|
||||||
@ -707,12 +759,15 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
var deadItem = _libraryManager.GetItemById(deadId);
|
var deadItem = _libraryManager.GetItemById(deadId);
|
||||||
if (deadItem != null)
|
if (deadItem != null)
|
||||||
{
|
{
|
||||||
_libraryManager.DeleteItem(deadItem, new DeleteOptions
|
_libraryManager.DeleteItem(
|
||||||
{
|
deadItem,
|
||||||
DeleteFileLocation = false,
|
new DeleteOptions
|
||||||
DeleteFromExternalProvider = false
|
{
|
||||||
|
DeleteFileLocation = false,
|
||||||
}, parentItem, false);
|
DeleteFromExternalProvider = false
|
||||||
|
},
|
||||||
|
parentItem,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -720,6 +775,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);
|
||||||
@ -735,7 +791,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
|
||||||
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
|
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
|
||||||
User user,
|
User user,
|
||||||
string externalFolderId,
|
string externalFolderId,
|
||||||
@ -743,7 +798,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
bool sortDescending,
|
bool sortDescending,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
|
var userId = user?.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
var cacheLength = CacheLength;
|
var cacheLength = CacheLength;
|
||||||
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
|
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
|
||||||
@ -761,11 +816,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
@ -785,16 +838,14 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -833,7 +884,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetChannelDataCachePath(IChannel channel,
|
private string GetChannelDataCachePath(
|
||||||
|
IChannel channel,
|
||||||
string userId,
|
string userId,
|
||||||
string externalFolderId,
|
string externalFolderId,
|
||||||
ChannelItemSortField? sortField,
|
ChannelItemSortField? sortField,
|
||||||
@ -843,8 +895,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var userCacheKey = string.Empty;
|
var userCacheKey = string.Empty;
|
||||||
|
|
||||||
var hasCacheKey = channel as IHasCacheKey;
|
if (channel is IHasCacheKey hasCacheKey)
|
||||||
if (hasCacheKey != null)
|
|
||||||
{
|
{
|
||||||
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
|
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
|
||||||
}
|
}
|
||||||
@ -858,6 +909,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
filename += "-sortField-" + sortField.Value;
|
filename += "-sortField-" + sortField.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortDescending)
|
if (sortDescending)
|
||||||
{
|
{
|
||||||
filename += "-sortDescending";
|
filename += "-sortDescending";
|
||||||
@ -865,7 +917,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
return Path.Combine(_config.ApplicationPaths.CachePath,
|
return Path.Combine(
|
||||||
|
_config.ApplicationPaths.CachePath,
|
||||||
"channels",
|
"channels",
|
||||||
channelId,
|
channelId,
|
||||||
version,
|
version,
|
||||||
@ -919,60 +972,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;
|
||||||
@ -981,7 +1006,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
item.RunTimeTicks = null;
|
item.RunTimeTicks = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (isNew || !enableMediaProbe)
|
else if (isNew || !enableMediaProbe)
|
||||||
{
|
{
|
||||||
item.RunTimeTicks = info.RunTimeTicks;
|
item.RunTimeTicks = info.RunTimeTicks;
|
||||||
@ -1014,26 +1038,24 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasArtists = item as IHasArtist;
|
if (item is IHasArtist hasArtists)
|
||||||
if (hasArtists != null)
|
|
||||||
{
|
{
|
||||||
hasArtists.Artists = info.Artists.ToArray();
|
hasArtists.Artists = info.Artists.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasAlbumArtists = item as IHasAlbumArtist;
|
if (item is IHasAlbumArtist hasAlbumArtists)
|
||||||
if (hasAlbumArtists != null)
|
|
||||||
{
|
{
|
||||||
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
|
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var trailer = item as Trailer;
|
if (item is Trailer trailer)
|
||||||
if (trailer != null)
|
|
||||||
{
|
{
|
||||||
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
|
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
|
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
trailer.TrailerTypes = info.TrailerTypes.ToArray();
|
trailer.TrailerTypes = info.TrailerTypes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1057,6 +1079,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
|
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ChannelId = internalChannelId;
|
item.ChannelId = internalChannelId;
|
||||||
|
|
||||||
if (!item.ParentId.Equals(parentFolderId))
|
if (!item.ParentId.Equals(parentFolderId))
|
||||||
@ -1064,16 +1087,17 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
|
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ParentId = parentFolderId;
|
item.ParentId = parentFolderId;
|
||||||
|
|
||||||
var hasSeries = item as IHasSeries;
|
if (item is IHasSeries hasSeries)
|
||||||
if (hasSeries != null)
|
|
||||||
{
|
{
|
||||||
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
|
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasSeries.SeriesName = info.SeriesName;
|
hasSeries.SeriesName = info.SeriesName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1082,24 +1106,23 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
|
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ExternalId = info.Id;
|
item.ExternalId = info.Id;
|
||||||
|
|
||||||
var channelAudioItem = item as Audio;
|
if (item is Audio channelAudioItem)
|
||||||
if (channelAudioItem != null)
|
|
||||||
{
|
{
|
||||||
channelAudioItem.ExtraType = info.ExtraType;
|
channelAudioItem.ExtraType = info.ExtraType;
|
||||||
|
|
||||||
var mediaSource = info.MediaSources.FirstOrDefault();
|
var mediaSource = info.MediaSources.FirstOrDefault();
|
||||||
item.Path = mediaSource == null ? null : mediaSource.Path;
|
item.Path = mediaSource?.Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
var channelVideoItem = item as Video;
|
if (item is Video channelVideoItem)
|
||||||
if (channelVideoItem != null)
|
|
||||||
{
|
{
|
||||||
channelVideoItem.ExtraType = info.ExtraType;
|
channelVideoItem.ExtraType = info.ExtraType;
|
||||||
|
|
||||||
var mediaSource = info.MediaSources.FirstOrDefault();
|
var mediaSource = info.MediaSources.FirstOrDefault();
|
||||||
item.Path = mediaSource == null ? null : mediaSource.Path;
|
item.Path = mediaSource?.Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
|
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
|
||||||
@ -1156,7 +1179,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
|
if (isNew || forceUpdate || item.DateLastRefreshed == default)
|
||||||
{
|
{
|
||||||
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
||||||
}
|
}
|
||||||
|
@ -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,21 +9,34 @@ 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 IUserManager _userManager;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, 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)
|
||||||
{
|
{
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_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;
|
||||||
@ -7,29 +5,36 @@ using System.Threading.Tasks;
|
|||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Progress;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MediaBrowser.Model.Globalization;
|
|
||||||
|
|
||||||
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 IUserManager _userManager;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _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,
|
||||||
IUserManager userManager,
|
|
||||||
ILogger<RefreshChannelsScheduledTask> logger,
|
ILogger<RefreshChannelsScheduledTask> logger,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILocalizationManager localization)
|
ILocalizationManager localization)
|
||||||
{
|
{
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
@ -63,7 +68,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
await manager.RefreshChannels(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
await manager.RefreshChannels(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await new ChannelPostScanTask(_channelManager, _userManager, _logger, _libraryManager).Run(progress, cancellationToken)
|
await new ChannelPostScanTask(_channelManager, _logger, _libraryManager).Run(progress, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +77,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
|
|
||||||
// Every so often
|
// Every so often
|
||||||
new TaskTriggerInfo
|
new TaskTriggerInfo
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
@ -48,13 +58,10 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
var episode = subItem as Episode;
|
var episode = subItem as Episode;
|
||||||
|
|
||||||
if (episode != null)
|
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))
|
||||||
@ -80,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;
|
||||||
@ -23,6 +21,9 @@ 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;
|
||||||
@ -33,6 +34,16 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
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,
|
||||||
@ -51,8 +62,13 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
_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)
|
||||||
@ -109,11 +125,12 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
{
|
{
|
||||||
var folder = GetCollectionsFolder(false).Result;
|
var folder = GetCollectionsFolder(false).Result;
|
||||||
|
|
||||||
return folder == null ?
|
return folder == null
|
||||||
new List<BoxSet>() :
|
? Enumerable.Empty<BoxSet>()
|
||||||
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;
|
||||||
@ -178,11 +195,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)));
|
||||||
@ -191,7 +210,6 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
|
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
|
||||||
{
|
{
|
||||||
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
|
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
|
||||||
|
|
||||||
if (collection == null)
|
if (collection == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("No collection exists with the supplied Id");
|
throw new ArgumentException("No collection exists with the supplied Id");
|
||||||
@ -246,11 +264,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;
|
||||||
@ -289,10 +309,13 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
|
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
|
||||||
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
_providerManager.QueueRefresh(
|
||||||
{
|
collection.Id,
|
||||||
ForceSave = true
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
}, RefreshPriority.High);
|
{
|
||||||
|
ForceSave = true
|
||||||
|
},
|
||||||
|
RefreshPriority.High);
|
||||||
|
|
||||||
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
|
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
|
||||||
{
|
{
|
||||||
@ -301,6 +324,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>();
|
||||||
@ -309,9 +333,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;
|
||||||
}
|
}
|
||||||
@ -341,12 +363,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 _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,
|
||||||
|
@ -69,21 +69,16 @@ namespace Emby.Server.Implementations.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateMetadataPath()
|
private void UpdateMetadataPath()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(Configuration.MetadataPath))
|
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = string.IsNullOrWhiteSpace(Configuration.MetadataPath)
|
||||||
{
|
? Path.Combine(ApplicationPaths.ProgramDataPath, "metadata")
|
||||||
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
|
: Configuration.MetadataPath;
|
||||||
}
|
|
||||||
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;
|
||||||
|
@ -1,5 +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 MediaBrowser.Providers.Music;
|
using MediaBrowser.Providers.Music;
|
||||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ namespace Emby.Server.Implementations
|
|||||||
{
|
{
|
||||||
{ HostWebClientKey, bool.TrueString },
|
{ HostWebClientKey, bool.TrueString },
|
||||||
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
||||||
|
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
|
||||||
{ FfmpegProbeSizeKey, "1G" },
|
{ FfmpegProbeSizeKey, "1G" },
|
||||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||||
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
||||||
|
@ -31,7 +31,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 +56,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 +72,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 />
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using SQLitePCL.pretty;
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Data
|
namespace Emby.Server.Implementations.Data
|
||||||
@ -109,25 +107,6 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serializes to bytes.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>System.Byte[][].</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">obj</exception>
|
|
||||||
public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
json.SerializeToStream(obj, stream);
|
|
||||||
return stream.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
|
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
|
||||||
{
|
{
|
||||||
var commandText = string.Format(
|
var commandText = string.Format(
|
||||||
@ -287,7 +266,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TryBind(this IStatement statement, string name, byte[] value)
|
public static void TryBind(this IStatement statement, string name, ReadOnlySpan<byte> value)
|
||||||
{
|
{
|
||||||
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
|
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
|
||||||
{
|
{
|
||||||
@ -383,11 +362,11 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
|
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement)
|
||||||
{
|
{
|
||||||
while (This.MoveNext())
|
while (statement.MoveNext())
|
||||||
{
|
{
|
||||||
yield return This.Current;
|
yield return statement.Current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,12 +39,11 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
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 +70,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 +82,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 +99,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>
|
||||||
@ -1991,7 +1990,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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3315,7 +3321,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
for (int i = 0; i < str.Length; i++)
|
for (int i = 0; i < str.Length; i++)
|
||||||
{
|
{
|
||||||
if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i]))))
|
if (!char.IsLetter(str[i]) && !char.IsNumber(str[i]))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -3339,7 +3345,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return IsAlphaNumeric(value);
|
return IsAlphaNumeric(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "")
|
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement)
|
||||||
{
|
{
|
||||||
if (query.IsResumable ?? false)
|
if (query.IsResumable ?? false)
|
||||||
{
|
{
|
||||||
@ -3351,27 +3357,27 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
if (query.IsHD.HasValue)
|
if (query.IsHD.HasValue)
|
||||||
{
|
{
|
||||||
var threshold = 1200;
|
const int Threshold = 1200;
|
||||||
if (query.IsHD.Value)
|
if (query.IsHD.Value)
|
||||||
{
|
{
|
||||||
minWidth = threshold;
|
minWidth = Threshold;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
maxWidth = threshold - 1;
|
maxWidth = Threshold - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.Is4K.HasValue)
|
if (query.Is4K.HasValue)
|
||||||
{
|
{
|
||||||
var threshold = 3800;
|
const int Threshold = 3800;
|
||||||
if (query.Is4K.Value)
|
if (query.Is4K.Value)
|
||||||
{
|
{
|
||||||
minWidth = threshold;
|
minWidth = Threshold;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
maxWidth = threshold - 1;
|
maxWidth = Threshold - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3380,93 +3386,61 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (minWidth.HasValue)
|
if (minWidth.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("Width>=@MinWidth");
|
whereClauses.Add("Width>=@MinWidth");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinWidth", minWidth);
|
||||||
{
|
|
||||||
statement.TryBind("@MinWidth", minWidth);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MinHeight.HasValue)
|
if (query.MinHeight.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("Height>=@MinHeight");
|
whereClauses.Add("Height>=@MinHeight");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinHeight", query.MinHeight);
|
||||||
{
|
|
||||||
statement.TryBind("@MinHeight", query.MinHeight);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxWidth.HasValue)
|
if (maxWidth.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("Width<=@MaxWidth");
|
whereClauses.Add("Width<=@MaxWidth");
|
||||||
if (statement != null)
|
statement?.TryBind("@MaxWidth", maxWidth);
|
||||||
{
|
|
||||||
statement.TryBind("@MaxWidth", maxWidth);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MaxHeight.HasValue)
|
if (query.MaxHeight.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("Height<=@MaxHeight");
|
whereClauses.Add("Height<=@MaxHeight");
|
||||||
if (statement != null)
|
statement?.TryBind("@MaxHeight", query.MaxHeight);
|
||||||
{
|
|
||||||
statement.TryBind("@MaxHeight", query.MaxHeight);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsLocked.HasValue)
|
if (query.IsLocked.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("IsLocked=@IsLocked");
|
whereClauses.Add("IsLocked=@IsLocked");
|
||||||
if (statement != null)
|
statement?.TryBind("@IsLocked", query.IsLocked);
|
||||||
{
|
|
||||||
statement.TryBind("@IsLocked", query.IsLocked);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tags = query.Tags.ToList();
|
var tags = query.Tags.ToList();
|
||||||
var excludeTags = query.ExcludeTags.ToList();
|
var excludeTags = query.ExcludeTags.ToList();
|
||||||
|
|
||||||
if (query.IsMovie ?? false)
|
if (query.IsMovie == true)
|
||||||
{
|
{
|
||||||
var alternateTypes = new List<string>();
|
if (query.IncludeItemTypes.Length == 0
|
||||||
if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
|
|| query.IncludeItemTypes.Contains(nameof(Movie))
|
||||||
|
|| query.IncludeItemTypes.Contains(nameof(Trailer)))
|
||||||
{
|
{
|
||||||
alternateTypes.Add(typeof(Movie).FullName);
|
whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
|
||||||
}
|
|
||||||
if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
|
|
||||||
{
|
|
||||||
alternateTypes.Add(typeof(Trailer).FullName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var programAttribtues = new List<string>();
|
|
||||||
if (alternateTypes.Count == 0)
|
|
||||||
{
|
|
||||||
programAttribtues.Add("IsMovie=@IsMovie");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)");
|
whereClauses.Add("IsMovie=@IsMovie");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statement != null)
|
statement?.TryBind("@IsMovie", true);
|
||||||
{
|
|
||||||
statement.TryBind("@IsMovie", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
whereClauses.Add("(" + string.Join(" OR ", programAttribtues) + ")");
|
|
||||||
}
|
}
|
||||||
else if (query.IsMovie.HasValue)
|
else if (query.IsMovie.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("IsMovie=@IsMovie");
|
whereClauses.Add("IsMovie=@IsMovie");
|
||||||
if (statement != null)
|
statement?.TryBind("@IsMovie", query.IsMovie);
|
||||||
{
|
|
||||||
statement.TryBind("@IsMovie", query.IsMovie);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsSeries.HasValue)
|
if (query.IsSeries.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("IsSeries=@IsSeries");
|
whereClauses.Add("IsSeries=@IsSeries");
|
||||||
if (statement != null)
|
statement?.TryBind("@IsSeries", query.IsSeries);
|
||||||
{
|
|
||||||
statement.TryBind("@IsSeries", query.IsSeries);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsSports.HasValue)
|
if (query.IsSports.HasValue)
|
||||||
@ -3518,10 +3492,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (query.IsFolder.HasValue)
|
if (query.IsFolder.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("IsFolder=@IsFolder");
|
whereClauses.Add("IsFolder=@IsFolder");
|
||||||
if (statement != null)
|
statement?.TryBind("@IsFolder", query.IsFolder);
|
||||||
{
|
|
||||||
statement.TryBind("@IsFolder", query.IsFolder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
|
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
|
||||||
@ -3532,10 +3503,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (excludeTypes.Length == 1)
|
if (excludeTypes.Length == 1)
|
||||||
{
|
{
|
||||||
whereClauses.Add("type<>@type");
|
whereClauses.Add("type<>@type");
|
||||||
if (statement != null)
|
statement?.TryBind("@type", excludeTypes[0]);
|
||||||
{
|
|
||||||
statement.TryBind("@type", excludeTypes[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (excludeTypes.Length > 1)
|
else if (excludeTypes.Length > 1)
|
||||||
{
|
{
|
||||||
@ -3546,10 +3514,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
else if (includeTypes.Length == 1)
|
else if (includeTypes.Length == 1)
|
||||||
{
|
{
|
||||||
whereClauses.Add("type=@type");
|
whereClauses.Add("type=@type");
|
||||||
if (statement != null)
|
statement?.TryBind("@type", includeTypes[0]);
|
||||||
{
|
|
||||||
statement.TryBind("@type", includeTypes[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (includeTypes.Length > 1)
|
else if (includeTypes.Length > 1)
|
||||||
{
|
{
|
||||||
@ -3560,10 +3525,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (query.ChannelIds.Length == 1)
|
if (query.ChannelIds.Length == 1)
|
||||||
{
|
{
|
||||||
whereClauses.Add("ChannelId=@ChannelId");
|
whereClauses.Add("ChannelId=@ChannelId");
|
||||||
if (statement != null)
|
statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||||
{
|
|
||||||
statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (query.ChannelIds.Length > 1)
|
else if (query.ChannelIds.Length > 1)
|
||||||
{
|
{
|
||||||
@ -3574,98 +3536,65 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (!query.ParentId.Equals(Guid.Empty))
|
if (!query.ParentId.Equals(Guid.Empty))
|
||||||
{
|
{
|
||||||
whereClauses.Add("ParentId=@ParentId");
|
whereClauses.Add("ParentId=@ParentId");
|
||||||
if (statement != null)
|
statement?.TryBind("@ParentId", query.ParentId);
|
||||||
{
|
|
||||||
statement.TryBind("@ParentId", query.ParentId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.Path))
|
if (!string.IsNullOrWhiteSpace(query.Path))
|
||||||
{
|
{
|
||||||
whereClauses.Add("Path=@Path");
|
whereClauses.Add("Path=@Path");
|
||||||
if (statement != null)
|
statement?.TryBind("@Path", GetPathToSave(query.Path));
|
||||||
{
|
|
||||||
statement.TryBind("@Path", GetPathToSave(query.Path));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
|
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
|
||||||
{
|
{
|
||||||
whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
|
whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
|
||||||
if (statement != null)
|
statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
|
||||||
{
|
|
||||||
statement.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MinCommunityRating.HasValue)
|
if (query.MinCommunityRating.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("CommunityRating>=@MinCommunityRating");
|
whereClauses.Add("CommunityRating>=@MinCommunityRating");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MinIndexNumber.HasValue)
|
if (query.MinIndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("IndexNumber>=@MinIndexNumber");
|
whereClauses.Add("IndexNumber>=@MinIndexNumber");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MinDateCreated.HasValue)
|
if (query.MinDateCreated.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("DateCreated>=@MinDateCreated");
|
whereClauses.Add("DateCreated>=@MinDateCreated");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MinDateCreated", query.MinDateCreated.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MinDateLastSaved.HasValue)
|
if (query.MinDateLastSaved.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MinDateLastSavedForUser.HasValue)
|
if (query.MinDateLastSavedForUser.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IndexNumber.HasValue)
|
if (query.IndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("IndexNumber=@IndexNumber");
|
whereClauses.Add("IndexNumber=@IndexNumber");
|
||||||
if (statement != null)
|
statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@IndexNumber", query.IndexNumber.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (query.ParentIndexNumber.HasValue)
|
if (query.ParentIndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
|
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
|
||||||
if (statement != null)
|
statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (query.ParentIndexNumberNotEquals.HasValue)
|
if (query.ParentIndexNumberNotEquals.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
|
whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
|
||||||
if (statement != null)
|
statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var minEndDate = query.MinEndDate;
|
var minEndDate = query.MinEndDate;
|
||||||
@ -3686,73 +3615,59 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (minEndDate.HasValue)
|
if (minEndDate.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("EndDate>=@MinEndDate");
|
whereClauses.Add("EndDate>=@MinEndDate");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinEndDate", minEndDate.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MinEndDate", minEndDate.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxEndDate.HasValue)
|
if (maxEndDate.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("EndDate<=@MaxEndDate");
|
whereClauses.Add("EndDate<=@MaxEndDate");
|
||||||
if (statement != null)
|
statement?.TryBind("@MaxEndDate", maxEndDate.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MaxEndDate", maxEndDate.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MinStartDate.HasValue)
|
if (query.MinStartDate.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("StartDate>=@MinStartDate");
|
whereClauses.Add("StartDate>=@MinStartDate");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinStartDate", query.MinStartDate.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MinStartDate", query.MinStartDate.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MaxStartDate.HasValue)
|
if (query.MaxStartDate.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("StartDate<=@MaxStartDate");
|
whereClauses.Add("StartDate<=@MaxStartDate");
|
||||||
if (statement != null)
|
statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MaxStartDate", query.MaxStartDate.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MinPremiereDate.HasValue)
|
if (query.MinPremiereDate.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("PremiereDate>=@MinPremiereDate");
|
whereClauses.Add("PremiereDate>=@MinPremiereDate");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MaxPremiereDate.HasValue)
|
if (query.MaxPremiereDate.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("PremiereDate<=@MaxPremiereDate");
|
whereClauses.Add("PremiereDate<=@MaxPremiereDate");
|
||||||
if (statement != null)
|
statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.TrailerTypes.Length > 0)
|
var trailerTypes = query.TrailerTypes;
|
||||||
|
int trailerTypesLen = trailerTypes.Length;
|
||||||
|
if (trailerTypesLen > 0)
|
||||||
{
|
{
|
||||||
var clauses = new List<string>();
|
const string Or = " OR ";
|
||||||
var index = 0;
|
StringBuilder clause = new StringBuilder("(", trailerTypesLen * 32);
|
||||||
foreach (var type in query.TrailerTypes)
|
for (int i = 0; i < trailerTypesLen; i++)
|
||||||
{
|
{
|
||||||
var paramName = "@TrailerTypes" + index;
|
var paramName = "@TrailerTypes" + i;
|
||||||
|
clause.Append("TrailerTypes like ")
|
||||||
clauses.Add("TrailerTypes like " + paramName);
|
.Append(paramName)
|
||||||
if (statement != null)
|
.Append(Or);
|
||||||
{
|
statement?.TryBind(paramName, "%" + trailerTypes[i] + "%");
|
||||||
statement.TryBind(paramName, "%" + type + "%");
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
|
||||||
whereClauses.Add(clause);
|
// Remove last " OR "
|
||||||
|
clause.Length -= Or.Length;
|
||||||
|
clause.Append(')');
|
||||||
|
|
||||||
|
whereClauses.Add(clause.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsAiring.HasValue)
|
if (query.IsAiring.HasValue)
|
||||||
@ -3760,24 +3675,15 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (query.IsAiring.Value)
|
if (query.IsAiring.Value)
|
||||||
{
|
{
|
||||||
whereClauses.Add("StartDate<=@MaxStartDate");
|
whereClauses.Add("StartDate<=@MaxStartDate");
|
||||||
if (statement != null)
|
statement?.TryBind("@MaxStartDate", DateTime.UtcNow);
|
||||||
{
|
|
||||||
statement.TryBind("@MaxStartDate", DateTime.UtcNow);
|
|
||||||
}
|
|
||||||
|
|
||||||
whereClauses.Add("EndDate>=@MinEndDate");
|
whereClauses.Add("EndDate>=@MinEndDate");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinEndDate", DateTime.UtcNow);
|
||||||
{
|
|
||||||
statement.TryBind("@MinEndDate", DateTime.UtcNow);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
|
whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
|
||||||
if (statement != null)
|
statement?.TryBind("@IsAiringDate", DateTime.UtcNow);
|
||||||
{
|
|
||||||
statement.TryBind("@IsAiringDate", DateTime.UtcNow);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3792,13 +3698,10 @@ namespace Emby.Server.Implementations.Data
|
|||||||
var paramName = "@PersonId" + index;
|
var paramName = "@PersonId" + index;
|
||||||
|
|
||||||
clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))");
|
clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))");
|
||||||
|
statement?.TryBind(paramName, personId.ToByteArray());
|
||||||
if (statement != null)
|
|
||||||
{
|
|
||||||
statement.TryBind(paramName, personId.ToByteArray());
|
|
||||||
}
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||||
whereClauses.Add(clause);
|
whereClauses.Add(clause);
|
||||||
}
|
}
|
||||||
@ -3806,47 +3709,31 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (!string.IsNullOrWhiteSpace(query.Person))
|
if (!string.IsNullOrWhiteSpace(query.Person))
|
||||||
{
|
{
|
||||||
whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
|
whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
|
||||||
if (statement != null)
|
statement?.TryBind("@PersonName", query.Person);
|
||||||
{
|
|
||||||
statement.TryBind("@PersonName", query.Person);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.MinSortName))
|
if (!string.IsNullOrWhiteSpace(query.MinSortName))
|
||||||
{
|
{
|
||||||
whereClauses.Add("SortName>=@MinSortName");
|
whereClauses.Add("SortName>=@MinSortName");
|
||||||
if (statement != null)
|
statement?.TryBind("@MinSortName", query.MinSortName);
|
||||||
{
|
|
||||||
statement.TryBind("@MinSortName", query.MinSortName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
|
if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
|
||||||
{
|
{
|
||||||
whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
|
whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
|
||||||
if (statement != null)
|
statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
|
||||||
{
|
|
||||||
statement.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.ExternalId))
|
if (!string.IsNullOrWhiteSpace(query.ExternalId))
|
||||||
{
|
{
|
||||||
whereClauses.Add("ExternalId=@ExternalId");
|
whereClauses.Add("ExternalId=@ExternalId");
|
||||||
if (statement != null)
|
statement?.TryBind("@ExternalId", query.ExternalId);
|
||||||
{
|
|
||||||
statement.TryBind("@ExternalId", query.ExternalId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.Name))
|
if (!string.IsNullOrWhiteSpace(query.Name))
|
||||||
{
|
{
|
||||||
whereClauses.Add("CleanName=@Name");
|
whereClauses.Add("CleanName=@Name");
|
||||||
|
statement?.TryBind("@Name", GetCleanValue(query.Name));
|
||||||
if (statement != null)
|
|
||||||
{
|
|
||||||
statement.TryBind("@Name", GetCleanValue(query.Name));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are the same, for now
|
// These are the same, for now
|
||||||
@ -3865,28 +3752,21 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
|
if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
|
||||||
{
|
{
|
||||||
whereClauses.Add("SortName like @NameStartsWith");
|
whereClauses.Add("SortName like @NameStartsWith");
|
||||||
if (statement != null)
|
statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%");
|
||||||
{
|
|
||||||
statement.TryBind("@NameStartsWith", query.NameStartsWith + "%");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
|
if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
|
||||||
{
|
{
|
||||||
whereClauses.Add("SortName >= @NameStartsWithOrGreater");
|
whereClauses.Add("SortName >= @NameStartsWithOrGreater");
|
||||||
// lowercase this because SortName is stored as lowercase
|
// lowercase this because SortName is stored as lowercase
|
||||||
if (statement != null)
|
statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
|
||||||
{
|
|
||||||
statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.NameLessThan))
|
if (!string.IsNullOrWhiteSpace(query.NameLessThan))
|
||||||
{
|
{
|
||||||
whereClauses.Add("SortName < @NameLessThan");
|
whereClauses.Add("SortName < @NameLessThan");
|
||||||
// lowercase this because SortName is stored as lowercase
|
// lowercase this because SortName is stored as lowercase
|
||||||
if (statement != null)
|
statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
|
||||||
{
|
|
||||||
statement.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ImageTypes.Length > 0)
|
if (query.ImageTypes.Length > 0)
|
||||||
@ -3902,18 +3782,12 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (query.IsLiked.Value)
|
if (query.IsLiked.Value)
|
||||||
{
|
{
|
||||||
whereClauses.Add("rating>=@UserRating");
|
whereClauses.Add("rating>=@UserRating");
|
||||||
if (statement != null)
|
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||||
{
|
|
||||||
statement.TryBind("@UserRating", UserItemData.MinLikeValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
whereClauses.Add("(rating is null or rating<@UserRating)");
|
whereClauses.Add("(rating is null or rating<@UserRating)");
|
||||||
if (statement != null)
|
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||||
{
|
|
||||||
statement.TryBind("@UserRating", UserItemData.MinLikeValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3927,10 +3801,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
|
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
|
||||||
}
|
}
|
||||||
if (statement != null)
|
|
||||||
{
|
statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
|
||||||
statement.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsFavorite.HasValue)
|
if (query.IsFavorite.HasValue)
|
||||||
@ -3943,10 +3815,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
|
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
|
||||||
}
|
}
|
||||||
if (statement != null)
|
|
||||||
{
|
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
|
||||||
statement.TryBind("@IsFavorite", query.IsFavorite.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EnableJoinUserData(query))
|
if (EnableJoinUserData(query))
|
||||||
@ -3975,10 +3845,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
whereClauses.Add("(played is null or played=@IsPlayed)");
|
whereClauses.Add("(played is null or played=@IsPlayed)");
|
||||||
}
|
}
|
||||||
if (statement != null)
|
|
||||||
{
|
statement?.TryBind("@IsPlayed", query.IsPlayed.Value);
|
||||||
statement.TryBind("@IsPlayed", query.IsPlayed.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4010,6 +3878,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||||
whereClauses.Add(clause);
|
whereClauses.Add(clause);
|
||||||
}
|
}
|
||||||
@ -4029,6 +3898,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||||
whereClauses.Add(clause);
|
whereClauses.Add(clause);
|
||||||
}
|
}
|
||||||
@ -4762,18 +4632,22 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
list.Add(typeof(Person).Name);
|
list.Add(typeof(Person).Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsTypeInQuery(typeof(Genre).Name, query))
|
if (IsTypeInQuery(typeof(Genre).Name, query))
|
||||||
{
|
{
|
||||||
list.Add(typeof(Genre).Name);
|
list.Add(typeof(Genre).Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsTypeInQuery(typeof(MusicGenre).Name, query))
|
if (IsTypeInQuery(typeof(MusicGenre).Name, query))
|
||||||
{
|
{
|
||||||
list.Add(typeof(MusicGenre).Name);
|
list.Add(typeof(MusicGenre).Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsTypeInQuery(typeof(MusicArtist).Name, query))
|
if (IsTypeInQuery(typeof(MusicArtist).Name, query))
|
||||||
{
|
{
|
||||||
list.Add(typeof(MusicArtist).Name);
|
list.Add(typeof(MusicArtist).Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsTypeInQuery(typeof(Studio).Name, query))
|
if (IsTypeInQuery(typeof(Studio).Name, query))
|
||||||
{
|
{
|
||||||
list.Add(typeof(Studio).Name);
|
list.Add(typeof(Studio).Name);
|
||||||
@ -4847,7 +4721,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Type[] KnownTypes =
|
private static readonly Type[] _knownTypes =
|
||||||
{
|
{
|
||||||
typeof(LiveTvProgram),
|
typeof(LiveTvProgram),
|
||||||
typeof(LiveTvChannel),
|
typeof(LiveTvChannel),
|
||||||
@ -4916,7 +4790,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
{
|
{
|
||||||
var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (var t in KnownTypes)
|
foreach (var t in _knownTypes)
|
||||||
{
|
{
|
||||||
dict[t.Name] = new[] { t.FullName };
|
dict[t.Name] = new[] { t.FullName };
|
||||||
}
|
}
|
||||||
@ -4928,7 +4802,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Not crazy about having this all the way down here, but at least it's in one place
|
// Not crazy about having this all the way down here, but at least it's in one place
|
||||||
readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
|
private readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
|
||||||
|
|
||||||
private string[] MapIncludeItemTypes(string value)
|
private string[] MapIncludeItemTypes(string value)
|
||||||
{
|
{
|
||||||
@ -4945,7 +4819,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteItem(Guid id, CancellationToken cancellationToken)
|
public void DeleteItem(Guid id)
|
||||||
{
|
{
|
||||||
if (id == Guid.Empty)
|
if (id == Guid.Empty)
|
||||||
{
|
{
|
||||||
@ -4981,7 +4855,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteWithSingleParam(IDatabaseConnection db, string query, byte[] value)
|
private void ExecuteWithSingleParam(IDatabaseConnection db, string query, ReadOnlySpan<byte> value)
|
||||||
{
|
{
|
||||||
using (var statement = PrepareStatement(db, query))
|
using (var statement = PrepareStatement(db, query))
|
||||||
{
|
{
|
||||||
@ -5541,6 +5415,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
{
|
{
|
||||||
GetWhereClauses(typeSubQuery, null);
|
GetWhereClauses(typeSubQuery, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
BindSimilarParams(query, statement);
|
BindSimilarParams(query, statement);
|
||||||
BindSearchParams(query, statement);
|
BindSearchParams(query, statement);
|
||||||
GetWhereClauses(innerQuery, statement);
|
GetWhereClauses(innerQuery, statement);
|
||||||
@ -5582,7 +5457,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
}
|
}
|
||||||
|
|
||||||
var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
|
var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
.ToLookup(i => i);
|
.ToLookup(x => x);
|
||||||
|
|
||||||
foreach (var type in allTypes)
|
foreach (var type in allTypes)
|
||||||
{
|
{
|
||||||
@ -5673,30 +5548,26 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
|
|
||||||
private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
|
private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
|
||||||
{
|
{
|
||||||
|
const int Limit = 100;
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
var limit = 100;
|
|
||||||
|
|
||||||
while (startIndex < values.Count)
|
while (startIndex < values.Count)
|
||||||
{
|
{
|
||||||
var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values ");
|
var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values ");
|
||||||
|
|
||||||
var endIndex = Math.Min(values.Count, startIndex + limit);
|
var endIndex = Math.Min(values.Count, startIndex + Limit);
|
||||||
var isSubsequentRow = false;
|
|
||||||
|
|
||||||
for (var i = startIndex; i < endIndex; i++)
|
for (var i = startIndex; i < endIndex; i++)
|
||||||
{
|
{
|
||||||
if (isSubsequentRow)
|
|
||||||
{
|
|
||||||
insertText.Append(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
insertText.AppendFormat(
|
insertText.AppendFormat(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})",
|
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),",
|
||||||
i);
|
i);
|
||||||
isSubsequentRow = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove last comma
|
||||||
|
insertText.Length--;
|
||||||
|
|
||||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||||
{
|
{
|
||||||
statement.TryBind("@ItemId", idBlob);
|
statement.TryBind("@ItemId", idBlob);
|
||||||
@ -5724,7 +5595,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex += limit;
|
startIndex += Limit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5759,28 +5630,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
|
|
||||||
private void InsertPeople(byte[] idBlob, List<PersonInfo> people, IDatabaseConnection db)
|
private void InsertPeople(byte[] idBlob, List<PersonInfo> people, IDatabaseConnection db)
|
||||||
{
|
{
|
||||||
|
const int Limit = 100;
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
var limit = 100;
|
|
||||||
var listIndex = 0;
|
var listIndex = 0;
|
||||||
|
|
||||||
while (startIndex < people.Count)
|
while (startIndex < people.Count)
|
||||||
{
|
{
|
||||||
var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ");
|
var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ");
|
||||||
|
|
||||||
var endIndex = Math.Min(people.Count, startIndex + limit);
|
var endIndex = Math.Min(people.Count, startIndex + Limit);
|
||||||
var isSubsequentRow = false;
|
|
||||||
|
|
||||||
for (var i = startIndex; i < endIndex; i++)
|
for (var i = startIndex; i < endIndex; i++)
|
||||||
{
|
{
|
||||||
if (isSubsequentRow)
|
insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture));
|
||||||
{
|
|
||||||
insertText.Append(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture));
|
|
||||||
isSubsequentRow = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove last comma
|
||||||
|
insertText.Length--;
|
||||||
|
|
||||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||||
{
|
{
|
||||||
statement.TryBind("@ItemId", idBlob);
|
statement.TryBind("@ItemId", idBlob);
|
||||||
@ -5804,16 +5670,17 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex += limit;
|
startIndex += Limit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader)
|
private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader)
|
||||||
{
|
{
|
||||||
var item = new PersonInfo();
|
var item = new PersonInfo
|
||||||
|
{
|
||||||
item.ItemId = reader.GetGuid(0);
|
ItemId = reader.GetGuid(0),
|
||||||
item.Name = reader.GetString(1);
|
Name = reader.GetString(1)
|
||||||
|
};
|
||||||
|
|
||||||
if (!reader.IsDBNull(2))
|
if (!reader.IsDBNull(2))
|
||||||
{
|
{
|
||||||
@ -5920,20 +5787,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
|
|
||||||
private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db)
|
private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db)
|
||||||
{
|
{
|
||||||
|
const int Limit = 10;
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
var limit = 10;
|
|
||||||
|
|
||||||
while (startIndex < streams.Count)
|
while (startIndex < streams.Count)
|
||||||
{
|
{
|
||||||
var insertText = new StringBuilder(string.Format("insert into mediastreams ({0}) values ", string.Join(",", _mediaStreamSaveColumns)));
|
var insertText = new StringBuilder("insert into mediastreams (");
|
||||||
|
foreach (var column in _mediaStreamSaveColumns)
|
||||||
|
{
|
||||||
|
insertText.Append(column).Append(',');
|
||||||
|
}
|
||||||
|
|
||||||
var endIndex = Math.Min(streams.Count, startIndex + limit);
|
// Remove last comma
|
||||||
|
insertText.Length--;
|
||||||
|
insertText.Append(") values ");
|
||||||
|
|
||||||
|
var endIndex = Math.Min(streams.Count, startIndex + Limit);
|
||||||
|
|
||||||
for (var i = startIndex; i < endIndex; i++)
|
for (var i = startIndex; i < endIndex; i++)
|
||||||
{
|
{
|
||||||
if (i != startIndex)
|
if (i != startIndex)
|
||||||
{
|
{
|
||||||
insertText.Append(",");
|
insertText.Append(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = i.ToString(CultureInfo.InvariantCulture);
|
var index = i.ToString(CultureInfo.InvariantCulture);
|
||||||
@ -5941,11 +5816,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
|
|
||||||
foreach (var column in _mediaStreamSaveColumns.Skip(1))
|
foreach (var column in _mediaStreamSaveColumns.Skip(1))
|
||||||
{
|
{
|
||||||
insertText.Append("@" + column + index + ",");
|
insertText.Append('@').Append(column).Append(index).Append(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
insertText.Length -= 1; // Remove the last comma
|
insertText.Length -= 1; // Remove the last comma
|
||||||
|
|
||||||
insertText.Append(")");
|
insertText.Append(')');
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||||
@ -6007,7 +5883,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex += limit;
|
startIndex += Limit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6024,7 +5900,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
Index = reader[1].ToInt()
|
Index = reader[1].ToInt()
|
||||||
};
|
};
|
||||||
|
|
||||||
item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader[2].ToString(), true);
|
item.Type = Enum.Parse<MediaStreamType>(reader[2].ToString(), true);
|
||||||
|
|
||||||
if (reader[3].SQLiteType != SQLiteType.Null)
|
if (reader[3].SQLiteType != SQLiteType.Null)
|
||||||
{
|
{
|
||||||
|
@ -38,10 +38,11 @@ namespace Emby.Server.Implementations.Devices
|
|||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILocalizationManager _localizationManager;
|
private readonly ILocalizationManager _localizationManager;
|
||||||
|
|
||||||
private readonly IAuthenticationRepository _authRepo;
|
private readonly IAuthenticationRepository _authRepo;
|
||||||
|
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
|
||||||
|
|
||||||
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
|
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
|
||||||
|
|
||||||
public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
|
public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
|
||||||
|
|
||||||
private readonly object _cameraUploadSyncLock = new object();
|
private readonly object _cameraUploadSyncLock = new object();
|
||||||
@ -65,10 +66,9 @@ namespace Emby.Server.Implementations.Devices
|
|||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_localizationManager = localizationManager;
|
_localizationManager = localizationManager;
|
||||||
_authRepo = authRepo;
|
_authRepo = authRepo;
|
||||||
|
_capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Dictionary<string, ClientCapabilities> _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
|
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
|
var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Diagnostics
|
|
||||||
{
|
|
||||||
public class CommonProcess : IProcess
|
|
||||||
{
|
|
||||||
private readonly Process _process;
|
|
||||||
|
|
||||||
private bool _disposed = false;
|
|
||||||
private bool _hasExited;
|
|
||||||
|
|
||||||
public CommonProcess(ProcessOptions options)
|
|
||||||
{
|
|
||||||
StartInfo = options;
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
Arguments = options.Arguments,
|
|
||||||
FileName = options.FileName,
|
|
||||||
WorkingDirectory = options.WorkingDirectory,
|
|
||||||
UseShellExecute = options.UseShellExecute,
|
|
||||||
CreateNoWindow = options.CreateNoWindow,
|
|
||||||
RedirectStandardError = options.RedirectStandardError,
|
|
||||||
RedirectStandardInput = options.RedirectStandardInput,
|
|
||||||
RedirectStandardOutput = options.RedirectStandardOutput,
|
|
||||||
ErrorDialog = options.ErrorDialog
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (options.IsHidden)
|
|
||||||
{
|
|
||||||
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
_process = new Process
|
|
||||||
{
|
|
||||||
StartInfo = startInfo
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.EnableRaisingEvents)
|
|
||||||
{
|
|
||||||
_process.EnableRaisingEvents = true;
|
|
||||||
_process.Exited += OnProcessExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler Exited;
|
|
||||||
|
|
||||||
public ProcessOptions StartInfo { get; }
|
|
||||||
|
|
||||||
public StreamWriter StandardInput => _process.StandardInput;
|
|
||||||
|
|
||||||
public StreamReader StandardError => _process.StandardError;
|
|
||||||
|
|
||||||
public StreamReader StandardOutput => _process.StandardOutput;
|
|
||||||
|
|
||||||
public int ExitCode => _process.ExitCode;
|
|
||||||
|
|
||||||
private bool HasExited
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_hasExited)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_hasExited = _process.HasExited;
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
_hasExited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _hasExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
_process.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Kill()
|
|
||||||
{
|
|
||||||
_process.Kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool WaitForExit(int timeMs)
|
|
||||||
{
|
|
||||||
return _process.WaitForExit(timeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<bool> WaitForExitAsync(int timeMs)
|
|
||||||
{
|
|
||||||
// Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
|
||||||
|
|
||||||
if (HasExited)
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
timeMs = Math.Max(0, timeMs);
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
|
||||||
|
|
||||||
var cancellationToken = new CancellationTokenSource(timeMs).Token;
|
|
||||||
|
|
||||||
_process.Exited += (sender, args) => tcs.TrySetResult(true);
|
|
||||||
|
|
||||||
cancellationToken.Register(() => tcs.TrySetResult(HasExited));
|
|
||||||
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_process?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnProcessExited(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
_hasExited = true;
|
|
||||||
Exited?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Diagnostics
|
|
||||||
{
|
|
||||||
public class ProcessFactory : IProcessFactory
|
|
||||||
{
|
|
||||||
public IProcess Create(ProcessOptions options)
|
|
||||||
{
|
|
||||||
return new CommonProcess(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -38,21 +38,23 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
|
|
||||||
private readonly IApplicationHost _appHost;
|
private readonly IApplicationHost _appHost;
|
||||||
private readonly Func<IMediaSourceManager> _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly Func<ILiveTvManager> _livetvManager;
|
private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
|
||||||
|
|
||||||
|
private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
|
||||||
|
|
||||||
public DtoService(
|
public DtoService(
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<DtoService> logger,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IUserDataManager userDataRepository,
|
IUserDataManager userDataRepository,
|
||||||
IItemRepository itemRepo,
|
IItemRepository itemRepo,
|
||||||
IImageProcessor imageProcessor,
|
IImageProcessor imageProcessor,
|
||||||
IProviderManager providerManager,
|
IProviderManager providerManager,
|
||||||
IApplicationHost appHost,
|
IApplicationHost appHost,
|
||||||
Func<IMediaSourceManager> mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
Func<ILiveTvManager> livetvManager)
|
Lazy<ILiveTvManager> livetvManagerFactory)
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger(nameof(DtoService));
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userDataRepository = userDataRepository;
|
_userDataRepository = userDataRepository;
|
||||||
_itemRepo = itemRepo;
|
_itemRepo = itemRepo;
|
||||||
@ -60,7 +62,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_livetvManager = livetvManager;
|
_livetvManagerFactory = livetvManagerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -125,12 +127,12 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
|
|
||||||
if (programTuples.Count > 0)
|
if (programTuples.Count > 0)
|
||||||
{
|
{
|
||||||
_livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
|
LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelTuples.Count > 0)
|
if (channelTuples.Count > 0)
|
||||||
{
|
{
|
||||||
_livetvManager().AddChannelInfo(channelTuples, options, user);
|
LivetvManager.AddChannelInfo(channelTuples, options, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnItems;
|
return returnItems;
|
||||||
@ -142,12 +144,12 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
if (item is LiveTvChannel tvChannel)
|
if (item is LiveTvChannel tvChannel)
|
||||||
{
|
{
|
||||||
var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) };
|
var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) };
|
||||||
_livetvManager().AddChannelInfo(list, options, user);
|
LivetvManager.AddChannelInfo(list, options, user);
|
||||||
}
|
}
|
||||||
else if (item is LiveTvProgram)
|
else if (item is LiveTvProgram)
|
||||||
{
|
{
|
||||||
var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) };
|
var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) };
|
||||||
var task = _livetvManager().AddInfoToProgramDto(list, options.Fields, user);
|
var task = LivetvManager.AddInfoToProgramDto(list, options.Fields, user);
|
||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +225,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
if (item is IHasMediaSources
|
if (item is IHasMediaSources
|
||||||
&& options.ContainsField(ItemFields.MediaSources))
|
&& options.ContainsField(ItemFields.MediaSources))
|
||||||
{
|
{
|
||||||
dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(item, true, user).ToArray();
|
dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
|
||||||
|
|
||||||
NormalizeMediaSourceContainers(dto);
|
NormalizeMediaSourceContainers(dto);
|
||||||
}
|
}
|
||||||
@ -254,7 +256,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
dto.Etag = item.GetEtag(user);
|
dto.Etag = item.GetEtag(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
var liveTvManager = _livetvManager();
|
var liveTvManager = LivetvManager;
|
||||||
var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
|
var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
|
||||||
if (activeRecording != null)
|
if (activeRecording != null)
|
||||||
{
|
{
|
||||||
@ -1045,7 +1047,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
|
mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.MediaStreams = mediaStreams;
|
dto.MediaStreams = mediaStreams;
|
||||||
@ -1056,30 +1058,19 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
|
|
||||||
if (options.ContainsField(ItemFields.SpecialFeatureCount))
|
if (options.ContainsField(ItemFields.SpecialFeatureCount))
|
||||||
{
|
{
|
||||||
if (allExtras == null)
|
allExtras = item.GetExtras().ToArray();
|
||||||
{
|
|
||||||
allExtras = item.GetExtras().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value));
|
dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.LocalTrailerCount))
|
if (options.ContainsField(ItemFields.LocalTrailerCount))
|
||||||
{
|
{
|
||||||
int trailerCount = 0;
|
allExtras ??= item.GetExtras().ToArray();
|
||||||
if (allExtras == null)
|
dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
|
||||||
{
|
|
||||||
allExtras = item.GetExtras().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer);
|
|
||||||
|
|
||||||
if (item is IHasTrailers hasTrailers)
|
if (item is IHasTrailers hasTrailers)
|
||||||
{
|
{
|
||||||
trailerCount += hasTrailers.GetTrailerCount();
|
dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.LocalTrailerCount = trailerCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add EpisodeInfo
|
// Add EpisodeInfo
|
||||||
|
@ -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>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
|
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
|
||||||
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
|
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
|
||||||
@ -32,11 +37,11 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
|
||||||
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
||||||
|
<PackageReference Include="Mono.Nat" Version="2.0.1" />
|
||||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
<PackageReference Include="sharpcompress" Version="0.25.0" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||||
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
|
||||||
private readonly object _createdRulesLock = new object();
|
private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
|
||||||
private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
|
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
private string _lastConfigIdentifier;
|
private string _configIdentifier;
|
||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
return new StringBuilder(32)
|
return new StringBuilder(32)
|
||||||
.Append(config.EnableUPnP).Append(Separator)
|
.Append(config.EnableUPnP).Append(Separator)
|
||||||
.Append(config.PublicPort).Append(Separator)
|
.Append(config.PublicPort).Append(Separator)
|
||||||
|
.Append(config.PublicHttpsPort).Append(Separator)
|
||||||
.Append(_appHost.HttpPort).Append(Separator)
|
.Append(_appHost.HttpPort).Append(Separator)
|
||||||
.Append(_appHost.HttpsPort).Append(Separator)
|
.Append(_appHost.HttpsPort).Append(Separator)
|
||||||
.Append(_appHost.ListenWithHttps).Append(Separator)
|
.Append(_appHost.ListenWithHttps).Append(Separator)
|
||||||
@ -69,7 +71,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
|
|
||||||
private void OnConfigurationUpdated(object sender, EventArgs e)
|
private void OnConfigurationUpdated(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
|
var oldConfigIdentifier = _configIdentifier;
|
||||||
|
_configIdentifier = GetConfigIdentifier();
|
||||||
|
|
||||||
|
if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
Start();
|
Start();
|
||||||
@ -93,21 +98,19 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("Starting NAT discovery");
|
_logger.LogInformation("Starting NAT discovery");
|
||||||
|
|
||||||
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
|
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
|
||||||
NatUtility.StartDiscovery();
|
NatUtility.StartDiscovery();
|
||||||
|
|
||||||
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
_timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
||||||
|
|
||||||
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
|
||||||
|
|
||||||
_lastConfigIdentifier = GetConfigIdentifier();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Stop()
|
private void Stop()
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Stopping NAT discovery");
|
_logger.LogInformation("Stopping NAT discovery");
|
||||||
|
|
||||||
NatUtility.StopDiscovery();
|
NatUtility.StopDiscovery();
|
||||||
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
|
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
|
||||||
@ -117,26 +120,16 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearCreatedRules(object state)
|
|
||||||
{
|
|
||||||
lock (_createdRulesLock)
|
|
||||||
{
|
|
||||||
_createdRules.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||||
{
|
{
|
||||||
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
|
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
|
private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var device = e.Device;
|
await CreateRules(e.Device).ConfigureAwait(false);
|
||||||
|
|
||||||
CreateRules(device);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -144,7 +137,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void CreateRules(INatDevice device)
|
private Task CreateRules(INatDevice device)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
@ -153,50 +146,46 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
|
|
||||||
// On some systems the device discovered event seems to fire repeatedly
|
// On some systems the device discovered event seems to fire repeatedly
|
||||||
// This check will help ensure we're not trying to port map the same device over and over
|
// This check will help ensure we're not trying to port map the same device over and over
|
||||||
var address = device.DeviceEndpoint;
|
if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
|
||||||
|
|
||||||
lock (_createdRulesLock)
|
|
||||||
{
|
{
|
||||||
if (!_createdRules.Contains(address))
|
return Task.CompletedTask;
|
||||||
{
|
|
||||||
_createdRules.Add(address);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
return Task.WhenAll(CreatePortMaps(device));
|
||||||
{
|
}
|
||||||
await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error creating http port map");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
private IEnumerable<Task> CreatePortMaps(INatDevice device)
|
||||||
|
{
|
||||||
|
yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
|
||||||
|
|
||||||
|
if (_appHost.EnableHttps)
|
||||||
{
|
{
|
||||||
await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
|
yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error creating https port map");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
|
private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
|
||||||
{
|
{
|
||||||
_logger.LogDebug(
|
_logger.LogDebug(
|
||||||
"Creating port map on local port {0} to public port {1} with device {2}",
|
"Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
|
||||||
privatePort,
|
privatePort,
|
||||||
publicPort,
|
publicPort,
|
||||||
device.DeviceEndpoint);
|
device.DeviceEndpoint);
|
||||||
|
|
||||||
return device.CreatePortMapAsync(
|
try
|
||||||
new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
|
{
|
||||||
|
var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
|
||||||
|
await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.",
|
||||||
|
privatePort,
|
||||||
|
publicPort,
|
||||||
|
device.DeviceEndpoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -16,46 +16,63 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly IConfiguration _appConfig;
|
private readonly IConfiguration _appConfig;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
private readonly IStartupOptions _startupOptions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StartupWizard"/> class.
|
/// Initializes a new instance of the <see cref="StartupWizard"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appHost">The application host.</param>
|
/// <param name="appHost">The application host.</param>
|
||||||
|
/// <param name="appConfig">The application configuration.</param>
|
||||||
/// <param name="config">The configuration manager.</param>
|
/// <param name="config">The configuration manager.</param>
|
||||||
public StartupWizard(IServerApplicationHost appHost, IConfiguration appConfig, IServerConfigurationManager config)
|
/// <param name="startupOptions">The application startup options.</param>
|
||||||
|
public StartupWizard(
|
||||||
|
IServerApplicationHost appHost,
|
||||||
|
IConfiguration appConfig,
|
||||||
|
IServerConfigurationManager config,
|
||||||
|
IStartupOptions startupOptions)
|
||||||
{
|
{
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_appConfig = appConfig;
|
_appConfig = appConfig;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_startupOptions = startupOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task RunAsync()
|
public Task RunAsync()
|
||||||
|
{
|
||||||
|
Run();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Run()
|
||||||
{
|
{
|
||||||
if (!_appHost.CanLaunchWebBrowser)
|
if (!_appHost.CanLaunchWebBrowser)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_appConfig.HostWebClient())
|
// Always launch the startup wizard if possible when it has not been completed
|
||||||
|
if (!_config.Configuration.IsStartupWizardCompleted && _appConfig.HostWebClient())
|
||||||
{
|
{
|
||||||
BrowserLauncher.OpenSwaggerPage(_appHost);
|
BrowserLauncher.OpenWebApp(_appHost);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (!_config.Configuration.IsStartupWizardCompleted)
|
|
||||||
|
// Do nothing if the web app is configured to not run automatically
|
||||||
|
if (!_config.Configuration.AutoRunWebApp || _startupOptions.NoAutoRunWebApp)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch the swagger page if the web client is not hosted, otherwise open the web client
|
||||||
|
if (_appConfig.HostWebClient())
|
||||||
{
|
{
|
||||||
BrowserLauncher.OpenWebApp(_appHost);
|
BrowserLauncher.OpenWebApp(_appHost);
|
||||||
}
|
}
|
||||||
else if (_config.Configuration.AutoRunWebApp)
|
else
|
||||||
{
|
{
|
||||||
var options = ((ApplicationHost)_appHost).StartupOptions;
|
BrowserLauncher.OpenSwaggerPage(_appHost);
|
||||||
|
|
||||||
if (!options.NoAutoRunWebApp)
|
|
||||||
{
|
|
||||||
BrowserLauncher.OpenWebApp(_appHost);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
@ -24,7 +25,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly Func<string> _defaultUserAgentFn;
|
private readonly IApplicationHost _appHost;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
|
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
|
||||||
@ -40,12 +41,12 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||||||
IApplicationPaths appPaths,
|
IApplicationPaths appPaths,
|
||||||
ILogger<HttpClientManager> logger,
|
ILogger<HttpClientManager> logger,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
Func<string> defaultUserAgentFn)
|
IApplicationHost appHost)
|
||||||
{
|
{
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths));
|
_appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths));
|
||||||
_defaultUserAgentFn = defaultUserAgentFn;
|
_appHost = appHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||||||
if (options.EnableDefaultUserAgent
|
if (options.EnableDefaultUserAgent
|
||||||
&& !request.Headers.TryGetValues(HeaderNames.UserAgent, out _))
|
&& !request.Headers.TryGetValues(HeaderNames.UserAgent, out _))
|
||||||
{
|
{
|
||||||
request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn());
|
request.Headers.Add(HeaderNames.UserAgent, _appHost.ApplicationUserAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (options.DecompressionMethod)
|
switch (options.DecompressionMethod)
|
||||||
|
@ -14,6 +14,7 @@ using Emby.Server.Implementations.Services;
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
@ -23,6 +24,7 @@ using MediaBrowser.Model.Services;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using ServiceStack.Text.Jsv;
|
using ServiceStack.Text.Jsv;
|
||||||
|
|
||||||
@ -48,6 +50,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
private readonly string _baseUrlPrefix;
|
private readonly string _baseUrlPrefix;
|
||||||
private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
|
private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
|
||||||
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
|
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
|
||||||
|
private readonly IHostEnvironment _hostEnvironment;
|
||||||
|
|
||||||
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
@ -61,7 +65,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
IXmlSerializer xmlSerializer,
|
IXmlSerializer xmlSerializer,
|
||||||
IHttpListener socketListener,
|
IHttpListener socketListener,
|
||||||
ILocalizationManager localizationManager,
|
ILocalizationManager localizationManager,
|
||||||
ServiceController serviceController)
|
ServiceController serviceController,
|
||||||
|
IHostEnvironment hostEnvironment)
|
||||||
{
|
{
|
||||||
_appHost = applicationHost;
|
_appHost = applicationHost;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -75,6 +80,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
ServiceController = serviceController;
|
ServiceController = serviceController;
|
||||||
|
|
||||||
_socketListener.WebSocketConnected = OnWebSocketConnected;
|
_socketListener.WebSocketConnected = OnWebSocketConnected;
|
||||||
|
_hostEnvironment = hostEnvironment;
|
||||||
|
|
||||||
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
|
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
|
||||||
|
|
||||||
@ -225,7 +231,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
switch (ex)
|
switch (ex)
|
||||||
{
|
{
|
||||||
case ArgumentException _: return 400;
|
case ArgumentException _: return 400;
|
||||||
case SecurityException _: return 401;
|
case AuthenticationException _: return 401;
|
||||||
|
case SecurityException _: return 403;
|
||||||
case DirectoryNotFoundException _:
|
case DirectoryNotFoundException _:
|
||||||
case FileNotFoundException _:
|
case FileNotFoundException _:
|
||||||
case ResourceNotFoundException _: return 404;
|
case ResourceNotFoundException _: return 404;
|
||||||
@ -234,55 +241,52 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace)
|
private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog)
|
||||||
{
|
{
|
||||||
try
|
bool ignoreStackTrace =
|
||||||
|
ex is SocketException
|
||||||
|
|| ex is IOException
|
||||||
|
|| ex is OperationCanceledException
|
||||||
|
|| ex is SecurityException
|
||||||
|
|| ex is AuthenticationException
|
||||||
|
|| ex is FileNotFoundException;
|
||||||
|
|
||||||
|
if (ignoreStackTrace)
|
||||||
{
|
{
|
||||||
ex = GetActualException(ex);
|
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
|
||||||
|
|
||||||
if (logExceptionStackTrace)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error processing request");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogError("Error processing request: {Message}", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpRes = httpReq.Response;
|
|
||||||
|
|
||||||
if (httpRes.HasStarted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusCode = GetStatusCode(ex);
|
|
||||||
httpRes.StatusCode = statusCode;
|
|
||||||
|
|
||||||
var errContent = NormalizeExceptionMessage(ex.Message);
|
|
||||||
httpRes.ContentType = "text/plain";
|
|
||||||
httpRes.ContentLength = errContent.Length;
|
|
||||||
await httpRes.WriteAsync(errContent).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception errorEx)
|
else
|
||||||
{
|
{
|
||||||
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
|
_logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var httpRes = httpReq.Response;
|
||||||
|
|
||||||
|
if (httpRes.HasStarted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpRes.StatusCode = statusCode;
|
||||||
|
|
||||||
|
var errContent = NormalizeExceptionMessage(ex) ?? string.Empty;
|
||||||
|
httpRes.ContentType = "text/plain";
|
||||||
|
httpRes.ContentLength = errContent.Length;
|
||||||
|
await httpRes.WriteAsync(errContent).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string NormalizeExceptionMessage(string msg)
|
private string NormalizeExceptionMessage(Exception ex)
|
||||||
{
|
{
|
||||||
if (msg == null)
|
// Do not expose the exception message for AuthenticationException
|
||||||
|
if (ex is AuthenticationException)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip any information we don't want to reveal
|
// Strip any information we don't want to reveal
|
||||||
|
return ex.Message
|
||||||
msg = msg.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase);
|
?.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||||
msg = msg.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
|
.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -455,7 +459,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
var stopWatch = new Stopwatch();
|
var stopWatch = new Stopwatch();
|
||||||
stopWatch.Start();
|
stopWatch.Start();
|
||||||
var httpRes = httpReq.Response;
|
var httpRes = httpReq.Response;
|
||||||
string urlToLog = null;
|
string urlToLog = GetUrlToLog(urlString);
|
||||||
string remoteIp = httpReq.RemoteIp;
|
string remoteIp = httpReq.RemoteIp;
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -501,8 +505,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
urlToLog = GetUrlToLog(urlString);
|
|
||||||
|
|
||||||
if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|
if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|
||||||
@ -534,22 +536,35 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await ErrorHandler(new FileNotFoundException(), httpReq, false).ConfigureAwait(false);
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException)
|
catch (Exception requestEx)
|
||||||
{
|
{
|
||||||
await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
|
try
|
||||||
}
|
{
|
||||||
catch (SecurityException ex)
|
var requestInnerEx = GetActualException(requestEx);
|
||||||
{
|
var statusCode = GetStatusCode(requestInnerEx);
|
||||||
await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
await ErrorHandler(ex, httpReq, logException).ConfigureAwait(false);
|
// Do not handle 500 server exceptions manually when in development mode
|
||||||
|
// The framework-defined development exception page will be returned instead
|
||||||
|
if (statusCode == 500 && _hostEnvironment.IsDevelopment())
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception handlerException)
|
||||||
|
{
|
||||||
|
var aggregateEx = new AggregateException("Error while handling request exception", requestEx, handlerException);
|
||||||
|
_logger.LogError(aggregateEx, "Error while handling exception in response to {Url}", urlToLog);
|
||||||
|
|
||||||
|
if (_hostEnvironment.IsDevelopment())
|
||||||
|
{
|
||||||
|
throw aggregateEx;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -28,6 +28,12 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class HttpResultFactory : IHttpResultFactory
|
public class HttpResultFactory : IHttpResultFactory
|
||||||
{
|
{
|
||||||
|
// Last-Modified and If-Modified-Since must follow strict date format,
|
||||||
|
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since
|
||||||
|
private const string HttpDateFormat = "ddd, dd MMM yyyy HH:mm:ss \"GMT\"";
|
||||||
|
// We specifically use en-US culture because both day of week and month names require it
|
||||||
|
private static readonly CultureInfo _enUSculture = new CultureInfo("en-US", false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger.
|
/// The logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -420,7 +426,11 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
|
|
||||||
if (!noCache)
|
if (!noCache)
|
||||||
{
|
{
|
||||||
DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
|
if (!DateTime.TryParseExact(requestContext.Headers[HeaderNames.IfModifiedSince], HttpDateFormat, _enUSculture, DateTimeStyles.AssumeUniversal, out var ifModifiedSinceHeader))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Failed to parse If-Modified-Since header date: {0}", requestContext.Headers[HeaderNames.IfModifiedSince]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
|
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
|
||||||
{
|
{
|
||||||
@ -629,7 +639,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
|
|
||||||
if (lastModifiedDate.HasValue)
|
if (lastModifiedDate.HasValue)
|
||||||
{
|
{
|
||||||
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture);
|
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToUniversalTime().ToString(HttpDateFormat, _enUSculture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security.Authentication;
|
||||||
using Emby.Server.Implementations.SocketSharp;
|
using Emby.Server.Implementations.SocketSharp;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
@ -68,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
|
|
||||||
if (user == null && auth.UserId != Guid.Empty)
|
if (user == null && auth.UserId != Guid.Empty)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User with Id " + auth.UserId + " not found");
|
throw new AuthenticationException("User with Id " + auth.UserId + " not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
@ -108,18 +109,12 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
if (user.Policy.IsDisabled)
|
if (user.Policy.IsDisabled)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User account has been disabled.")
|
throw new SecurityException("User account has been disabled.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
||||||
{
|
{
|
||||||
throw new SecurityException("User account has been disabled.")
|
throw new SecurityException("User account has been disabled.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.IsAdministrator
|
if (!user.Policy.IsAdministrator
|
||||||
@ -128,10 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
|
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
|
||||||
|
|
||||||
throw new SecurityException("This user account is not allowed access at this time.")
|
throw new SecurityException("This user account is not allowed access at this time.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.ParentalControl
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,10 +182,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
if (user == null || !user.Policy.IsAdministrator)
|
if (user == null || !user.Policy.IsAdministrator)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User does not have admin access.")
|
throw new SecurityException("User does not have admin access.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,10 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
if (user == null || !user.Policy.EnableContentDeletion)
|
if (user == null || !user.Policy.EnableContentDeletion)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User does not have delete access.")
|
throw new SecurityException("User does not have delete access.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,10 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
if (user == null || !user.Policy.EnableContentDownloading)
|
if (user == null || !user.Policy.EnableContentDownloading)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User does not have download access.")
|
throw new SecurityException("User does not have download access.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,14 +213,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
throw new SecurityException("Access token is required.");
|
throw new AuthenticationException("Access token is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = GetTokenInfo(request);
|
var info = GetTokenInfo(request);
|
||||||
|
|
||||||
if (info == null)
|
if (info == null)
|
||||||
{
|
{
|
||||||
throw new SecurityException("Access token is invalid or expired.");
|
throw new AuthenticationException("Access token is invalid or expired.");
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (!string.IsNullOrEmpty(info.UserId))
|
//if (!string.IsNullOrEmpty(info.UserId))
|
||||||
|
@ -17,6 +17,11 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
public class LibraryMonitor : ILibraryMonitor
|
public class LibraryMonitor : ILibraryMonitor
|
||||||
{
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The file system watchers.
|
/// The file system watchers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -113,34 +118,23 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
|
_logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the logger.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The logger.</value>
|
|
||||||
private ILogger Logger { get; set; }
|
|
||||||
|
|
||||||
private ILibraryManager LibraryManager { get; set; }
|
|
||||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
|
/// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LibraryMonitor(
|
public LibraryMonitor(
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<LibraryMonitor> logger,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IServerConfigurationManager configurationManager,
|
IServerConfigurationManager configurationManager,
|
||||||
IFileSystem fileSystem)
|
IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
LibraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
Logger = loggerFactory.CreateLogger(GetType().Name);
|
_logger = logger;
|
||||||
ConfigurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +145,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = LibraryManager.GetLibraryOptions(item);
|
var options = _libraryManager.GetLibraryOptions(item);
|
||||||
|
|
||||||
if (options != null)
|
if (options != null)
|
||||||
{
|
{
|
||||||
@ -163,12 +157,12 @@ namespace Emby.Server.Implementations.IO
|
|||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
LibraryManager.ItemAdded += OnLibraryManagerItemAdded;
|
_libraryManager.ItemAdded += OnLibraryManagerItemAdded;
|
||||||
LibraryManager.ItemRemoved += OnLibraryManagerItemRemoved;
|
_libraryManager.ItemRemoved += OnLibraryManagerItemRemoved;
|
||||||
|
|
||||||
var pathsToWatch = new List<string>();
|
var pathsToWatch = new List<string>();
|
||||||
|
|
||||||
var paths = LibraryManager
|
var paths = _libraryManager
|
||||||
.RootFolder
|
.RootFolder
|
||||||
.Children
|
.Children
|
||||||
.Where(IsLibraryMonitorEnabled)
|
.Where(IsLibraryMonitorEnabled)
|
||||||
@ -261,7 +255,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
if (!Directory.Exists(path))
|
if (!Directory.Exists(path))
|
||||||
{
|
{
|
||||||
// Seeing a crash in the mono runtime due to an exception being thrown on a different thread
|
// Seeing a crash in the mono runtime due to an exception being thrown on a different thread
|
||||||
Logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path);
|
_logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +291,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
if (_fileSystemWatchers.TryAdd(path, newWatcher))
|
if (_fileSystemWatchers.TryAdd(path, newWatcher))
|
||||||
{
|
{
|
||||||
newWatcher.EnableRaisingEvents = true;
|
newWatcher.EnableRaisingEvents = true;
|
||||||
Logger.LogInformation("Watching directory " + path);
|
_logger.LogInformation("Watching directory " + path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -307,7 +301,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error watching path: {path}", path);
|
_logger.LogError(ex, "Error watching path: {path}", path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -333,7 +327,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
using (watcher)
|
using (watcher)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path);
|
_logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path);
|
||||||
|
|
||||||
watcher.Created -= OnWatcherChanged;
|
watcher.Created -= OnWatcherChanged;
|
||||||
watcher.Deleted -= OnWatcherChanged;
|
watcher.Deleted -= OnWatcherChanged;
|
||||||
@ -372,7 +366,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
var ex = e.GetException();
|
var ex = e.GetException();
|
||||||
var dw = (FileSystemWatcher)sender;
|
var dw = (FileSystemWatcher)sender;
|
||||||
|
|
||||||
Logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path);
|
_logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path);
|
||||||
|
|
||||||
DisposeWatcher(dw, true);
|
DisposeWatcher(dw, true);
|
||||||
}
|
}
|
||||||
@ -390,7 +384,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath);
|
_logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,13 +410,13 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
if (_fileSystem.AreEqual(i, path))
|
if (_fileSystem.AreEqual(i, path))
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Ignoring change to {Path}", path);
|
_logger.LogDebug("Ignoring change to {Path}", path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_fileSystem.ContainsSubPath(i, path))
|
if (_fileSystem.ContainsSubPath(i, path))
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Ignoring change to {Path}", path);
|
_logger.LogDebug("Ignoring change to {Path}", path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +424,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
var parent = Path.GetDirectoryName(i);
|
var parent = Path.GetDirectoryName(i);
|
||||||
if (!string.IsNullOrEmpty(parent) && _fileSystem.AreEqual(parent, path))
|
if (!string.IsNullOrEmpty(parent) && _fileSystem.AreEqual(parent, path))
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Ignoring change to {Path}", path);
|
_logger.LogDebug("Ignoring change to {Path}", path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,7 +479,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newRefresher = new FileRefresher(path, ConfigurationManager, LibraryManager, Logger);
|
var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger);
|
||||||
newRefresher.Completed += NewRefresher_Completed;
|
newRefresher.Completed += NewRefresher_Completed;
|
||||||
_activeRefreshers.Add(newRefresher);
|
_activeRefreshers.Add(newRefresher);
|
||||||
}
|
}
|
||||||
@ -502,8 +496,8 @@ namespace Emby.Server.Implementations.IO
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
LibraryManager.ItemAdded -= OnLibraryManagerItemAdded;
|
_libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
|
||||||
LibraryManager.ItemRemoved -= OnLibraryManagerItemRemoved;
|
_libraryManager.ItemRemoved -= OnLibraryManagerItemRemoved;
|
||||||
|
|
||||||
foreach (var watcher in _fileSystemWatchers.Values.ToList())
|
foreach (var watcher in _fileSystemWatchers.Values.ToList())
|
||||||
{
|
{
|
||||||
|
@ -3,33 +3,38 @@ namespace Emby.Server.Implementations
|
|||||||
public interface IStartupOptions
|
public interface IStartupOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// --ffmpeg
|
/// Gets the value of the --ffmpeg command line option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string FFmpegPath { get; }
|
string FFmpegPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// --service
|
/// Gets the value of the --service command line option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsService { get; }
|
bool IsService { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// --noautorunwebapp
|
/// Gets the value of the --noautorunwebapp command line option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool NoAutoRunWebApp { get; }
|
bool NoAutoRunWebApp { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// --package-name
|
/// Gets the value of the --package-name command line option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string PackageName { get; }
|
string PackageName { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// --restartpath
|
/// Gets the value of the --restartpath command line option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string RestartPath { get; }
|
string RestartPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// --restartargs
|
/// Gets the value of the --restartargs command line option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string RestartArgs { get; }
|
string RestartArgs { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the --plugin-manifest-url command line option.
|
||||||
|
/// </summary>
|
||||||
|
string PluginManifestUrl { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (resolvedUser == null)
|
if (resolvedUser == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(resolvedUser));
|
throw new AuthenticationException($"Specified user does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
@ -54,9 +54,29 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class LibraryManager : ILibraryManager
|
public class LibraryManager : ILibraryManager
|
||||||
{
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly ITaskManager _taskManager;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IUserDataManager _userDataRepository;
|
||||||
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
|
private readonly Lazy<ILibraryMonitor> _libraryMonitorFactory;
|
||||||
|
private readonly Lazy<IProviderManager> _providerManagerFactory;
|
||||||
|
private readonly Lazy<IUserViewManager> _userviewManagerFactory;
|
||||||
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IItemRepository _itemRepository;
|
||||||
|
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
|
||||||
|
|
||||||
private NamingOptions _namingOptions;
|
private NamingOptions _namingOptions;
|
||||||
private string[] _videoFileExtensions;
|
private string[] _videoFileExtensions;
|
||||||
|
|
||||||
|
private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
|
||||||
|
|
||||||
|
private IProviderManager ProviderManager => _providerManagerFactory.Value;
|
||||||
|
|
||||||
|
private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the postscan tasks.
|
/// Gets or sets the postscan tasks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -89,12 +109,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <value>The comparers.</value>
|
/// <value>The comparers.</value>
|
||||||
private IBaseItemComparer[] Comparers { get; set; }
|
private IBaseItemComparer[] Comparers { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the active item repository
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The item repository.</value>
|
|
||||||
public IItemRepository ItemRepository { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when [item added].
|
/// Occurs when [item added].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -110,90 +124,47 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
|
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _logger
|
|
||||||
/// </summary>
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _task manager
|
|
||||||
/// </summary>
|
|
||||||
private readonly ITaskManager _taskManager;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _user manager
|
|
||||||
/// </summary>
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _user data repository
|
|
||||||
/// </summary>
|
|
||||||
private readonly IUserDataManager _userDataRepository;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the configuration manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The configuration manager.</value>
|
|
||||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
|
||||||
|
|
||||||
private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
|
|
||||||
private readonly Func<IProviderManager> _providerManagerFactory;
|
|
||||||
private readonly Func<IUserViewManager> _userviewManager;
|
|
||||||
public bool IsScanRunning { get; private set; }
|
public bool IsScanRunning { get; private set; }
|
||||||
|
|
||||||
private IServerApplicationHost _appHost;
|
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _library items cache
|
|
||||||
/// </summary>
|
|
||||||
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the library items cache.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The library items cache.</value>
|
|
||||||
private ConcurrentDictionary<Guid, BaseItem> LibraryItemsCache => _libraryItemsCache;
|
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appHost">The application host</param>
|
/// <param name="appHost">The application host</param>
|
||||||
/// <param name="loggerFactory">The logger factory.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="taskManager">The task manager.</param>
|
/// <param name="taskManager">The task manager.</param>
|
||||||
/// <param name="userManager">The user manager.</param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
/// <param name="configurationManager">The configuration manager.</param>
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
/// <param name="userDataRepository">The user data repository.</param>
|
/// <param name="userDataRepository">The user data repository.</param>
|
||||||
public LibraryManager(
|
public LibraryManager(
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<LibraryManager> logger,
|
||||||
ITaskManager taskManager,
|
ITaskManager taskManager,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
IServerConfigurationManager configurationManager,
|
IServerConfigurationManager configurationManager,
|
||||||
IUserDataManager userDataRepository,
|
IUserDataManager userDataRepository,
|
||||||
Func<ILibraryMonitor> libraryMonitorFactory,
|
Lazy<ILibraryMonitor> libraryMonitorFactory,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
Func<IProviderManager> providerManagerFactory,
|
Lazy<IProviderManager> providerManagerFactory,
|
||||||
Func<IUserViewManager> userviewManager,
|
Lazy<IUserViewManager> userviewManagerFactory,
|
||||||
IMediaEncoder mediaEncoder)
|
IMediaEncoder mediaEncoder,
|
||||||
|
IItemRepository itemRepository)
|
||||||
{
|
{
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(LibraryManager));
|
_logger = logger;
|
||||||
_taskManager = taskManager;
|
_taskManager = taskManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
ConfigurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
_userDataRepository = userDataRepository;
|
_userDataRepository = userDataRepository;
|
||||||
_libraryMonitorFactory = libraryMonitorFactory;
|
_libraryMonitorFactory = libraryMonitorFactory;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_providerManagerFactory = providerManagerFactory;
|
_providerManagerFactory = providerManagerFactory;
|
||||||
_userviewManager = userviewManager;
|
_userviewManagerFactory = userviewManagerFactory;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
|
_itemRepository = itemRepository;
|
||||||
|
|
||||||
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
|
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
|
||||||
|
|
||||||
ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
||||||
|
|
||||||
RecordConfigurationValues(configurationManager.Configuration);
|
RecordConfigurationValues(configurationManager.Configuration);
|
||||||
}
|
}
|
||||||
@ -272,7 +243,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
|
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
|
||||||
private void ConfigurationUpdated(object sender, EventArgs e)
|
private void ConfigurationUpdated(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var config = ConfigurationManager.Configuration;
|
var config = _configurationManager.Configuration;
|
||||||
|
|
||||||
var wizardChanged = config.IsStartupWizardCompleted != _wizardCompleted;
|
var wizardChanged = config.IsStartupWizardCompleted != _wizardCompleted;
|
||||||
|
|
||||||
@ -306,7 +277,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
|
_libraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteItem(BaseItem item, DeleteOptions options)
|
public void DeleteItem(BaseItem item, DeleteOptions options)
|
||||||
@ -437,10 +408,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
item.SetParent(null);
|
item.SetParent(null);
|
||||||
|
|
||||||
ItemRepository.DeleteItem(item.Id, CancellationToken.None);
|
_itemRepository.DeleteItem(item.Id);
|
||||||
foreach (var child in children)
|
foreach (var child in children)
|
||||||
{
|
{
|
||||||
ItemRepository.DeleteItem(child.Id, CancellationToken.None);
|
_itemRepository.DeleteItem(child.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
|
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
|
||||||
@ -509,15 +480,15 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new ArgumentNullException(nameof(type));
|
throw new ArgumentNullException(nameof(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal))
|
if (key.StartsWith(_configurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// Try to normalize paths located underneath program-data in an attempt to make them more portable
|
// Try to normalize paths located underneath program-data in an attempt to make them more portable
|
||||||
key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length)
|
key = key.Substring(_configurationManager.ApplicationPaths.ProgramDataPath.Length)
|
||||||
.TrimStart(new[] { '/', '\\' })
|
.TrimStart(new[] { '/', '\\' })
|
||||||
.Replace("/", "\\");
|
.Replace("/", "\\");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
|
if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds)
|
||||||
{
|
{
|
||||||
key = key.ToLowerInvariant();
|
key = key.ToLowerInvariant();
|
||||||
}
|
}
|
||||||
@ -550,7 +521,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
collectionType = GetContentTypeOverride(fullPath, true);
|
collectionType = GetContentTypeOverride(fullPath, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
|
var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
|
||||||
{
|
{
|
||||||
Parent = parent,
|
Parent = parent,
|
||||||
Path = fullPath,
|
Path = fullPath,
|
||||||
@ -720,7 +691,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception>
|
/// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception>
|
||||||
public AggregateFolder CreateRootFolder()
|
public AggregateFolder CreateRootFolder()
|
||||||
{
|
{
|
||||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.RootFolderPath;
|
||||||
|
|
||||||
Directory.CreateDirectory(rootFolderPath);
|
Directory.CreateDirectory(rootFolderPath);
|
||||||
|
|
||||||
@ -734,7 +705,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add in the plug-in folders
|
// Add in the plug-in folders
|
||||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists");
|
var path = Path.Combine(_configurationManager.ApplicationPaths.DataPath, "playlists");
|
||||||
|
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
@ -786,7 +757,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (_userRootFolder == null)
|
if (_userRootFolder == null)
|
||||||
{
|
{
|
||||||
var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
|
|
||||||
_logger.LogDebug("Creating userRootPath at {path}", userRootPath);
|
_logger.LogDebug("Creating userRootPath at {path}", userRootPath);
|
||||||
Directory.CreateDirectory(userRootPath);
|
Directory.CreateDirectory(userRootPath);
|
||||||
@ -980,7 +951,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
where T : BaseItem, new()
|
where T : BaseItem, new()
|
||||||
{
|
{
|
||||||
var path = getPathFn(name);
|
var path = getPathFn(name);
|
||||||
var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
|
var forceCaseInsensitiveId = _configurationManager.Configuration.EnableNormalizedItemByNameIds;
|
||||||
return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
|
return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -994,7 +965,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
|
public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
|
||||||
{
|
{
|
||||||
// Ensure the location is available.
|
// Ensure the location is available.
|
||||||
Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
|
Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath);
|
||||||
|
|
||||||
return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
|
return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
|
||||||
}
|
}
|
||||||
@ -1031,7 +1002,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
IsScanRunning = true;
|
IsScanRunning = true;
|
||||||
_libraryMonitorFactory().Stop();
|
LibraryMonitor.Stop();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -1039,7 +1010,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_libraryMonitorFactory().Start();
|
LibraryMonitor.Start();
|
||||||
IsScanRunning = false;
|
IsScanRunning = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1148,7 +1119,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
progress.Report(percent * 100);
|
progress.Report(percent * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemRepository.UpdateInheritedValues(cancellationToken);
|
_itemRepository.UpdateInheritedValues(cancellationToken);
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
@ -1168,9 +1139,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
var topLibraryFolders = GetUserRootFolder().Children.ToList();
|
var topLibraryFolders = GetUserRootFolder().Children.ToList();
|
||||||
|
|
||||||
_logger.LogDebug("Getting refreshQueue");
|
_logger.LogDebug("Getting refreshQueue");
|
||||||
var refreshQueue = includeRefreshState ? _providerManagerFactory().GetRefreshQueue() : null;
|
var refreshQueue = includeRefreshState ? ProviderManager.GetRefreshQueue() : null;
|
||||||
|
|
||||||
return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath)
|
return _fileSystem.GetDirectoryPaths(_configurationManager.ApplicationPaths.DefaultUserViewsPath)
|
||||||
.Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue))
|
.Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
@ -1245,7 +1216,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
|
if (_libraryItemsCache.TryGetValue(id, out BaseItem item))
|
||||||
{
|
{
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -1276,7 +1247,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
AddUserToQuery(query, query.User, allowExternalContent);
|
AddUserToQuery(query, query.User, allowExternalContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ItemRepository.GetItemList(query);
|
return _itemRepository.GetItemList(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BaseItem> GetItemList(InternalItemsQuery query)
|
public List<BaseItem> GetItemList(InternalItemsQuery query)
|
||||||
@ -1300,7 +1271,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
AddUserToQuery(query, query.User);
|
AddUserToQuery(query, query.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ItemRepository.GetCount(query);
|
return _itemRepository.GetCount(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
|
public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
|
||||||
@ -1315,7 +1286,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ItemRepository.GetItemList(query);
|
return _itemRepository.GetItemList(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
|
public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
|
||||||
@ -1327,12 +1298,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (query.EnableTotalRecordCount)
|
if (query.EnableTotalRecordCount)
|
||||||
{
|
{
|
||||||
return ItemRepository.GetItems(query);
|
return _itemRepository.GetItems(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<BaseItem>
|
return new QueryResult<BaseItem>
|
||||||
{
|
{
|
||||||
Items = ItemRepository.GetItemList(query).ToArray()
|
Items = _itemRepository.GetItemList(query).ToArray()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1343,7 +1314,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
AddUserToQuery(query, query.User);
|
AddUserToQuery(query, query.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ItemRepository.GetItemIdsList(query);
|
return _itemRepository.GetItemIdsList(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
|
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
|
||||||
@ -1354,7 +1325,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTopParentOrAncestorIds(query);
|
SetTopParentOrAncestorIds(query);
|
||||||
return ItemRepository.GetStudios(query);
|
return _itemRepository.GetStudios(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
|
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
|
||||||
@ -1365,7 +1336,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTopParentOrAncestorIds(query);
|
SetTopParentOrAncestorIds(query);
|
||||||
return ItemRepository.GetGenres(query);
|
return _itemRepository.GetGenres(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
||||||
@ -1376,7 +1347,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTopParentOrAncestorIds(query);
|
SetTopParentOrAncestorIds(query);
|
||||||
return ItemRepository.GetMusicGenres(query);
|
return _itemRepository.GetMusicGenres(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
||||||
@ -1387,7 +1358,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTopParentOrAncestorIds(query);
|
SetTopParentOrAncestorIds(query);
|
||||||
return ItemRepository.GetAllArtists(query);
|
return _itemRepository.GetAllArtists(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
|
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
|
||||||
@ -1398,7 +1369,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTopParentOrAncestorIds(query);
|
SetTopParentOrAncestorIds(query);
|
||||||
return ItemRepository.GetArtists(query);
|
return _itemRepository.GetArtists(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetTopParentOrAncestorIds(InternalItemsQuery query)
|
private void SetTopParentOrAncestorIds(InternalItemsQuery query)
|
||||||
@ -1439,7 +1410,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTopParentOrAncestorIds(query);
|
SetTopParentOrAncestorIds(query);
|
||||||
return ItemRepository.GetAlbumArtists(query);
|
return _itemRepository.GetAlbumArtists(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
|
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
|
||||||
@ -1460,10 +1431,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (query.EnableTotalRecordCount)
|
if (query.EnableTotalRecordCount)
|
||||||
{
|
{
|
||||||
return ItemRepository.GetItems(query);
|
return _itemRepository.GetItems(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = ItemRepository.GetItemList(query);
|
var list = _itemRepository.GetItemList(query);
|
||||||
|
|
||||||
return new QueryResult<BaseItem>
|
return new QueryResult<BaseItem>
|
||||||
{
|
{
|
||||||
@ -1509,7 +1480,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) &&
|
string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) &&
|
||||||
query.ItemIds.Length == 0)
|
query.ItemIds.Length == 0)
|
||||||
{
|
{
|
||||||
var userViews = _userviewManager().GetUserViews(new UserViewQuery
|
var userViews = UserViewManager.GetUserViews(new UserViewQuery
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
IncludeHidden = true,
|
IncludeHidden = true,
|
||||||
@ -1809,7 +1780,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
// Don't iterate multiple times
|
// Don't iterate multiple times
|
||||||
var itemsList = items.ToList();
|
var itemsList = items.ToList();
|
||||||
|
|
||||||
ItemRepository.SaveItems(itemsList, cancellationToken);
|
_itemRepository.SaveItems(itemsList, cancellationToken);
|
||||||
|
|
||||||
foreach (var item in itemsList)
|
foreach (var item in itemsList)
|
||||||
{
|
{
|
||||||
@ -1846,7 +1817,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public void UpdateImages(BaseItem item)
|
public void UpdateImages(BaseItem item)
|
||||||
{
|
{
|
||||||
ItemRepository.SaveImages(item);
|
_itemRepository.SaveImages(item);
|
||||||
|
|
||||||
RegisterItem(item);
|
RegisterItem(item);
|
||||||
}
|
}
|
||||||
@ -1863,7 +1834,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (item.IsFileProtocol)
|
if (item.IsFileProtocol)
|
||||||
{
|
{
|
||||||
_providerManagerFactory().SaveMetadata(item, updateReason);
|
ProviderManager.SaveMetadata(item, updateReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.DateLastSaved = DateTime.UtcNow;
|
item.DateLastSaved = DateTime.UtcNow;
|
||||||
@ -1871,7 +1842,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
RegisterItem(item);
|
RegisterItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemRepository.SaveItems(itemsList, cancellationToken);
|
_itemRepository.SaveItems(itemsList, cancellationToken);
|
||||||
|
|
||||||
if (ItemUpdated != null)
|
if (ItemUpdated != null)
|
||||||
{
|
{
|
||||||
@ -1947,7 +1918,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <returns>BaseItem.</returns>
|
/// <returns>BaseItem.</returns>
|
||||||
public BaseItem RetrieveItem(Guid id)
|
public BaseItem RetrieveItem(Guid id)
|
||||||
{
|
{
|
||||||
return ItemRepository.RetrieveItem(id);
|
return _itemRepository.RetrieveItem(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Folder> GetCollectionFolders(BaseItem item)
|
public List<Folder> GetCollectionFolders(BaseItem item)
|
||||||
@ -2066,7 +2037,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
private string GetContentTypeOverride(string path, bool inherit)
|
private string GetContentTypeOverride(string path, bool inherit)
|
||||||
{
|
{
|
||||||
var nameValuePair = ConfigurationManager.Configuration.ContentTypes
|
var nameValuePair = _configurationManager.Configuration.ContentTypes
|
||||||
.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
|
.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
|
||||||
|| (inherit && !string.IsNullOrEmpty(i.Name)
|
|| (inherit && !string.IsNullOrEmpty(i.Name)
|
||||||
&& _fileSystem.ContainsSubPath(i.Name, path)));
|
&& _fileSystem.ContainsSubPath(i.Name, path)));
|
||||||
@ -2115,7 +2086,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
string sortName)
|
string sortName)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(
|
var path = Path.Combine(
|
||||||
ConfigurationManager.ApplicationPaths.InternalMetadataPath,
|
_configurationManager.ApplicationPaths.InternalMetadataPath,
|
||||||
"views",
|
"views",
|
||||||
_fileSystem.GetValidFilename(viewType));
|
_fileSystem.GetValidFilename(viewType));
|
||||||
|
|
||||||
@ -2147,7 +2118,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
if (refresh)
|
if (refresh)
|
||||||
{
|
{
|
||||||
item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
|
item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
|
||||||
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
ProviderManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
@ -2165,7 +2136,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var id = GetNewItemId(idValues, typeof(UserView));
|
var id = GetNewItemId(idValues, typeof(UserView));
|
||||||
|
|
||||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
|
var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
var item = GetItemById(id) as UserView;
|
var item = GetItemById(id) as UserView;
|
||||||
|
|
||||||
@ -2202,7 +2173,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (refresh)
|
if (refresh)
|
||||||
{
|
{
|
||||||
_providerManagerFactory().QueueRefresh(
|
ProviderManager.QueueRefresh(
|
||||||
item.Id,
|
item.Id,
|
||||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
@ -2269,7 +2240,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (refresh)
|
if (refresh)
|
||||||
{
|
{
|
||||||
_providerManagerFactory().QueueRefresh(
|
ProviderManager.QueueRefresh(
|
||||||
item.Id,
|
item.Id,
|
||||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
@ -2303,7 +2274,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var id = GetNewItemId(idValues, typeof(UserView));
|
var id = GetNewItemId(idValues, typeof(UserView));
|
||||||
|
|
||||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
|
var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
var item = GetItemById(id) as UserView;
|
var item = GetItemById(id) as UserView;
|
||||||
|
|
||||||
@ -2346,7 +2317,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (refresh)
|
if (refresh)
|
||||||
{
|
{
|
||||||
_providerManagerFactory().QueueRefresh(
|
ProviderManager.QueueRefresh(
|
||||||
item.Id,
|
item.Id,
|
||||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
@ -2364,7 +2335,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
string videoPath,
|
string videoPath,
|
||||||
string[] files)
|
string[] files)
|
||||||
{
|
{
|
||||||
new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
|
new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -2609,14 +2580,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}).OrderBy(i => i.Path);
|
}).OrderBy(i => i.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" };
|
|
||||||
|
|
||||||
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
var namingOptions = GetNamingOptions();
|
var namingOptions = GetNamingOptions();
|
||||||
|
|
||||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
||||||
.Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
.Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
|
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@ -2677,8 +2646,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadataPath = ConfigurationManager.Configuration.MetadataPath;
|
var metadataPath = _configurationManager.Configuration.MetadataPath;
|
||||||
var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath;
|
var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
|
if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
|
||||||
{
|
{
|
||||||
@ -2689,7 +2658,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
|
foreach (var map in _configurationManager.Configuration.PathSubstitutions)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(map.From))
|
if (!string.IsNullOrWhiteSpace(map.From))
|
||||||
{
|
{
|
||||||
@ -2758,7 +2727,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
|
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
|
||||||
{
|
{
|
||||||
return ItemRepository.GetPeople(query);
|
return _itemRepository.GetPeople(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PersonInfo> GetPeople(BaseItem item)
|
public List<PersonInfo> GetPeople(BaseItem item)
|
||||||
@ -2781,7 +2750,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public List<Person> GetPeopleItems(InternalPeopleQuery query)
|
public List<Person> GetPeopleItems(InternalPeopleQuery query)
|
||||||
{
|
{
|
||||||
return ItemRepository.GetPeopleNames(query).Select(i =>
|
return _itemRepository.GetPeopleNames(query).Select(i =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -2798,7 +2767,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public List<string> GetPeopleNames(InternalPeopleQuery query)
|
public List<string> GetPeopleNames(InternalPeopleQuery query)
|
||||||
{
|
{
|
||||||
return ItemRepository.GetPeopleNames(query);
|
return _itemRepository.GetPeopleNames(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
|
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
|
||||||
@ -2808,7 +2777,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemRepository.UpdatePeople(item.Id, people);
|
_itemRepository.UpdatePeople(item.Id, people);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
|
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
|
||||||
@ -2819,7 +2788,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
_logger.LogDebug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url);
|
_logger.LogDebug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url);
|
||||||
|
|
||||||
await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
|
await ProviderManager.SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
|
item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
|
||||||
|
|
||||||
@ -2852,7 +2821,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
name = _fileSystem.GetValidFilename(name);
|
name = _fileSystem.GetValidFilename(name);
|
||||||
|
|
||||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
|
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||||
while (Directory.Exists(virtualFolderPath))
|
while (Directory.Exists(virtualFolderPath))
|
||||||
@ -2871,7 +2840,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_libraryMonitorFactory().Stop();
|
LibraryMonitor.Stop();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -2906,7 +2875,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
// Need to add a delay here or directory watchers may still pick up the changes
|
// Need to add a delay here or directory watchers may still pick up the changes
|
||||||
await Task.Delay(1000).ConfigureAwait(false);
|
await Task.Delay(1000).ConfigureAwait(false);
|
||||||
_libraryMonitorFactory().Start();
|
LibraryMonitor.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2966,7 +2935,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new FileNotFoundException("The network path does not exist.");
|
throw new FileNotFoundException("The network path does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
||||||
|
|
||||||
var shortcutFilename = Path.GetFileNameWithoutExtension(path);
|
var shortcutFilename = Path.GetFileNameWithoutExtension(path);
|
||||||
@ -3009,7 +2978,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new FileNotFoundException("The network path does not exist.");
|
throw new FileNotFoundException("The network path does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
||||||
|
|
||||||
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
|
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
|
||||||
@ -3062,7 +3031,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new ArgumentNullException(nameof(name));
|
throw new ArgumentNullException(nameof(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
|
|
||||||
var path = Path.Combine(rootFolderPath, name);
|
var path = Path.Combine(rootFolderPath, name);
|
||||||
|
|
||||||
@ -3071,7 +3040,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new FileNotFoundException("The media folder does not exist");
|
throw new FileNotFoundException("The media folder does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
_libraryMonitorFactory().Stop();
|
LibraryMonitor.Stop();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -3091,7 +3060,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
// Need to add a delay here or directory watchers may still pick up the changes
|
// Need to add a delay here or directory watchers may still pick up the changes
|
||||||
await Task.Delay(1000).ConfigureAwait(false);
|
await Task.Delay(1000).ConfigureAwait(false);
|
||||||
_libraryMonitorFactory().Start();
|
LibraryMonitor.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3105,7 +3074,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var removeList = new List<NameValuePair>();
|
var removeList = new List<NameValuePair>();
|
||||||
|
|
||||||
foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
|
foreach (var contentType in _configurationManager.Configuration.ContentTypes)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(contentType.Name))
|
if (string.IsNullOrWhiteSpace(contentType.Name))
|
||||||
{
|
{
|
||||||
@ -3120,11 +3089,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (removeList.Count > 0)
|
if (removeList.Count > 0)
|
||||||
{
|
{
|
||||||
ConfigurationManager.Configuration.ContentTypes = ConfigurationManager.Configuration.ContentTypes
|
_configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes
|
||||||
.Except(removeList)
|
.Except(removeList)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
ConfigurationManager.SaveConfiguration();
|
_configurationManager.SaveConfiguration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3135,7 +3104,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new ArgumentNullException(nameof(mediaPath));
|
throw new ArgumentNullException(nameof(mediaPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
||||||
|
|
||||||
if (!Directory.Exists(virtualFolderPath))
|
if (!Directory.Exists(virtualFolderPath))
|
||||||
|
@ -33,13 +33,13 @@ namespace Emby.Server.Implementations.Library
|
|||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
private IMediaSourceProvider[] _providers;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly Func<IMediaEncoder> _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private ILocalizationManager _localizationManager;
|
private readonly ILocalizationManager _localizationManager;
|
||||||
private IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
|
||||||
|
private IMediaSourceProvider[] _providers;
|
||||||
|
|
||||||
public MediaSourceManager(
|
public MediaSourceManager(
|
||||||
IItemRepository itemRepo,
|
IItemRepository itemRepo,
|
||||||
@ -47,16 +47,16 @@ namespace Emby.Server.Implementations.Library
|
|||||||
ILocalizationManager localizationManager,
|
ILocalizationManager localizationManager,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<MediaSourceManager> logger,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
Func<IMediaEncoder> mediaEncoder)
|
IMediaEncoder mediaEncoder)
|
||||||
{
|
{
|
||||||
_itemRepo = itemRepo;
|
_itemRepo = itemRepo;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(MediaSourceManager));
|
_logger = logger;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
@ -496,7 +496,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
// hack - these two values were taken from LiveTVMediaSourceProvider
|
// hack - these two values were taken from LiveTVMediaSourceProvider
|
||||||
string cacheKey = request.OpenToken;
|
string cacheKey = request.OpenToken;
|
||||||
|
|
||||||
await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths)
|
await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _appPaths)
|
||||||
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
|
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -621,7 +621,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (liveStreamInfo is IDirectStreamProvider)
|
if (liveStreamInfo is IDirectStreamProvider)
|
||||||
{
|
{
|
||||||
var info = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest
|
var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
||||||
{
|
{
|
||||||
MediaSource = mediaSource,
|
MediaSource = mediaSource,
|
||||||
ExtractChapters = false,
|
ExtractChapters = false,
|
||||||
@ -674,7 +674,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
mediaSource.AnalyzeDurationMs = 3000;
|
mediaSource.AnalyzeDurationMs = 3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaInfo = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest
|
mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
||||||
{
|
{
|
||||||
MediaSource = mediaSource,
|
MediaSource = mediaSource,
|
||||||
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
||||||
|
@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
// for imdbid we also accept pattern matching
|
// for imdbid we also accept pattern matching
|
||||||
if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var m = Regex.Match(str, "tt\\d{7}", RegexOptions.IgnoreCase);
|
var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
|
||||||
return m.Success ? m.Value : null;
|
return m.Success ? m.Value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,16 +17,15 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
public class SearchEngine : ISearchEngine
|
public class SearchEngine : ISearchEngine
|
||||||
{
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public SearchEngine(ILoggerFactory loggerFactory, ILibraryManager libraryManager, IUserManager userManager)
|
public SearchEngine(ILogger<SearchEngine> logger, ILibraryManager libraryManager, IUserManager userManager)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
|
||||||
_logger = loggerFactory.CreateLogger("SearchEngine");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
|
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
|
||||||
|
@ -28,25 +28,24 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IUserDataRepository _repository;
|
||||||
|
|
||||||
private Func<IUserManager> _userManager;
|
public UserDataManager(
|
||||||
|
ILogger<UserDataManager> logger,
|
||||||
public UserDataManager(ILoggerFactory loggerFactory, IServerConfigurationManager config, Func<IUserManager> userManager)
|
IServerConfigurationManager config,
|
||||||
|
IUserManager userManager,
|
||||||
|
IUserDataRepository repository)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
_logger = loggerFactory.CreateLogger(GetType().Name);
|
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the repository.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The repository.</value>
|
|
||||||
public IUserDataRepository Repository { get; set; }
|
|
||||||
|
|
||||||
public void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
|
public void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var user = _userManager().GetUserById(userId);
|
var user = _userManager.GetUserById(userId);
|
||||||
|
|
||||||
SaveUserData(user, item, userData, reason, cancellationToken);
|
SaveUserData(user, item, userData, reason, cancellationToken);
|
||||||
}
|
}
|
||||||
@ -71,7 +70,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
foreach (var key in keys)
|
foreach (var key in keys)
|
||||||
{
|
{
|
||||||
Repository.SaveUserData(userId, key, userData, cancellationToken);
|
_repository.SaveUserData(userId, key, userData, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
var cacheKey = GetCacheKey(userId, item.Id);
|
var cacheKey = GetCacheKey(userId, item.Id);
|
||||||
@ -96,9 +95,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
|
public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var user = _userManager().GetUserById(userId);
|
var user = _userManager.GetUserById(userId);
|
||||||
|
|
||||||
Repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
|
_repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -108,14 +107,14 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public List<UserItemData> GetAllUserData(Guid userId)
|
public List<UserItemData> GetAllUserData(Guid userId)
|
||||||
{
|
{
|
||||||
var user = _userManager().GetUserById(userId);
|
var user = _userManager.GetUserById(userId);
|
||||||
|
|
||||||
return Repository.GetAllUserData(user.InternalId);
|
return _repository.GetAllUserData(user.InternalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
|
public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
|
||||||
{
|
{
|
||||||
var user = _userManager().GetUserById(userId);
|
var user = _userManager.GetUserById(userId);
|
||||||
|
|
||||||
return GetUserData(user, itemId, keys);
|
return GetUserData(user, itemId, keys);
|
||||||
}
|
}
|
||||||
@ -131,7 +130,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
|
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
|
||||||
{
|
{
|
||||||
var userData = Repository.GetUserData(internalUserId, keys);
|
var userData = _repository.GetUserData(internalUserId, keys);
|
||||||
|
|
||||||
if (userData != null)
|
if (userData != null)
|
||||||
{
|
{
|
||||||
|
@ -20,6 +20,7 @@ using MediaBrowser.Controller.Drawing;
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
@ -44,22 +45,14 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
private readonly object _policySyncLock = new object();
|
private readonly object _policySyncLock = new object();
|
||||||
private readonly object _configSyncLock = new object();
|
private readonly object _configSyncLock = new object();
|
||||||
/// <summary>
|
|
||||||
/// The logger.
|
|
||||||
/// </summary>
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
/// <summary>
|
private readonly ILogger _logger;
|
||||||
/// Gets the active user repository.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The user repository.</value>
|
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
private readonly IXmlSerializer _xmlSerializer;
|
private readonly IXmlSerializer _xmlSerializer;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
|
private readonly IImageProcessor _imageProcessor;
|
||||||
private readonly Func<IImageProcessor> _imageProcessorFactory;
|
private readonly Lazy<IDtoService> _dtoServiceFactory;
|
||||||
private readonly Func<IDtoService> _dtoServiceFactory;
|
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ICryptoProvider _cryptoProvider;
|
private readonly ICryptoProvider _cryptoProvider;
|
||||||
@ -74,13 +67,15 @@ namespace Emby.Server.Implementations.Library
|
|||||||
private IPasswordResetProvider[] _passwordResetProviders;
|
private IPasswordResetProvider[] _passwordResetProviders;
|
||||||
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
|
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
|
||||||
|
|
||||||
|
private IDtoService DtoService => _dtoServiceFactory.Value;
|
||||||
|
|
||||||
public UserManager(
|
public UserManager(
|
||||||
ILogger<UserManager> logger,
|
ILogger<UserManager> logger,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
IXmlSerializer xmlSerializer,
|
IXmlSerializer xmlSerializer,
|
||||||
INetworkManager networkManager,
|
INetworkManager networkManager,
|
||||||
Func<IImageProcessor> imageProcessorFactory,
|
IImageProcessor imageProcessor,
|
||||||
Func<IDtoService> dtoServiceFactory,
|
Lazy<IDtoService> dtoServiceFactory,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
@ -90,7 +85,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_xmlSerializer = xmlSerializer;
|
_xmlSerializer = xmlSerializer;
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
_imageProcessorFactory = imageProcessorFactory;
|
_imageProcessor = imageProcessor;
|
||||||
_dtoServiceFactory = dtoServiceFactory;
|
_dtoServiceFactory = dtoServiceFactory;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
@ -264,6 +259,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(username))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint);
|
||||||
throw new ArgumentNullException(nameof(username));
|
throw new ArgumentNullException(nameof(username));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,26 +315,26 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint);
|
||||||
throw new AuthenticationException("Invalid username or password entered.");
|
throw new AuthenticationException("Invalid username or password entered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.Policy.IsDisabled)
|
if (user.Policy.IsDisabled)
|
||||||
{
|
{
|
||||||
throw new AuthenticationException(
|
_logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
|
||||||
string.Format(
|
throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator.");
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"The {0} account is currently disabled. Please consult with your administrator.",
|
|
||||||
user.Name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
||||||
{
|
{
|
||||||
throw new AuthenticationException("Forbidden.");
|
_logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
|
||||||
|
throw new SecurityException("Forbidden.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.IsParentalScheduleAllowed())
|
if (!user.IsParentalScheduleAllowed())
|
||||||
{
|
{
|
||||||
throw new AuthenticationException("User is not allowed access at this time.");
|
_logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
|
||||||
|
throw new SecurityException("User is not allowed access at this time.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update LastActivityDate and LastLoginDate, then save
|
// Update LastActivityDate and LastLoginDate, then save
|
||||||
@ -351,14 +347,14 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
ResetInvalidLoginAttemptCount(user);
|
ResetInvalidLoginAttemptCount(user);
|
||||||
|
_logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IncrementInvalidLoginAttemptCount(user);
|
IncrementInvalidLoginAttemptCount(user);
|
||||||
|
_logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
|
|
||||||
|
|
||||||
return success ? user : null;
|
return success ? user : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,7 +596,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user);
|
DtoService.AttachPrimaryImageAspectRatio(dto, user);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -625,7 +621,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _imageProcessorFactory().GetImageCacheTag(item, image);
|
return _imageProcessor.GetImageCacheTag(item, image);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -25,7 +26,6 @@ using MediaBrowser.Controller.LiveTv;
|
|||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IStreamHelper _streamHelper;
|
private readonly IStreamHelper _streamHelper;
|
||||||
|
|
||||||
@ -88,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILibraryMonitor libraryMonitor,
|
ILibraryMonitor libraryMonitor,
|
||||||
IProviderManager providerManager,
|
IProviderManager providerManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder)
|
||||||
IProcessFactory processFactory)
|
|
||||||
{
|
{
|
||||||
Current = this;
|
Current = this;
|
||||||
|
|
||||||
@ -102,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
_libraryMonitor = libraryMonitor;
|
_libraryMonitor = libraryMonitor;
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_processFactory = processFactory;
|
|
||||||
_liveTvManager = (LiveTvManager)liveTvManager;
|
_liveTvManager = (LiveTvManager)liveTvManager;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
@ -1662,7 +1659,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
{
|
{
|
||||||
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
||||||
{
|
{
|
||||||
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
||||||
@ -1683,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
StartInfo = new ProcessStartInfo
|
||||||
CreateNoWindow = true,
|
{
|
||||||
EnableRaisingEvents = true,
|
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
||||||
ErrorDialog = false,
|
CreateNoWindow = true,
|
||||||
FileName = options.RecordingPostProcessor,
|
ErrorDialog = false,
|
||||||
IsHidden = true,
|
FileName = options.RecordingPostProcessor,
|
||||||
UseShellExecute = false
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
});
|
UseShellExecute = false
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
|
||||||
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
@ -1712,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
private void Process_Exited(object sender, EventArgs e)
|
private void Process_Exited(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using (var process = (IProcess)sender)
|
using (var process = (Process)sender)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
|
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
|
||||||
|
|
||||||
process.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -13,7 +14,6 @@ using MediaBrowser.Controller.Configuration;
|
|||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
@ -29,8 +29,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
private bool _hasExited;
|
private bool _hasExited;
|
||||||
private Stream _logFileStream;
|
private Stream _logFileStream;
|
||||||
private string _targetPath;
|
private string _targetPath;
|
||||||
private IProcess _process;
|
private Process _process;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
@ -40,14 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IJsonSerializer json,
|
IJsonSerializer json,
|
||||||
IProcessFactory processFactory,
|
|
||||||
IServerConfigurationManager config)
|
IServerConfigurationManager config)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_json = json;
|
_json = json;
|
||||||
_processFactory = processFactory;
|
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
_targetPath = targetFile;
|
_targetPath = targetFile;
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var processStartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
@ -90,14 +87,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
FileName = _mediaEncoder.EncoderPath,
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
||||||
|
|
||||||
IsHidden = true,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false
|
||||||
EnableRaisingEvents = true
|
};
|
||||||
});
|
|
||||||
|
|
||||||
_process = process;
|
var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
|
||||||
|
|
||||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
|
||||||
_logger.LogInformation(commandLineLogMessage);
|
_logger.LogInformation(commandLineLogMessage);
|
||||||
|
|
||||||
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
||||||
@ -109,16 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||||
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
||||||
|
|
||||||
process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
|
_process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = processStartInfo,
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
|
||||||
|
|
||||||
process.Start();
|
_process.Start();
|
||||||
|
|
||||||
cancellationToken.Register(Stop);
|
cancellationToken.Register(Stop);
|
||||||
|
|
||||||
onStarted();
|
onStarted();
|
||||||
|
|
||||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||||
StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
|
StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
||||||
|
|
||||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
||||||
|
|
||||||
@ -292,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the exited.
|
/// Processes the exited.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnFfMpegProcessExited(IProcess process, string inputFile)
|
private void OnFfMpegProcessExited(Process process, string inputFile)
|
||||||
{
|
{
|
||||||
_hasExited = true;
|
using (process)
|
||||||
|
|
||||||
_logFileStream?.Dispose();
|
|
||||||
_logFileStream = null;
|
|
||||||
|
|
||||||
var exitCode = process.ExitCode;
|
|
||||||
|
|
||||||
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
|
||||||
|
|
||||||
if (exitCode == 0)
|
|
||||||
{
|
{
|
||||||
_taskCompletionSource.TrySetResult(true);
|
_hasExited = true;
|
||||||
}
|
|
||||||
else
|
_logFileStream?.Dispose();
|
||||||
{
|
_logFileStream = null;
|
||||||
_taskCompletionSource.TrySetException(
|
|
||||||
new Exception(
|
var exitCode = process.ExitCode;
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
||||||
"Recording for {0} failed. Exit code {1}",
|
|
||||||
_targetPath,
|
if (exitCode == 0)
|
||||||
exitCode)));
|
{
|
||||||
|
_taskCompletionSource.TrySetResult(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_taskCompletionSource.TrySetException(
|
||||||
|
new Exception(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"Recording for {0} failed. Exit code {1}",
|
||||||
|
_targetPath,
|
||||||
|
exitCode)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,9 +22,12 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
public class LiveTvDtoService
|
public class LiveTvDtoService
|
||||||
{
|
{
|
||||||
|
private const string InternalVersionNumber = "4";
|
||||||
|
|
||||||
|
private const string ServiceName = "Emby";
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
private readonly IApplicationHost _appHost;
|
private readonly IApplicationHost _appHost;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
@ -32,13 +35,13 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
public LiveTvDtoService(
|
public LiveTvDtoService(
|
||||||
IDtoService dtoService,
|
IDtoService dtoService,
|
||||||
IImageProcessor imageProcessor,
|
IImageProcessor imageProcessor,
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<LiveTvDtoService> logger,
|
||||||
IApplicationHost appHost,
|
IApplicationHost appHost,
|
||||||
ILibraryManager libraryManager)
|
ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(LiveTvDtoService));
|
_logger = logger;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
@ -161,7 +164,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||||
DtoOptions = new DtoOptions(false)
|
DtoOptions = new DtoOptions(false)
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
|
|
||||||
if (librarySeries != null)
|
if (librarySeries != null)
|
||||||
@ -179,6 +181,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
_logger.LogError(ex, "Error");
|
_logger.LogError(ex, "Error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
|
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
@ -199,13 +202,12 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||||
ExternalSeriesId = programSeriesId,
|
ExternalSeriesId = programSeriesId,
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
DtoOptions = new DtoOptions(false),
|
DtoOptions = new DtoOptions(false),
|
||||||
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
|
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
|
|
||||||
if (program != null)
|
if (program != null)
|
||||||
@ -232,9 +234,10 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
dto.ParentBackdropImageTags = new string[]
|
dto.ParentBackdropImageTags = new string[]
|
||||||
{
|
{
|
||||||
_imageProcessor.GetImageCacheTag(program, image)
|
_imageProcessor.GetImageCacheTag(program, image)
|
||||||
};
|
};
|
||||||
|
|
||||||
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -255,7 +258,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||||
DtoOptions = new DtoOptions(false)
|
DtoOptions = new DtoOptions(false)
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
|
|
||||||
if (librarySeries != null)
|
if (librarySeries != null)
|
||||||
@ -273,6 +275,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
_logger.LogError(ex, "Error");
|
_logger.LogError(ex, "Error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
|
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
@ -298,7 +301,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
DtoOptions = new DtoOptions(false)
|
DtoOptions = new DtoOptions(false)
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
|
|
||||||
if (program == null)
|
if (program == null)
|
||||||
@ -311,7 +313,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
DtoOptions = new DtoOptions(false),
|
DtoOptions = new DtoOptions(false),
|
||||||
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
|
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,8 +397,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string InternalVersionNumber = "4";
|
|
||||||
|
|
||||||
public Guid GetInternalChannelId(string serviceName, string externalId)
|
public Guid GetInternalChannelId(string serviceName, string externalId)
|
||||||
{
|
{
|
||||||
var name = serviceName + externalId + InternalVersionNumber;
|
var name = serviceName + externalId + InternalVersionNumber;
|
||||||
@ -405,7 +404,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel));
|
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel));
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string ServiceName = "Emby";
|
|
||||||
public string GetInternalTimerId(string externalId)
|
public string GetInternalTimerId(string externalId)
|
||||||
{
|
{
|
||||||
var name = ServiceName + externalId + InternalVersionNumber;
|
var name = ServiceName + externalId + InternalVersionNumber;
|
||||||
|
@ -41,33 +41,32 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class LiveTvManager : ILiveTvManager, IDisposable
|
public class LiveTvManager : ILiveTvManager, IDisposable
|
||||||
{
|
{
|
||||||
|
private const string ExternalServiceTag = "ExternalServiceId";
|
||||||
|
|
||||||
|
private const string EtagKey = "ProgramEtag";
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IDtoService _dtoService;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ITaskManager _taskManager;
|
private readonly ITaskManager _taskManager;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
|
||||||
private readonly Func<IChannelManager> _channelManager;
|
|
||||||
|
|
||||||
private readonly IDtoService _dtoService;
|
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IChannelManager _channelManager;
|
||||||
private readonly LiveTvDtoService _tvDtoService;
|
private readonly LiveTvDtoService _tvDtoService;
|
||||||
|
|
||||||
private ILiveTvService[] _services = Array.Empty<ILiveTvService>();
|
private ILiveTvService[] _services = Array.Empty<ILiveTvService>();
|
||||||
|
|
||||||
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
|
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
|
||||||
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
|
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
public LiveTvManager(
|
public LiveTvManager(
|
||||||
IServerApplicationHost appHost,
|
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<LiveTvManager> logger,
|
||||||
IItemRepository itemRepo,
|
IItemRepository itemRepo,
|
||||||
IImageProcessor imageProcessor,
|
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
IDtoService dtoService,
|
IDtoService dtoService,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
@ -76,10 +75,11 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
Func<IChannelManager> channelManager)
|
IChannelManager channelManager,
|
||||||
|
LiveTvDtoService liveTvDtoService)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(LiveTvManager));
|
_logger = logger;
|
||||||
_itemRepo = itemRepo;
|
_itemRepo = itemRepo;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
@ -90,8 +90,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
|
_tvDtoService = liveTvDtoService;
|
||||||
_tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, loggerFactory, appHost, _libraryManager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
|
public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
|
||||||
@ -178,7 +177,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
Name = i.Name,
|
Name = i.Name,
|
||||||
Id = i.Type
|
Id = i.Type
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,6 +259,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
var endTime = DateTime.UtcNow;
|
var endTime = DateTime.UtcNow;
|
||||||
_logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
|
_logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
info.RequiresClosing = true;
|
info.RequiresClosing = true;
|
||||||
|
|
||||||
var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
|
var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
|
||||||
@ -362,30 +361,37 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
stream.BitRate = null;
|
stream.BitRate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.Channels.HasValue && stream.Channels <= 0)
|
if (stream.Channels.HasValue && stream.Channels <= 0)
|
||||||
{
|
{
|
||||||
stream.Channels = null;
|
stream.Channels = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
|
if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
|
||||||
{
|
{
|
||||||
stream.AverageFrameRate = null;
|
stream.AverageFrameRate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
|
if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
|
||||||
{
|
{
|
||||||
stream.RealFrameRate = null;
|
stream.RealFrameRate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.Width.HasValue && stream.Width <= 0)
|
if (stream.Width.HasValue && stream.Width <= 0)
|
||||||
{
|
{
|
||||||
stream.Width = null;
|
stream.Width = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.Height.HasValue && stream.Height <= 0)
|
if (stream.Height.HasValue && stream.Height <= 0)
|
||||||
{
|
{
|
||||||
stream.Height = null;
|
stream.Height = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
|
if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
|
||||||
{
|
{
|
||||||
stream.SampleRate = null;
|
stream.SampleRate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.Level.HasValue && stream.Level <= 0)
|
if (stream.Level.HasValue && stream.Level <= 0)
|
||||||
{
|
{
|
||||||
stream.Level = null;
|
stream.Level = null;
|
||||||
@ -427,7 +433,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string ExternalServiceTag = "ExternalServiceId";
|
|
||||||
private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
|
private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var parentFolderId = parentFolder.Id;
|
var parentFolderId = parentFolder.Id;
|
||||||
@ -456,6 +461,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Tags = channelInfo.Tags;
|
item.Tags = channelInfo.Tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,6 +469,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ParentId = parentFolderId;
|
item.ParentId = parentFolderId;
|
||||||
|
|
||||||
item.ChannelType = channelInfo.ChannelType;
|
item.ChannelType = channelInfo.ChannelType;
|
||||||
@ -472,24 +479,28 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.SetProviderId(ExternalServiceTag, serviceName);
|
item.SetProviderId(ExternalServiceTag, serviceName);
|
||||||
|
|
||||||
if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
|
if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ExternalId = channelInfo.Id;
|
item.ExternalId = channelInfo.Id;
|
||||||
|
|
||||||
if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
|
if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Number = channelInfo.Number;
|
item.Number = channelInfo.Number;
|
||||||
|
|
||||||
if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal))
|
if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Name = channelInfo.Name;
|
item.Name = channelInfo.Name;
|
||||||
|
|
||||||
if (!item.HasImage(ImageType.Primary))
|
if (!item.HasImage(ImageType.Primary))
|
||||||
@ -518,8 +529,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string EtagKey = "ProgramEtag";
|
|
||||||
|
|
||||||
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
|
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var id = _tvDtoService.GetInternalProgramId(info.Id);
|
var id = _tvDtoService.GetInternalProgramId(info.Id);
|
||||||
@ -2482,7 +2491,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
.OrderBy(i => i.SortName)
|
.OrderBy(i => i.SortName)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
folders.AddRange(_channelManager().GetChannelsInternal(new MediaBrowser.Model.Channels.ChannelQuery
|
folders.AddRange(_channelManager.GetChannelsInternal(new MediaBrowser.Model.Channels.ChannelQuery
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
IsRecordingsFolder = true,
|
IsRecordingsFolder = true,
|
||||||
|
@ -102,5 +102,17 @@
|
|||||||
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
|
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
|
||||||
"TaskRefreshChapterImagesDescription": "إنشاء صور مصغرة لمقاطع الفيديو ذات فصول.",
|
"TaskRefreshChapterImagesDescription": "إنشاء صور مصغرة لمقاطع الفيديو ذات فصول.",
|
||||||
"TaskRefreshChapterImages": "استخراج صور الفصل",
|
"TaskRefreshChapterImages": "استخراج صور الفصل",
|
||||||
"TasksApplicationCategory": "تطبيق"
|
"TasksApplicationCategory": "تطبيق",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "ابحث في الإنترنت على الترجمات المفقودة إستنادا على الميتاداتا.",
|
||||||
|
"TaskDownloadMissingSubtitles": "تحميل الترجمات المفقودة",
|
||||||
|
"TaskRefreshChannelsDescription": "تحديث معلومات قنوات الإنترنت.",
|
||||||
|
"TaskRefreshChannels": "إعادة تحديث القنوات",
|
||||||
|
"TaskCleanTranscodeDescription": "حذف ملفات الترميز الأقدم من يوم واحد.",
|
||||||
|
"TaskCleanTranscode": "حذف سجلات الترميز",
|
||||||
|
"TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.",
|
||||||
|
"TaskUpdatePlugins": "تحديث الإضافات",
|
||||||
|
"TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
|
||||||
|
"TaskRefreshPeople": "إعادة تحميل الأشخاص",
|
||||||
|
"TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.",
|
||||||
|
"TaskCleanLogs": "حذف دليل السجل"
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,19 @@
|
|||||||
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
|
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
|
||||||
"Application": "Aplicació",
|
"Application": "Aplicació",
|
||||||
"Artists": "Artistes",
|
"Artists": "Artistes",
|
||||||
"AuthenticationSucceededWithUserName": "{0} s'ha autentificat correctament",
|
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
|
||||||
"Books": "Llibres",
|
"Books": "Llibres",
|
||||||
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha sigut pujada des de {0}",
|
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}",
|
||||||
"Channels": "Canals",
|
"Channels": "Canals",
|
||||||
"ChapterNameValue": "Episodi {0}",
|
"ChapterNameValue": "Capítol {0}",
|
||||||
"Collections": "Col·leccions",
|
"Collections": "Col·leccions",
|
||||||
"DeviceOfflineWithName": "{0} s'ha desconnectat",
|
"DeviceOfflineWithName": "{0} s'ha desconnectat",
|
||||||
"DeviceOnlineWithName": "{0} està connectat",
|
"DeviceOnlineWithName": "{0} està connectat",
|
||||||
"FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}",
|
"FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}",
|
||||||
"Favorites": "Preferits",
|
"Favorites": "Preferits",
|
||||||
"Folders": "Directoris",
|
"Folders": "Carpetes",
|
||||||
"Genres": "Gèneres",
|
"Genres": "Gèneres",
|
||||||
"HeaderAlbumArtists": "Artistes dels Àlbums",
|
"HeaderAlbumArtists": "Artistes del Àlbum",
|
||||||
"HeaderCameraUploads": "Pujades de Càmera",
|
"HeaderCameraUploads": "Pujades de Càmera",
|
||||||
"HeaderContinueWatching": "Continua Veient",
|
"HeaderContinueWatching": "Continua Veient",
|
||||||
"HeaderFavoriteAlbums": "Àlbums Preferits",
|
"HeaderFavoriteAlbums": "Àlbums Preferits",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Albums": "Album",
|
"Albums": "Albums",
|
||||||
"AppDeviceValues": "App: {0}, Enhed: {1}",
|
"AppDeviceValues": "App: {0}, Enhed: {1}",
|
||||||
"Application": "Applikation",
|
"Application": "Applikation",
|
||||||
"Artists": "Kunstnere",
|
"Artists": "Kunstnere",
|
||||||
@ -35,8 +35,8 @@
|
|||||||
"Latest": "Seneste",
|
"Latest": "Seneste",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
|
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Server konfiguration sektion {0} er blevet opdateret",
|
||||||
"MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
|
"MessageServerConfigurationUpdated": "Server konfigurationen er blevet opdateret",
|
||||||
"MixedContent": "Blandet indhold",
|
"MixedContent": "Blandet indhold",
|
||||||
"Movies": "Film",
|
"Movies": "Film",
|
||||||
"Music": "Musik",
|
"Music": "Musik",
|
||||||
@ -92,5 +92,27 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
|
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
|
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
|
||||||
"ValueSpecialEpisodeName": "Special - {0}",
|
"ValueSpecialEpisodeName": "Special - {0}",
|
||||||
"VersionNumber": "Version {0}"
|
"VersionNumber": "Version {0}",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiguration.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Download manglende undertekster",
|
||||||
|
"TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.",
|
||||||
|
"TaskUpdatePlugins": "Opdater Plugins",
|
||||||
|
"TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.",
|
||||||
|
"TaskCleanLogs": "Ryd Log Mappe",
|
||||||
|
"TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdaterer metadata.",
|
||||||
|
"TaskRefreshLibrary": "Scan Medie Bibliotek",
|
||||||
|
"TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
|
||||||
|
"TaskCleanCache": "Ryd Cache Mappe",
|
||||||
|
"TasksChannelsCategory": "Internet Kanaler",
|
||||||
|
"TasksApplicationCategory": "Applikation",
|
||||||
|
"TasksLibraryCategory": "Bibliotek",
|
||||||
|
"TasksMaintenanceCategory": "Vedligeholdelse",
|
||||||
|
"TaskRefreshChapterImages": "Udtræk Kapitel billeder",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler.",
|
||||||
|
"TaskRefreshChannelsDescription": "Genopfrisker internet kanal information.",
|
||||||
|
"TaskRefreshChannels": "Genopfrisk Kanaler",
|
||||||
|
"TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end en dag gammel.",
|
||||||
|
"TaskCleanTranscode": "Rengør Transcode Mappen",
|
||||||
|
"TaskRefreshPeople": "Genopfrisk Personer",
|
||||||
|
"TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek."
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||||
"Application": "Anwendung",
|
"Application": "Anwendung",
|
||||||
"Artists": "Interpreten",
|
"Artists": "Interpreten",
|
||||||
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifziert",
|
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifiziert",
|
||||||
"Books": "Bücher",
|
"Books": "Bücher",
|
||||||
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
|
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
|
||||||
"Channels": "Kanäle",
|
"Channels": "Kanäle",
|
||||||
@ -99,11 +99,11 @@
|
|||||||
"TaskRefreshChannels": "Erneuere Kanäle",
|
"TaskRefreshChannels": "Erneuere Kanäle",
|
||||||
"TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.",
|
"TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.",
|
||||||
"TaskCleanTranscode": "Lösche Transkodier Pfad",
|
"TaskCleanTranscode": "Lösche Transkodier Pfad",
|
||||||
"TaskUpdatePluginsDescription": "Läd Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
|
"TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
|
||||||
"TaskUpdatePlugins": "Update Plugins",
|
"TaskUpdatePlugins": "Update Plugins",
|
||||||
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.",
|
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.",
|
||||||
"TaskRefreshPeople": "Erneuere Schausteller",
|
"TaskRefreshPeople": "Erneuere Schausteller",
|
||||||
"TaskCleanLogsDescription": "Lösche Log Datein die älter als {0} Tage sind.",
|
"TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.",
|
||||||
"TaskCleanLogs": "Lösche Log Pfad",
|
"TaskCleanLogs": "Lösche Log Pfad",
|
||||||
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",
|
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",
|
||||||
"TaskRefreshLibrary": "Scanne alle Bibliotheken",
|
"TaskRefreshLibrary": "Scanne alle Bibliotheken",
|
||||||
|
@ -92,5 +92,27 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||||
"ValueSpecialEpisodeName": "Special - {0}",
|
"ValueSpecialEpisodeName": "Special - {0}",
|
||||||
"VersionNumber": "Version {0}"
|
"VersionNumber": "Version {0}",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Download missing subtitles",
|
||||||
|
"TaskRefreshChannelsDescription": "Refreshes internet channel information.",
|
||||||
|
"TaskRefreshChannels": "Refresh Channels",
|
||||||
|
"TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.",
|
||||||
|
"TaskCleanTranscode": "Clean Transcode Directory",
|
||||||
|
"TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
|
||||||
|
"TaskUpdatePlugins": "Update Plugins",
|
||||||
|
"TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
|
||||||
|
"TaskRefreshPeople": "Refresh People",
|
||||||
|
"TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
|
||||||
|
"TaskCleanLogs": "Clean Log Directory",
|
||||||
|
"TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
|
||||||
|
"TaskRefreshLibrary": "Scan Media Library",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
|
||||||
|
"TaskRefreshChapterImages": "Extract Chapter Images",
|
||||||
|
"TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
|
||||||
|
"TaskCleanCache": "Clean Cache Directory",
|
||||||
|
"TasksChannelsCategory": "Internet Channels",
|
||||||
|
"TasksApplicationCategory": "Application",
|
||||||
|
"TasksLibraryCategory": "Library",
|
||||||
|
"TasksMaintenanceCategory": "Maintenance"
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"Genres": "Géneros",
|
"Genres": "Géneros",
|
||||||
"HeaderAlbumArtists": "Artistas de álbum",
|
"HeaderAlbumArtists": "Artistas de álbum",
|
||||||
"HeaderCameraUploads": "Subidas de cámara",
|
"HeaderCameraUploads": "Subidas de cámara",
|
||||||
"HeaderContinueWatching": "Continuar viendo",
|
"HeaderContinueWatching": "Seguir viendo",
|
||||||
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
||||||
"HeaderFavoriteArtists": "Artistas favoritos",
|
"HeaderFavoriteArtists": "Artistas favoritos",
|
||||||
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"Collections": "Colecciones",
|
"Collections": "Colecciones",
|
||||||
"Artists": "Artistas",
|
"Artists": "Artistas",
|
||||||
"DeviceOnlineWithName": "{0} está conectado",
|
"DeviceOnlineWithName": "{0} está conectado",
|
||||||
"DeviceOfflineWithName": "{0} ha desconectado",
|
"DeviceOfflineWithName": "{0} se ha desconectado",
|
||||||
"ChapterNameValue": "Capítulo {0}",
|
"ChapterNameValue": "Capítulo {0}",
|
||||||
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
|
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
|
||||||
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
|
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"HeaderFavoriteEpisodes": "قسمتهای مورد علاقه",
|
"HeaderFavoriteEpisodes": "قسمتهای مورد علاقه",
|
||||||
"HeaderFavoriteShows": "سریالهای مورد علاقه",
|
"HeaderFavoriteShows": "سریالهای مورد علاقه",
|
||||||
"HeaderFavoriteSongs": "آهنگهای مورد علاقه",
|
"HeaderFavoriteSongs": "آهنگهای مورد علاقه",
|
||||||
"HeaderLiveTV": "پخش زنده تلویزیون",
|
"HeaderLiveTV": "پخش زنده",
|
||||||
"HeaderNextUp": "قسمت بعدی",
|
"HeaderNextUp": "قسمت بعدی",
|
||||||
"HeaderRecordingGroups": "گروههای ضبط",
|
"HeaderRecordingGroups": "گروههای ضبط",
|
||||||
"HomeVideos": "ویدیوهای خانگی",
|
"HomeVideos": "ویدیوهای خانگی",
|
||||||
@ -92,5 +92,27 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
|
"UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} به کتابخانهی رسانهی شما افزوده شد",
|
"ValueHasBeenAddedToLibrary": "{0} به کتابخانهی رسانهی شما افزوده شد",
|
||||||
"ValueSpecialEpisodeName": "ویژه - {0}",
|
"ValueSpecialEpisodeName": "ویژه - {0}",
|
||||||
"VersionNumber": "نسخه {0}"
|
"VersionNumber": "نسخه {0}",
|
||||||
|
"TaskCleanTranscodeDescription": "فایلهای کدگذاری که قدیمیتر از یک روز هستند را حذف میکند.",
|
||||||
|
"TaskCleanTranscode": "پاکسازی مسیر کد گذاری",
|
||||||
|
"TaskUpdatePluginsDescription": "دانلود و نصب به روز رسانی افزونههایی که برای به روز رسانی خودکار پیکربندی شدهاند.",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "جستجوی زیرنویسهای ناموجود در اینترنت بر اساس پیکربندی ابردادهها.",
|
||||||
|
"TaskDownloadMissingSubtitles": "دانلود زیرنویسهای ناموجود",
|
||||||
|
"TaskRefreshChannelsDescription": "اطلاعات کانال اینترنتی را تازه سازی میکند.",
|
||||||
|
"TaskRefreshChannels": "تازه سازی کانالها",
|
||||||
|
"TaskUpdatePlugins": "به روز رسانی افزونهها",
|
||||||
|
"TaskRefreshPeopleDescription": "ابردادهها برای بازیگران و کارگردانان در کتابخانه رسانه شما به روزرسانی می شوند.",
|
||||||
|
"TaskRefreshPeople": "تازه سازی افراد",
|
||||||
|
"TaskCleanLogsDescription": "واقعه نگارهایی را که قدیمی تر {0} روز هستند را حذف می کند.",
|
||||||
|
"TaskCleanLogs": "پاکسازی مسیر واقعه نگار",
|
||||||
|
"TaskRefreshLibraryDescription": "کتابخانه رسانه شما را اسکن میکند و ابردادهها را تازه سازی میکند.",
|
||||||
|
"TaskRefreshLibrary": "اسکن کتابخانه رسانه",
|
||||||
|
"TaskRefreshChapterImagesDescription": "عکسهای کوچک برای ویدیوهایی که سکانس دارند ایجاد میکند.",
|
||||||
|
"TaskRefreshChapterImages": "استخراج عکسهای سکانس",
|
||||||
|
"TaskCleanCacheDescription": "فایلهای حافظه موقت که توسط سیستم دیگر مورد نیاز نیستند حذف میشوند.",
|
||||||
|
"TaskCleanCache": "پاکسازی مسیر حافظه موقت",
|
||||||
|
"TasksChannelsCategory": "کانالهای داخلی",
|
||||||
|
"TasksApplicationCategory": "برنامه",
|
||||||
|
"TasksLibraryCategory": "کتابخانه",
|
||||||
|
"TasksMaintenanceCategory": "تعمیر"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"HeaderLiveTV": "TV-lähetykset",
|
"HeaderLiveTV": "Suorat lähetykset",
|
||||||
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
|
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
|
||||||
"NameSeasonUnknown": "Tuntematon Kausi",
|
"NameSeasonUnknown": "Tuntematon Kausi",
|
||||||
"NameSeasonNumber": "Kausi {0}",
|
"NameSeasonNumber": "Kausi {0}",
|
||||||
@ -19,12 +19,12 @@
|
|||||||
"ItemAddedWithName": "{0} lisättiin kirjastoon",
|
"ItemAddedWithName": "{0} lisättiin kirjastoon",
|
||||||
"Inherit": "Periytyä",
|
"Inherit": "Periytyä",
|
||||||
"HomeVideos": "Kotivideot",
|
"HomeVideos": "Kotivideot",
|
||||||
"HeaderRecordingGroups": "Nauhoitusryhmät",
|
"HeaderRecordingGroups": "Nauhoiteryhmät",
|
||||||
"HeaderNextUp": "Seuraavaksi",
|
"HeaderNextUp": "Seuraavaksi",
|
||||||
"HeaderFavoriteSongs": "Lempikappaleet",
|
"HeaderFavoriteSongs": "Lempikappaleet",
|
||||||
"HeaderFavoriteShows": "Lempisarjat",
|
"HeaderFavoriteShows": "Lempisarjat",
|
||||||
"HeaderFavoriteEpisodes": "Lempijaksot",
|
"HeaderFavoriteEpisodes": "Lempijaksot",
|
||||||
"HeaderCameraUploads": "Kameralataukset",
|
"HeaderCameraUploads": "Kamerasta Lähetetyt",
|
||||||
"HeaderFavoriteArtists": "Lempiartistit",
|
"HeaderFavoriteArtists": "Lempiartistit",
|
||||||
"HeaderFavoriteAlbums": "Lempialbumit",
|
"HeaderFavoriteAlbums": "Lempialbumit",
|
||||||
"HeaderContinueWatching": "Jatka katsomista",
|
"HeaderContinueWatching": "Jatka katsomista",
|
||||||
@ -63,10 +63,10 @@
|
|||||||
"UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
|
"UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
|
||||||
"UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
|
"UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
|
||||||
"UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
|
"UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
|
||||||
"UserLockedOutWithName": "Käyttäjä {0} kirjautui ulos",
|
"UserLockedOutWithName": "Käyttäjä {0} lukittu",
|
||||||
"UserDownloadingItemWithValues": "{0} latautumassa {1}",
|
"UserDownloadingItemWithValues": "{0} lataa {1}",
|
||||||
"UserDeletedWithName": "Poistettiin käyttäjä {0}",
|
"UserDeletedWithName": "Käyttäjä {0} poistettu",
|
||||||
"UserCreatedWithName": "Luotiin käyttäjä {0}",
|
"UserCreatedWithName": "Käyttäjä {0} luotu",
|
||||||
"TvShows": "TV-Ohjelmat",
|
"TvShows": "TV-Ohjelmat",
|
||||||
"Sync": "Synkronoi",
|
"Sync": "Synkronoi",
|
||||||
"SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
|
"SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
|
||||||
@ -74,22 +74,44 @@
|
|||||||
"Songs": "Kappaleet",
|
"Songs": "Kappaleet",
|
||||||
"Shows": "Ohjelmat",
|
"Shows": "Ohjelmat",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
|
"ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
|
||||||
"ProviderValue": "Palveluntarjoaja: {0}",
|
"ProviderValue": "Tarjoaja: {0}",
|
||||||
"Plugin": "Liitännäinen",
|
"Plugin": "Liitännäinen",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Videon toistaminen pysäytetty",
|
"NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
|
||||||
"NotificationOptionVideoPlayback": "Videon toistaminen aloitettu",
|
"NotificationOptionVideoPlayback": "Videon toisto aloitettu",
|
||||||
"NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
|
"NotificationOptionUserLockedOut": "Käyttäjä lukittu",
|
||||||
"NotificationOptionTaskFailed": "Ajastetun tehtävän ongelma",
|
"NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
|
||||||
"NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
|
"NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
|
"NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu",
|
||||||
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
||||||
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
||||||
"NotificationOptionPluginError": "Ongelma liitännäisessä",
|
"NotificationOptionPluginError": "Ongelma liitännäisessä",
|
||||||
"NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
|
"NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
|
||||||
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
|
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
|
||||||
"NotificationOptionCameraImageUploaded": "Kuva ladattu kamerasta",
|
"NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Audion toisto pysäytetty",
|
"NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
|
||||||
"NotificationOptionAudioPlayback": "Audion toisto aloitettu",
|
"NotificationOptionAudioPlayback": "Toistetaan ääntä",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Ohjelmistopäivitys asennettu",
|
"NotificationOptionApplicationUpdateInstalled": "Uusi sovellusversio asennettu",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla"
|
"NotificationOptionApplicationUpdateAvailable": "Sovelluksesta on uusi versio saatavilla",
|
||||||
|
"TasksMaintenanceCategory": "Ylläpito",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
|
||||||
|
"TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
|
||||||
|
"TaskRefreshChannels": "Päivitä kanavat",
|
||||||
|
"TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.",
|
||||||
|
"TaskCleanTranscode": "Puhdista transkoodaushakemisto",
|
||||||
|
"TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.",
|
||||||
|
"TaskUpdatePlugins": "Päivitä liitännäiset",
|
||||||
|
"TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.",
|
||||||
|
"TaskRefreshPeople": "Päivitä henkilöt",
|
||||||
|
"TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
|
||||||
|
"TaskCleanLogs": "Puhdista lokihakemisto",
|
||||||
|
"TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.",
|
||||||
|
"TaskRefreshLibrary": "Skannaa mediakirjasto",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.",
|
||||||
|
"TaskRefreshChapterImages": "Eristä lukujen kuvat",
|
||||||
|
"TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
|
||||||
|
"TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
|
||||||
|
"TasksChannelsCategory": "Internet kanavat",
|
||||||
|
"TasksApplicationCategory": "Sovellus",
|
||||||
|
"TasksLibraryCategory": "Kirjasto"
|
||||||
}
|
}
|
||||||
|
@ -90,5 +90,13 @@
|
|||||||
"Artists": "Artista",
|
"Artists": "Artista",
|
||||||
"Application": "Aplikasyon",
|
"Application": "Aplikasyon",
|
||||||
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
|
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
|
||||||
"Albums": "Albums"
|
"Albums": "Albums",
|
||||||
|
"TaskRefreshLibrary": "Suriin ang nasa librerya",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata",
|
||||||
|
"TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata",
|
||||||
|
"TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.",
|
||||||
|
"TasksChannelsCategory": "Palabas sa internet",
|
||||||
|
"TasksLibraryCategory": "Librerya",
|
||||||
|
"TasksMaintenanceCategory": "Pagpapanatili",
|
||||||
|
"HomeVideos": "Sariling pelikula"
|
||||||
}
|
}
|
||||||
|
@ -5,17 +5,17 @@
|
|||||||
"Artists": "Artistes",
|
"Artists": "Artistes",
|
||||||
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
||||||
"Books": "Livres",
|
"Books": "Livres",
|
||||||
"CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}",
|
"CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}",
|
||||||
"Channels": "Chaînes",
|
"Channels": "Chaînes",
|
||||||
"ChapterNameValue": "Chapitre {0}",
|
"ChapterNameValue": "Chapitre {0}",
|
||||||
"Collections": "Collections",
|
"Collections": "Collections",
|
||||||
"DeviceOfflineWithName": "{0} s'est déconnecté",
|
"DeviceOfflineWithName": "{0} s'est déconnecté",
|
||||||
"DeviceOnlineWithName": "{0} est connecté",
|
"DeviceOnlineWithName": "{0} est connecté",
|
||||||
"FailedLoginAttemptWithUserName": "Échec de connexion de {0}",
|
"FailedLoginAttemptWithUserName": "Échec de connexion depuis {0}",
|
||||||
"Favorites": "Favoris",
|
"Favorites": "Favoris",
|
||||||
"Folders": "Dossiers",
|
"Folders": "Dossiers",
|
||||||
"Genres": "Genres",
|
"Genres": "Genres",
|
||||||
"HeaderAlbumArtists": "Artistes d'album",
|
"HeaderAlbumArtists": "Artistes de l'album",
|
||||||
"HeaderCameraUploads": "Photos transférées",
|
"HeaderCameraUploads": "Photos transférées",
|
||||||
"HeaderContinueWatching": "Continuer à regarder",
|
"HeaderContinueWatching": "Continuer à regarder",
|
||||||
"HeaderFavoriteAlbums": "Albums favoris",
|
"HeaderFavoriteAlbums": "Albums favoris",
|
||||||
@ -69,7 +69,7 @@
|
|||||||
"PluginUpdatedWithName": "{0} a été mis à jour",
|
"PluginUpdatedWithName": "{0} a été mis à jour",
|
||||||
"ProviderValue": "Fournisseur : {0}",
|
"ProviderValue": "Fournisseur : {0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} a échoué",
|
"ScheduledTaskFailedWithName": "{0} a échoué",
|
||||||
"ScheduledTaskStartedWithName": "{0} a commencé",
|
"ScheduledTaskStartedWithName": "{0} a démarré",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
|
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
|
||||||
"Shows": "Émissions",
|
"Shows": "Émissions",
|
||||||
"Songs": "Chansons",
|
"Songs": "Chansons",
|
||||||
@ -95,21 +95,21 @@
|
|||||||
"VersionNumber": "Version {0}",
|
"VersionNumber": "Version {0}",
|
||||||
"TasksChannelsCategory": "Chaines en ligne",
|
"TasksChannelsCategory": "Chaines en ligne",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
|
"TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
|
||||||
"TaskDownloadMissingSubtitles": "Télécharge les sous-titres manquant",
|
"TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquant",
|
||||||
"TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
|
"TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
|
||||||
"TaskRefreshChannels": "Rafraîchit les chaines",
|
"TaskRefreshChannels": "Rafraîchir les chaines",
|
||||||
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
|
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
|
||||||
"TaskCleanTranscode": "Nettoie les dossier des transcodages",
|
"TaskCleanTranscode": "Nettoyer les dossier des transcodages",
|
||||||
"TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des plugins configurés pour être mis à jour automatiquement.",
|
"TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour être mises à jour automatiquement.",
|
||||||
"TaskUpdatePlugins": "Mettre à jour les plugins",
|
"TaskUpdatePlugins": "Mettre à jour les extensions",
|
||||||
"TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et directeurs dans votre bibliothèque.",
|
"TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.",
|
||||||
"TaskRefreshPeople": "Rafraîchit les acteurs",
|
"TaskRefreshPeople": "Rafraîchir les acteurs",
|
||||||
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
|
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
|
||||||
"TaskCleanLogs": "Nettoie le répertoire des journaux",
|
"TaskCleanLogs": "Nettoyer le répertoire des journaux",
|
||||||
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
|
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
|
||||||
"TaskRefreshLibrary": "Scanne toute les Bibliothèques",
|
"TaskRefreshLibrary": "Scanner toute les Bibliothèques",
|
||||||
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
|
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
|
||||||
"TaskRefreshChapterImages": "Extrait les images de chapitre",
|
"TaskRefreshChapterImages": "Extraire les images de chapitre",
|
||||||
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
|
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
|
||||||
"TaskCleanCache": "Vider le répertoire cache",
|
"TaskCleanCache": "Vider le répertoire cache",
|
||||||
"TasksApplicationCategory": "Application",
|
"TasksApplicationCategory": "Application",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Albums": "אלבומים",
|
"Albums": "אלבומים",
|
||||||
"AppDeviceValues": "יישום: {0}, מכשיר: {1}",
|
"AppDeviceValues": "יישום: {0}, מכשיר: {1}",
|
||||||
"Application": "אפליקציה",
|
"Application": "יישום",
|
||||||
"Artists": "אומנים",
|
"Artists": "אומנים",
|
||||||
"AuthenticationSucceededWithUserName": "{0} אומת בהצלחה",
|
"AuthenticationSucceededWithUserName": "{0} אומת בהצלחה",
|
||||||
"Books": "ספרים",
|
"Books": "ספרים",
|
||||||
@ -92,5 +92,12 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} סיים לנגן את {1} על {2}",
|
"UserStoppedPlayingItemWithValues": "{0} סיים לנגן את {1} על {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||||
"ValueSpecialEpisodeName": "מיוחד- {0}",
|
"ValueSpecialEpisodeName": "מיוחד- {0}",
|
||||||
"VersionNumber": "Version {0}"
|
"VersionNumber": "Version {0}",
|
||||||
|
"TaskRefreshLibrary": "סרוק ספריית מדיה",
|
||||||
|
"TaskRefreshChapterImages": "חלץ תמונות פרקים",
|
||||||
|
"TaskCleanCacheDescription": "מחק קבצי מטמון שלא בשימוש המערכת.",
|
||||||
|
"TaskCleanCache": "נקה תיקיית מטמון",
|
||||||
|
"TasksApplicationCategory": "יישום",
|
||||||
|
"TasksLibraryCategory": "ספרייה",
|
||||||
|
"TasksMaintenanceCategory": "תחזוקה"
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
"ScheduledTaskFailedWithName": "{0} sikertelen",
|
"ScheduledTaskFailedWithName": "{0} sikertelen",
|
||||||
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
||||||
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
||||||
"Shows": "Műsorok",
|
"Shows": "Sorozatok",
|
||||||
"Songs": "Dalok",
|
"Songs": "Dalok",
|
||||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
|
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
|
||||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||||
|
@ -104,10 +104,14 @@
|
|||||||
"TasksMaintenanceCategory": "メンテナンス",
|
"TasksMaintenanceCategory": "メンテナンス",
|
||||||
"TaskRefreshChannelsDescription": "ネットチャンネルの情報をリフレッシュします。",
|
"TaskRefreshChannelsDescription": "ネットチャンネルの情報をリフレッシュします。",
|
||||||
"TaskRefreshChannels": "チャンネルのリフレッシュ",
|
"TaskRefreshChannels": "チャンネルのリフレッシュ",
|
||||||
"TaskCleanTranscodeDescription": "一日以上前のトランスコードを消去します。",
|
"TaskCleanTranscodeDescription": "1日以上経過したトランスコードファイルを削除します。",
|
||||||
"TaskCleanTranscode": "トランスコード用のディレクトリの掃除",
|
"TaskCleanTranscode": "トランスコードディレクトリの削除",
|
||||||
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
||||||
"TaskUpdatePlugins": "プラグインの更新",
|
"TaskUpdatePlugins": "プラグインの更新",
|
||||||
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
|
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータを更新します。",
|
||||||
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ"
|
"TaskRefreshPeople": "俳優や監督のデータの更新",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
|
||||||
|
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
||||||
|
"TaskRefreshChapterImages": "チャプター画像を抽出する",
|
||||||
|
"TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする"
|
||||||
}
|
}
|
||||||
|
61
Emby.Server.Implementations/Localization/Core/mr.json
Normal file
61
Emby.Server.Implementations/Localization/Core/mr.json
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"Books": "पुस्तकं",
|
||||||
|
"Artists": "संगीतकार",
|
||||||
|
"Albums": "अल्बम",
|
||||||
|
"Playlists": "प्लेलिस्ट",
|
||||||
|
"HeaderAlbumArtists": "अल्बम संगीतकार",
|
||||||
|
"Folders": "फोल्डर",
|
||||||
|
"HeaderFavoriteEpisodes": "आवडते भाग",
|
||||||
|
"HeaderFavoriteSongs": "आवडती गाणी",
|
||||||
|
"Movies": "चित्रपट",
|
||||||
|
"HeaderFavoriteArtists": "आवडते संगीतकार",
|
||||||
|
"Shows": "कार्यक्रम",
|
||||||
|
"HeaderFavoriteAlbums": "आवडते अल्बम",
|
||||||
|
"Channels": "वाहिन्या",
|
||||||
|
"ValueSpecialEpisodeName": "विशेष - {0}",
|
||||||
|
"HeaderFavoriteShows": "आवडते कार्यक्रम",
|
||||||
|
"Favorites": "आवडीचे",
|
||||||
|
"HeaderNextUp": "यानंतर",
|
||||||
|
"Songs": "गाणी",
|
||||||
|
"HeaderLiveTV": "लाइव्ह टीव्ही",
|
||||||
|
"Genres": "जाँनरे",
|
||||||
|
"Photos": "चित्र",
|
||||||
|
"TaskDownloadMissingSubtitles": "नसलेले सबटायटल डाउनलोड करा",
|
||||||
|
"TaskCleanTranscodeDescription": "एक दिवसापेक्षा जुन्या ट्रान्सकोड फायली काढून टाका.",
|
||||||
|
"TaskCleanTranscode": "ट्रान्सकोड डिरेक्टरी साफ करून टाका",
|
||||||
|
"TaskUpdatePlugins": "प्लगइन अपडेट करा",
|
||||||
|
"TaskCleanLogs": "लॉग डिरेक्टरी साफ करून टाका",
|
||||||
|
"TaskCleanCache": "कॅश डिरेक्टरी साफ करून टाका",
|
||||||
|
"TasksChannelsCategory": "इंटरनेट वाहिन्या",
|
||||||
|
"TasksApplicationCategory": "अॅप्लिकेशन",
|
||||||
|
"TasksLibraryCategory": "संग्रहालय",
|
||||||
|
"VersionNumber": "आवृत्ती {0}",
|
||||||
|
"UserPasswordChangedWithName": "{0} या प्रयोक्त्याचे पासवर्ड बदलण्यात आले आहे",
|
||||||
|
"UserOnlineFromDevice": "{0} हे {1} येथून ऑनलाइन आहेत",
|
||||||
|
"UserDeletedWithName": "प्रयोक्ता {0} काढून टाकण्यात आले आहे",
|
||||||
|
"UserCreatedWithName": "प्रयोक्ता {0} बनवण्यात आले आहे",
|
||||||
|
"User": "प्रयोक्ता",
|
||||||
|
"TvShows": "टीव्ही कार्यक्रम",
|
||||||
|
"StartupEmbyServerIsLoading": "जेलिफिन सर्व्हर लोड होत आहे. कृपया थोड्या वेळात पुन्हा प्रयत्न करा.",
|
||||||
|
"Plugin": "प्लगइन",
|
||||||
|
"NotificationOptionCameraImageUploaded": "कॅमेरा चित्र अपलोड केले आहे",
|
||||||
|
"NotificationOptionApplicationUpdateInstalled": "अॅप्लिकेशन अपडेट इन्स्टॉल केले आहे",
|
||||||
|
"NotificationOptionApplicationUpdateAvailable": "अॅप्लिकेशन अपडेट उपलब्ध आहे",
|
||||||
|
"NewVersionIsAvailable": "जेलिफिन सर्व्हरची एक नवीन आवृत्ती डाउनलोड करण्यास उपलब्ध आहे.",
|
||||||
|
"NameSeasonUnknown": "अज्ञात सीझन",
|
||||||
|
"NameSeasonNumber": "सीझन {0}",
|
||||||
|
"MusicVideos": "संगीत व्हिडीयो",
|
||||||
|
"Music": "संगीत",
|
||||||
|
"MessageApplicationUpdatedTo": "जेलिफिन सर्व्हर अपडेट होऊन {0} आवृत्तीवर पोहोचला आहे",
|
||||||
|
"MessageApplicationUpdated": "जेलिफिन सर्व्हर अपडेट केला गेला आहे",
|
||||||
|
"Latest": "नवीनतम",
|
||||||
|
"LabelIpAddressValue": "आयपी पत्ता: {0}",
|
||||||
|
"ItemRemovedWithName": "{0} हे संग्रहालयातून काढून टाकण्यात आले",
|
||||||
|
"ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले",
|
||||||
|
"HomeVideos": "घरचे व्हिडीयो",
|
||||||
|
"HeaderRecordingGroups": "रेकॉर्डिंग गट",
|
||||||
|
"HeaderCameraUploads": "कॅमेरा अपलोड",
|
||||||
|
"CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे",
|
||||||
|
"Application": "अॅप्लिकेशन",
|
||||||
|
"AppDeviceValues": "अॅप: {0}, यंत्र: {1}"
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"Albums": "Albums",
|
"Albums": "Albums",
|
||||||
"AppDeviceValues": "App: {0}, Apparaat: {1}",
|
"AppDeviceValues": "App: {0}, Apparaat: {1}",
|
||||||
"Application": "Applicatie",
|
"Application": "Programma",
|
||||||
"Artists": "Artiesten",
|
"Artists": "Artiesten",
|
||||||
"AuthenticationSucceededWithUserName": "{0} succesvol geauthenticeerd",
|
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
|
||||||
"Books": "Boeken",
|
"Books": "Boeken",
|
||||||
"CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd van {0}",
|
"CameraImageUploadedFrom": "Er is een nieuwe afbeelding toegevoegd via {0}",
|
||||||
"Channels": "Kanalen",
|
"Channels": "Kanalen",
|
||||||
"ChapterNameValue": "Hoofdstuk {0}",
|
"ChapterNameValue": "Hoofdstuk {0}",
|
||||||
"Collections": "Verzamelingen",
|
"Collections": "Verzamelingen",
|
||||||
@ -92,5 +92,27 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
|
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
|
"ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
|
||||||
"ValueSpecialEpisodeName": "Speciaal - {0}",
|
"ValueSpecialEpisodeName": "Speciaal - {0}",
|
||||||
"VersionNumber": "Versie {0}"
|
"VersionNumber": "Versie {0}",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar missende ondertitels gebaseerd op metadata configuratie.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Download missende ondertitels",
|
||||||
|
"TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.",
|
||||||
|
"TaskRefreshChannels": "Vernieuw Kanalen",
|
||||||
|
"TaskCleanTranscodeDescription": "Verwijder transcode bestanden ouder dan 1 dag.",
|
||||||
|
"TaskCleanLogs": "Log Folder Opschonen",
|
||||||
|
"TaskCleanTranscode": "Transcode Folder Opschonen",
|
||||||
|
"TaskUpdatePluginsDescription": "Download en installeert updates voor plugins waar automatisch updaten aan staat.",
|
||||||
|
"TaskUpdatePlugins": "Update Plugins",
|
||||||
|
"TaskRefreshPeopleDescription": "Update metadata for acteurs en regisseurs in de media bibliotheek.",
|
||||||
|
"TaskRefreshPeople": "Vernieuw Personen",
|
||||||
|
"TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.",
|
||||||
|
"TaskRefreshLibraryDescription": "Scant de media bibliotheek voor nieuwe bestanden en vernieuwt de metadata.",
|
||||||
|
"TaskRefreshLibrary": "Scan Media Bibliotheek",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Maakt thumbnails aan voor videos met hoofdstukken.",
|
||||||
|
"TaskRefreshChapterImages": "Hoofdstukafbeeldingen Uitpakken",
|
||||||
|
"TaskCleanCacheDescription": "Verwijder gecachte bestanden die het systeem niet langer nodig heeft.",
|
||||||
|
"TaskCleanCache": "Cache Folder Opschonen",
|
||||||
|
"TasksChannelsCategory": "Internet Kanalen",
|
||||||
|
"TasksApplicationCategory": "Applicatie",
|
||||||
|
"TasksLibraryCategory": "Bibliotheek",
|
||||||
|
"TasksMaintenanceCategory": "Onderhoud"
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"HeaderLiveTV": "TV em Direto",
|
"HeaderLiveTV": "TV em Direto",
|
||||||
"HeaderNextUp": "A Seguir",
|
"HeaderNextUp": "A Seguir",
|
||||||
"HeaderRecordingGroups": "Grupos de Gravação",
|
"HeaderRecordingGroups": "Grupos de Gravação",
|
||||||
"HomeVideos": "Home videos",
|
"HomeVideos": "Videos caseiros",
|
||||||
"Inherit": "Herdar",
|
"Inherit": "Herdar",
|
||||||
"ItemAddedWithName": "{0} foi adicionado à biblioteca",
|
"ItemAddedWithName": "{0} foi adicionado à biblioteca",
|
||||||
"ItemRemovedWithName": "{0} foi removido da biblioteca",
|
"ItemRemovedWithName": "{0} foi removido da biblioteca",
|
||||||
@ -92,5 +92,27 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}",
|
"UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia",
|
"ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia",
|
||||||
"ValueSpecialEpisodeName": "Especial - {0}",
|
"ValueSpecialEpisodeName": "Especial - {0}",
|
||||||
"VersionNumber": "Versão {0}"
|
"VersionNumber": "Versão {0}",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas em falta baseado na configuração de metadados.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Fazer download de legendas em falta",
|
||||||
|
"TaskRefreshChannelsDescription": "Atualizar informação sobre canais da Internet.",
|
||||||
|
"TaskRefreshChannels": "Atualizar Canais",
|
||||||
|
"TaskCleanTranscodeDescription": "Apagar ficheiros de transcode com mais de um dia.",
|
||||||
|
"TaskCleanTranscode": "Limpar a Diretoria de Transcode",
|
||||||
|
"TaskUpdatePluginsDescription": "Faz o download e instala updates para os plugins que estão configurados para atualizar automaticamente.",
|
||||||
|
"TaskUpdatePlugins": "Atualizar Plugins",
|
||||||
|
"TaskRefreshPeopleDescription": "Atualizar metadados para atores e diretores na biblioteca.",
|
||||||
|
"TaskRefreshPeople": "Atualizar Pessoas",
|
||||||
|
"TaskCleanLogsDescription": "Apagar ficheiros de log que têm mais de {0} dias.",
|
||||||
|
"TaskCleanLogs": "Limpar a Diretoria de Logs",
|
||||||
|
"TaskRefreshLibraryDescription": "Scannear a biblioteca de música para novos ficheiros e atualizar os metadados.",
|
||||||
|
"TaskRefreshLibrary": "Scannear Biblioteca de Música",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Criar thumbnails para os vídeos que têm capítulos.",
|
||||||
|
"TaskRefreshChapterImages": "Extrair Imagens dos Capítulos",
|
||||||
|
"TaskCleanCacheDescription": "Apagar ficheiros em cache que já não são necessários.",
|
||||||
|
"TaskCleanCache": "Limpar Cache",
|
||||||
|
"TasksChannelsCategory": "Canais da Internet",
|
||||||
|
"TasksApplicationCategory": "Aplicação",
|
||||||
|
"TasksLibraryCategory": "Biblioteca",
|
||||||
|
"TasksMaintenanceCategory": "Manutenção"
|
||||||
}
|
}
|
||||||
|
@ -91,5 +91,17 @@
|
|||||||
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
|
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
|
||||||
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
||||||
"Application": "Aplicação",
|
"Application": "Aplicação",
|
||||||
"AppDeviceValues": "Aplicação {0}, Dispositivo: {1}"
|
"AppDeviceValues": "Aplicação {0}, Dispositivo: {1}",
|
||||||
|
"TaskCleanCache": "Limpar Diretório de Cache",
|
||||||
|
"TasksApplicationCategory": "Aplicação",
|
||||||
|
"TasksLibraryCategory": "Biblioteca",
|
||||||
|
"TasksMaintenanceCategory": "Manutenção",
|
||||||
|
"TaskRefreshChannels": "Atualizar Canais",
|
||||||
|
"TaskUpdatePlugins": "Atualizar Plugins",
|
||||||
|
"TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.",
|
||||||
|
"TaskCleanLogs": "Limpar diretório de log",
|
||||||
|
"TaskRefreshLibrary": "Escanear biblioteca de mídias",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Criar miniaturas para videos que tem capítulos.",
|
||||||
|
"TaskCleanCacheDescription": "Deletar arquivos de cache que não são mais usados pelo sistema.",
|
||||||
|
"TasksChannelsCategory": "Canais de Internet"
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
"Channels": "Каналы",
|
"Channels": "Каналы",
|
||||||
"ChapterNameValue": "Сцена {0}",
|
"ChapterNameValue": "Сцена {0}",
|
||||||
"Collections": "Коллекции",
|
"Collections": "Коллекции",
|
||||||
"DeviceOfflineWithName": "{0} - подкл. разъ-но",
|
"DeviceOfflineWithName": "{0} - отключено",
|
||||||
"DeviceOnlineWithName": "{0} - подкл. уст-но",
|
"DeviceOnlineWithName": "{0} - подключено",
|
||||||
"FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна",
|
"FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна",
|
||||||
"Favorites": "Избранное",
|
"Favorites": "Избранное",
|
||||||
"Folders": "Папки",
|
"Folders": "Папки",
|
||||||
@ -26,30 +26,30 @@
|
|||||||
"HeaderLiveTV": "Эфир",
|
"HeaderLiveTV": "Эфир",
|
||||||
"HeaderNextUp": "Очередное",
|
"HeaderNextUp": "Очередное",
|
||||||
"HeaderRecordingGroups": "Группы записей",
|
"HeaderRecordingGroups": "Группы записей",
|
||||||
"HomeVideos": "Дом. видео",
|
"HomeVideos": "Домашнее видео",
|
||||||
"Inherit": "Наследуемое",
|
"Inherit": "Наследуемое",
|
||||||
"ItemAddedWithName": "{0} - добавлено в медиатеку",
|
"ItemAddedWithName": "{0} - добавлено в медиатеку",
|
||||||
"ItemRemovedWithName": "{0} - изъято из медиатеки",
|
"ItemRemovedWithName": "{0} - изъято из медиатеки",
|
||||||
"LabelIpAddressValue": "IP-адрес: {0}",
|
"LabelIpAddressValue": "IP-адрес: {0}",
|
||||||
"LabelRunningTimeValue": "Длительность: {0}",
|
"LabelRunningTimeValue": "Длительность: {0}",
|
||||||
"Latest": "Новейшее",
|
"Latest": "Последнее",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
|
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
|
||||||
"MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена",
|
"MessageServerConfigurationUpdated": "Конфигурация сервера была обновлена",
|
||||||
"MixedContent": "Смешанное содержимое",
|
"MixedContent": "Смешанное содержимое",
|
||||||
"Movies": "Кино",
|
"Movies": "Кино",
|
||||||
"Music": "Музыка",
|
"Music": "Музыка",
|
||||||
"MusicVideos": "Муз. видео",
|
"MusicVideos": "Музыкальные клипы",
|
||||||
"NameInstallFailed": "Установка {0} неудачна",
|
"NameInstallFailed": "Установка {0} неудачна",
|
||||||
"NameSeasonNumber": "Сезон {0}",
|
"NameSeasonNumber": "Сезон {0}",
|
||||||
"NameSeasonUnknown": "Сезон неопознан",
|
"NameSeasonUnknown": "Сезон неопознан",
|
||||||
"NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
|
"NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
|
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
|
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
|
||||||
"NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но",
|
"NotificationOptionAudioPlayback": "Воспроизведение аудио запущено",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Восп-ие аудио ост-но",
|
"NotificationOptionAudioPlaybackStopped": "Воспроизведение аудио остановлено",
|
||||||
"NotificationOptionCameraImageUploaded": "Произведена выкладка отснятого с камеры",
|
"NotificationOptionCameraImageUploaded": "Изображения с камеры загружены",
|
||||||
"NotificationOptionInstallationFailed": "Сбой установки",
|
"NotificationOptionInstallationFailed": "Сбой установки",
|
||||||
"NotificationOptionNewLibraryContent": "Новое содержание добавлено",
|
"NotificationOptionNewLibraryContent": "Новое содержание добавлено",
|
||||||
"NotificationOptionPluginError": "Сбой плагина",
|
"NotificationOptionPluginError": "Сбой плагина",
|
||||||
@ -59,8 +59,8 @@
|
|||||||
"NotificationOptionServerRestartRequired": "Требуется перезапуск сервера",
|
"NotificationOptionServerRestartRequired": "Требуется перезапуск сервера",
|
||||||
"NotificationOptionTaskFailed": "Сбой назначенной задачи",
|
"NotificationOptionTaskFailed": "Сбой назначенной задачи",
|
||||||
"NotificationOptionUserLockedOut": "Пользователь заблокирован",
|
"NotificationOptionUserLockedOut": "Пользователь заблокирован",
|
||||||
"NotificationOptionVideoPlayback": "Воспр-ие видео зап-но",
|
"NotificationOptionVideoPlayback": "Воспроизведение видео запущено",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Восп-ие видео ост-но",
|
"NotificationOptionVideoPlaybackStopped": "Воспроизведение видео остановлено",
|
||||||
"Photos": "Фото",
|
"Photos": "Фото",
|
||||||
"Playlists": "Плей-листы",
|
"Playlists": "Плей-листы",
|
||||||
"Plugin": "Плагин",
|
"Plugin": "Плагин",
|
||||||
@ -76,21 +76,43 @@
|
|||||||
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
|
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
|
||||||
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
|
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
|
||||||
"SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
|
"SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
|
||||||
"Sync": "Синхро",
|
"Sync": "Синхронизация",
|
||||||
"System": "Система",
|
"System": "Система",
|
||||||
"TvShows": "ТВ",
|
"TvShows": "ТВ",
|
||||||
"User": "Польз-ль",
|
"User": "Пользователь",
|
||||||
"UserCreatedWithName": "Пользователь {0} был создан",
|
"UserCreatedWithName": "Пользователь {0} был создан",
|
||||||
"UserDeletedWithName": "Пользователь {0} был удалён",
|
"UserDeletedWithName": "Пользователь {0} был удалён",
|
||||||
"UserDownloadingItemWithValues": "{0} загружает {1}",
|
"UserDownloadingItemWithValues": "{0} загружает {1}",
|
||||||
"UserLockedOutWithName": "Пользователь {0} был заблокирован",
|
"UserLockedOutWithName": "Пользователь {0} был заблокирован",
|
||||||
"UserOfflineFromDevice": "{0} - подкл. с {1} разъ-но",
|
"UserOfflineFromDevice": "{0} отключился с {1}",
|
||||||
"UserOnlineFromDevice": "{0} - подкл. с {1} уст-но",
|
"UserOnlineFromDevice": "{0} подключился с {1}",
|
||||||
"UserPasswordChangedWithName": "Пароль польз-ля {0} был изменён",
|
"UserPasswordChangedWithName": "Пароль пользователя {0} был изменён",
|
||||||
"UserPolicyUpdatedWithName": "Польз-ие политики {0} были обновлены",
|
"UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены",
|
||||||
"UserStartedPlayingItemWithValues": "{0} - воспр. «{1}» на {2}",
|
"UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} - воспр. «{1}» ост-но на {2}",
|
"UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)",
|
"ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)",
|
||||||
"ValueSpecialEpisodeName": "Спецэпизод - {0}",
|
"ValueSpecialEpisodeName": "Специальный эпизод - {0}",
|
||||||
"VersionNumber": "Версия {0}"
|
"VersionNumber": "Версия {0}",
|
||||||
|
"TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров",
|
||||||
|
"TaskRefreshChannels": "Обновление каналов",
|
||||||
|
"TaskCleanTranscode": "Очистка каталога перекодировки",
|
||||||
|
"TaskUpdatePlugins": "Обновление плагинов",
|
||||||
|
"TaskRefreshPeople": "Обновление метаданных людей",
|
||||||
|
"TaskCleanLogs": "Очистка каталога журналов",
|
||||||
|
"TaskRefreshLibrary": "Сканирование медиатеки",
|
||||||
|
"TaskRefreshChapterImages": "Извлечение изображений сцен",
|
||||||
|
"TaskCleanCache": "Очистка каталога кеша",
|
||||||
|
"TasksChannelsCategory": "Интернет-каналы",
|
||||||
|
"TasksApplicationCategory": "Приложение",
|
||||||
|
"TasksLibraryCategory": "Медиатека",
|
||||||
|
"TasksMaintenanceCategory": "Обслуживание",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Выполняется поиск в Интернете отсутствующих субтитров на основе конфигурации метаданных.",
|
||||||
|
"TaskRefreshChannelsDescription": "Обновляются данные интернет-каналов.",
|
||||||
|
"TaskCleanTranscodeDescription": "Удаляются файлы перекодировки старше одного дня.",
|
||||||
|
"TaskUpdatePluginsDescription": "Загружаются и устанавливаются обновления для плагинов, у которых включено автоматическое обновление.",
|
||||||
|
"TaskRefreshPeopleDescription": "Обновляются метаданные актеров и режиссёров в медиатеке.",
|
||||||
|
"TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).",
|
||||||
|
"TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.",
|
||||||
|
"TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе."
|
||||||
}
|
}
|
||||||
|
@ -92,5 +92,26 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}",
|
"UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek",
|
"ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek",
|
||||||
"ValueSpecialEpisodeName": "Specialavsnitt - {0}",
|
"ValueSpecialEpisodeName": "Specialavsnitt - {0}",
|
||||||
"VersionNumber": "Version {0}"
|
"VersionNumber": "Version {0}",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Söker på internet efter saknade undertexter baserad på metadatas konfiguration.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
|
||||||
|
"TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
|
||||||
|
"TaskRefreshChannels": "Uppdatera kanaler",
|
||||||
|
"TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.",
|
||||||
|
"TaskCleanTranscode": "Töm transkodningskatalog",
|
||||||
|
"TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
|
||||||
|
"TaskUpdatePlugins": "Uppdatera insticksprogram",
|
||||||
|
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
|
||||||
|
"TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.",
|
||||||
|
"TaskCleanLogs": "Töm loggkatalog",
|
||||||
|
"TaskRefreshLibraryDescription": "Söker igenom ditt mediabibliotek efter nya filer och förnyar metadata.",
|
||||||
|
"TaskRefreshLibrary": "Genomsök mediabibliotek",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Skapa miniatyrbilder för videor med kapitel.",
|
||||||
|
"TaskRefreshChapterImages": "Extrahera kapitelbilder",
|
||||||
|
"TaskCleanCacheDescription": "Radera cachade filer som systemet inte längre behöver.",
|
||||||
|
"TaskCleanCache": "Rensa cachekatalog",
|
||||||
|
"TasksChannelsCategory": "Internetkanaler",
|
||||||
|
"TasksApplicationCategory": "Applikation",
|
||||||
|
"TasksLibraryCategory": "Bibliotek",
|
||||||
|
"TasksMaintenanceCategory": "Underhåll"
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
"NotificationOptionAudioPlayback": "Ses çalma başladı",
|
"NotificationOptionAudioPlayback": "Ses çalma başladı",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu",
|
"NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu",
|
||||||
"NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi",
|
"NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi",
|
||||||
"NotificationOptionInstallationFailed": "Yükleme başarısız oldu",
|
"NotificationOptionInstallationFailed": "Kurulum hatası",
|
||||||
"NotificationOptionNewLibraryContent": "Yeni içerik eklendi",
|
"NotificationOptionNewLibraryContent": "Yeni içerik eklendi",
|
||||||
"NotificationOptionPluginError": "Eklenti hatası",
|
"NotificationOptionPluginError": "Eklenti hatası",
|
||||||
"NotificationOptionPluginInstalled": "Eklenti yüklendi",
|
"NotificationOptionPluginInstalled": "Eklenti yüklendi",
|
||||||
@ -95,7 +95,24 @@
|
|||||||
"VersionNumber": "Versiyon {0}",
|
"VersionNumber": "Versiyon {0}",
|
||||||
"TaskCleanCache": "Geçici dosya klasörünü temizle",
|
"TaskCleanCache": "Geçici dosya klasörünü temizle",
|
||||||
"TasksChannelsCategory": "İnternet kanalları",
|
"TasksChannelsCategory": "İnternet kanalları",
|
||||||
"TasksApplicationCategory": "Yazılım",
|
"TasksApplicationCategory": "Uygulama",
|
||||||
"TasksLibraryCategory": "Kütüphane",
|
"TasksLibraryCategory": "Kütüphane",
|
||||||
"TasksMaintenanceCategory": "Onarım"
|
"TasksMaintenanceCategory": "Onarım",
|
||||||
|
"TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Metadata ayarlarını baz alarak eksik altyazıları internette arar.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Eksik altyazıları indir",
|
||||||
|
"TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.",
|
||||||
|
"TaskRefreshChannels": "Kanalları Yenile",
|
||||||
|
"TaskCleanTranscodeDescription": "Bir günü dolmuş dönüştürme bilgisi içeren dosyaları siler.",
|
||||||
|
"TaskCleanTranscode": "Dönüşüm Dizinini Temizle",
|
||||||
|
"TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.",
|
||||||
|
"TaskUpdatePlugins": "Eklentileri Güncelle",
|
||||||
|
"TaskRefreshPeople": "Kullanıcıları Yenile",
|
||||||
|
"TaskCleanLogsDescription": "{0} günden eski log dosyalarını siler.",
|
||||||
|
"TaskCleanLogs": "Log Dizinini Temizle",
|
||||||
|
"TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve bilgileri yeniler.",
|
||||||
|
"TaskRefreshLibrary": "Medya Kütüphanesini Tara",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.",
|
||||||
|
"TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar",
|
||||||
|
"TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler."
|
||||||
}
|
}
|
||||||
|
117
Emby.Server.Implementations/Localization/Core/ur_PK.json
Normal file
117
Emby.Server.Implementations/Localization/Core/ur_PK.json
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"HeaderFavoriteAlbums": "پسندیدہ البمز",
|
||||||
|
"HeaderNextUp": "اگلا",
|
||||||
|
"HeaderFavoriteArtists": "پسندیدہ فنکار",
|
||||||
|
"HeaderAlbumArtists": "البم کے فنکار",
|
||||||
|
"Movies": "فلمیں",
|
||||||
|
"HeaderFavoriteEpisodes": "پسندیدہ اقساط",
|
||||||
|
"Collections": "مجموعہ",
|
||||||
|
"Folders": "فولڈرز",
|
||||||
|
"HeaderLiveTV": "براہ راست ٹی وی",
|
||||||
|
"Channels": "چینل",
|
||||||
|
"HeaderContinueWatching": "دیکھنا جاری رکھیں",
|
||||||
|
"Playlists": "پلے لسٹس",
|
||||||
|
"ValueSpecialEpisodeName": "خاص - {0}",
|
||||||
|
"Shows": "شوز",
|
||||||
|
"Genres": "انواع",
|
||||||
|
"Artists": "فنکار",
|
||||||
|
"Sync": "مطابقت",
|
||||||
|
"Photos": "تصوریں",
|
||||||
|
"Albums": "البم",
|
||||||
|
"Favorites": "پسندیدہ",
|
||||||
|
"Songs": "گانے",
|
||||||
|
"Books": "کتابیں",
|
||||||
|
"HeaderFavoriteSongs": "پسندیدہ گانے",
|
||||||
|
"HeaderFavoriteShows": "پسندیدہ شوز",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "میٹا ڈیٹا کی تشکیل پر مبنی ذیلی عنوانات کے غائب عنوانات انٹرنیٹ پے تلاش کرتا ہے۔",
|
||||||
|
"TaskDownloadMissingSubtitles": "غائب سب ٹائٹلز ڈاؤن لوڈ کریں",
|
||||||
|
"TaskRefreshChannelsDescription": "انٹرنیٹ چینل کی معلومات کو تازہ دم کرتا ہے۔",
|
||||||
|
"TaskRefreshChannels": "چینلز ریفریش کریں",
|
||||||
|
"TaskCleanTranscodeDescription": "ایک دن سے زیادہ پرانی ٹرانسکوڈ فائلوں کو حذف کرتا ہے۔",
|
||||||
|
"TaskCleanTranscode": "ٹرانس کوڈ ڈائرکٹری صاف کریں",
|
||||||
|
"TaskUpdatePluginsDescription": "پلگ انز کے لئے اپ ڈیٹس ڈاؤن لوڈ اور انسٹال کرتے ہیں جو خود بخود اپ ڈیٹ کرنے کیلئے تشکیل شدہ ہیں۔",
|
||||||
|
"TaskUpdatePlugins": "پلگ انز کو اپ ڈیٹ کریں",
|
||||||
|
"TaskRefreshPeopleDescription": "آپ کی میڈیا لائبریری میں اداکاروں اور ہدایت کاروں کے لئے میٹا ڈیٹا کی تازہ کاری۔",
|
||||||
|
"TaskRefreshPeople": "لوگوں کو تروتازہ کریں",
|
||||||
|
"TaskCleanLogsDescription": "لاگ فائلوں کو حذف کریں جو {0} دن سے زیادہ پرانی ہیں۔",
|
||||||
|
"TaskCleanLogs": "لاگ ڈائرکٹری کو صاف کریں",
|
||||||
|
"TaskRefreshLibraryDescription": "میڈیا لائبریری کو اسکین کرتا ھے ہر میٹا دیٹا کہ تازہ دم کرتا ھے.",
|
||||||
|
"TaskRefreshLibrary": "اسکین میڈیا لائبریری",
|
||||||
|
"TaskRefreshChapterImagesDescription": "بابوں والی ویڈیوز کے لئے تمبنیل بنایں۔",
|
||||||
|
"TaskRefreshChapterImages": "باب کی تصاویر نکالیں",
|
||||||
|
"TaskCleanCacheDescription": "فائلوں کو حذف کریں جنکی ضرورت نھیں ھے۔",
|
||||||
|
"TaskCleanCache": "کیش ڈائرکٹری کلیر کریں",
|
||||||
|
"TasksChannelsCategory": "انٹرنیٹ چینلز",
|
||||||
|
"TasksApplicationCategory": "پروگرام",
|
||||||
|
"TasksLibraryCategory": "لآیبریری",
|
||||||
|
"TasksMaintenanceCategory": "مرمت",
|
||||||
|
"VersionNumber": "ورژن {0}",
|
||||||
|
"ValueHasBeenAddedToLibrary": "{0} آپ کی میڈیا لائبریری میں شامل کر دیا گیا ہے",
|
||||||
|
"UserStoppedPlayingItemWithValues": "{0} نے {1} چلانا ختم کر دیا ھے {2} پے",
|
||||||
|
"UserStartedPlayingItemWithValues": "{0} چلا رہا ہے {1} {2} پے",
|
||||||
|
"UserPolicyUpdatedWithName": "صارف {0} کی پالیسی کیلئے تازہ کاری کی گئی ہے",
|
||||||
|
"UserPasswordChangedWithName": "صارف {0} کے لئے پاس ورڈ تبدیل کر دیا گیا ہے",
|
||||||
|
"UserOnlineFromDevice": "{0} آن لائن ہے {1} سے",
|
||||||
|
"UserOfflineFromDevice": "{0} سے منقطع ہوگیا ہے {1}",
|
||||||
|
"UserLockedOutWithName": "صارف {0} کو لاک آؤٹ کردیا گیا ہے",
|
||||||
|
"UserDownloadingItemWithValues": "{0} ڈاؤن لوڈ کر رھا ھے {1}",
|
||||||
|
"UserDeletedWithName": "صارف {0} کو ہٹا دیا گیا ہے",
|
||||||
|
"UserCreatedWithName": "صارف {0} تشکیل دیا گیا ہے",
|
||||||
|
"User": "صارف",
|
||||||
|
"TvShows": "ٹی وی کے پروگرام",
|
||||||
|
"System": "نظام",
|
||||||
|
"SubtitleDownloadFailureFromForItem": "ذیلی عنوانات {0} سے ڈاؤن لوڈ کرنے میں ناکام {1} کے لیے",
|
||||||
|
"StartupEmbyServerIsLoading": "جیلیفن سرور لوڈ ہورہا ہے۔ براہ کرم جلد ہی دوبارہ کوشش کریں۔",
|
||||||
|
"ServerNameNeedsToBeRestarted": "{0} دوبارہ چلانے کرنے کی ضرورت ہے",
|
||||||
|
"ScheduledTaskStartedWithName": "{0} شروع",
|
||||||
|
"ScheduledTaskFailedWithName": "{0} ناکام",
|
||||||
|
"ProviderValue": "فراہم کرنے والا: {0}",
|
||||||
|
"PluginUpdatedWithName": "{0} تازہ کاری کی گئی تھی",
|
||||||
|
"PluginUninstalledWithName": "[0} ہٹا دیا گیا تھا",
|
||||||
|
"PluginInstalledWithName": "{0} انسٹال کیا گیا تھا",
|
||||||
|
"Plugin": "پلگن",
|
||||||
|
"NotificationOptionVideoPlaybackStopped": "ویڈیو پلے بیک رک گیا",
|
||||||
|
"NotificationOptionVideoPlayback": "ویڈیو پلے بیک شروع ہوا",
|
||||||
|
"NotificationOptionUserLockedOut": "صارف کو لاک آؤٹ کیا گیا",
|
||||||
|
"NotificationOptionTaskFailed": "طے شدہ کام کی ناکامی",
|
||||||
|
"NotificationOptionServerRestartRequired": "سرور دوبارہ چلانے کرنے کی ضرورت ہے",
|
||||||
|
"NotificationOptionPluginUpdateInstalled": "پلگ ان اپ ڈیٹ انسٹال",
|
||||||
|
"NotificationOptionPluginUninstalled": "پلگ ان ہٹا دیا گیا",
|
||||||
|
"NotificationOptionPluginInstalled": "پلگ ان انسٹال ہوا",
|
||||||
|
"NotificationOptionPluginError": "پلگ ان کی ناکامی",
|
||||||
|
"NotificationOptionNewLibraryContent": "نیا مواد شامل کیا گیا",
|
||||||
|
"NotificationOptionInstallationFailed": "تنصیب کی ناکامی",
|
||||||
|
"NotificationOptionCameraImageUploaded": "کیمرے کی تصویر اپ لوڈ ہوگئی",
|
||||||
|
"NotificationOptionAudioPlaybackStopped": "آڈیو پلے بیک رک گیا",
|
||||||
|
"NotificationOptionAudioPlayback": "آڈیو پلے بیک شروع ہوا",
|
||||||
|
"NotificationOptionApplicationUpdateInstalled": "پروگرام اپ ڈیٹ انسٹال ہوچکا ھے",
|
||||||
|
"NotificationOptionApplicationUpdateAvailable": "پروگرام کی تازہ کاری دستیاب ہے",
|
||||||
|
"NewVersionIsAvailable": "جیلیفن سرور کا ایک نیا ورژن ڈاؤن لوڈ کے لئے دستیاب ہے۔",
|
||||||
|
"NameSeasonUnknown": "نامعلوم باب",
|
||||||
|
"NameSeasonNumber": "باب {0}",
|
||||||
|
"NameInstallFailed": "{0} تنصیب ناکام ہوگئی",
|
||||||
|
"MusicVideos": "موسیقی ویڈیو",
|
||||||
|
"Music": "موسیقی",
|
||||||
|
"MixedContent": "مخلوط مواد",
|
||||||
|
"MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے",
|
||||||
|
"MessageNamedServerConfigurationUpdatedWithValue": "سرور ضمن {0} کو ترتیب دے دیا گیا ھے",
|
||||||
|
"MessageApplicationUpdatedTo": "جیلیفن سرور کو اپ ڈیٹ کیا ہے {0}",
|
||||||
|
"MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے",
|
||||||
|
"Latest": "تازہ ترین",
|
||||||
|
"LabelRunningTimeValue": "چلانے کی مدت",
|
||||||
|
"LabelIpAddressValue": "ای پی پتے {0}",
|
||||||
|
"ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے",
|
||||||
|
"ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے",
|
||||||
|
"Inherit": "وراثت میں",
|
||||||
|
"HomeVideos": "ہوم ویڈیو",
|
||||||
|
"HeaderRecordingGroups": "ریکارڈنگ گروپس",
|
||||||
|
"HeaderCameraUploads": "کیمرہ اپلوڈز",
|
||||||
|
"FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}",
|
||||||
|
"DeviceOnlineWithName": "{0} متصل ھو چکا ھے",
|
||||||
|
"DeviceOfflineWithName": "{0} منقطع ھو چکا ھے",
|
||||||
|
"ChapterNameValue": "باب",
|
||||||
|
"AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے",
|
||||||
|
"CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
|
||||||
|
"Application": "پروگرام",
|
||||||
|
"AppDeviceValues": "پروگرام:{0}, آلہ:{1}"
|
||||||
|
}
|
@ -20,7 +20,7 @@
|
|||||||
"HeaderContinueWatching": "繼續觀賞",
|
"HeaderContinueWatching": "繼續觀賞",
|
||||||
"HeaderFavoriteAlbums": "最愛專輯",
|
"HeaderFavoriteAlbums": "最愛專輯",
|
||||||
"HeaderFavoriteArtists": "最愛演出者",
|
"HeaderFavoriteArtists": "最愛演出者",
|
||||||
"HeaderFavoriteEpisodes": "最愛級數",
|
"HeaderFavoriteEpisodes": "最愛影集",
|
||||||
"HeaderFavoriteShows": "最愛節目",
|
"HeaderFavoriteShows": "最愛節目",
|
||||||
"HeaderFavoriteSongs": "最愛歌曲",
|
"HeaderFavoriteSongs": "最愛歌曲",
|
||||||
"HeaderLiveTV": "電視直播",
|
"HeaderLiveTV": "電視直播",
|
||||||
@ -50,10 +50,10 @@
|
|||||||
"NotificationOptionCameraImageUploaded": "相機相片已上傳",
|
"NotificationOptionCameraImageUploaded": "相機相片已上傳",
|
||||||
"NotificationOptionInstallationFailed": "安裝失敗",
|
"NotificationOptionInstallationFailed": "安裝失敗",
|
||||||
"NotificationOptionNewLibraryContent": "已新增新內容",
|
"NotificationOptionNewLibraryContent": "已新增新內容",
|
||||||
"NotificationOptionPluginError": "擴充元件錯誤",
|
"NotificationOptionPluginError": "插件安裝錯誤",
|
||||||
"NotificationOptionPluginInstalled": "擴充元件已安裝",
|
"NotificationOptionPluginInstalled": "插件已安裝",
|
||||||
"NotificationOptionPluginUninstalled": "擴充元件已移除",
|
"NotificationOptionPluginUninstalled": "插件已移除",
|
||||||
"NotificationOptionPluginUpdateInstalled": "已更新擴充元件",
|
"NotificationOptionPluginUpdateInstalled": "插件已更新",
|
||||||
"NotificationOptionServerRestartRequired": "伺服器需要重新啟動",
|
"NotificationOptionServerRestartRequired": "伺服器需要重新啟動",
|
||||||
"NotificationOptionTaskFailed": "排程任務失敗",
|
"NotificationOptionTaskFailed": "排程任務失敗",
|
||||||
"NotificationOptionUserLockedOut": "使用者已鎖定",
|
"NotificationOptionUserLockedOut": "使用者已鎖定",
|
||||||
@ -61,7 +61,7 @@
|
|||||||
"NotificationOptionVideoPlaybackStopped": "影片停止播放",
|
"NotificationOptionVideoPlaybackStopped": "影片停止播放",
|
||||||
"Photos": "相片",
|
"Photos": "相片",
|
||||||
"Playlists": "播放清單",
|
"Playlists": "播放清單",
|
||||||
"Plugin": "外掛",
|
"Plugin": "插件",
|
||||||
"PluginInstalledWithName": "{0} 已安裝",
|
"PluginInstalledWithName": "{0} 已安裝",
|
||||||
"PluginUninstalledWithName": "{0} 已移除",
|
"PluginUninstalledWithName": "{0} 已移除",
|
||||||
"PluginUpdatedWithName": "{0} 已更新",
|
"PluginUpdatedWithName": "{0} 已更新",
|
||||||
@ -91,5 +91,27 @@
|
|||||||
"VersionNumber": "版本 {0}",
|
"VersionNumber": "版本 {0}",
|
||||||
"HeaderRecordingGroups": "錄製組",
|
"HeaderRecordingGroups": "錄製組",
|
||||||
"Inherit": "繼承",
|
"Inherit": "繼承",
|
||||||
"SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕"
|
"SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "在網路上透過描述資料搜尋遺失的字幕。",
|
||||||
|
"TaskDownloadMissingSubtitles": "下載遺失的字幕",
|
||||||
|
"TaskRefreshChannels": "重新整理頻道",
|
||||||
|
"TaskUpdatePlugins": "更新插件",
|
||||||
|
"TaskRefreshPeople": "重新整理人員",
|
||||||
|
"TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔案。",
|
||||||
|
"TaskCleanLogs": "清空紀錄資料夾",
|
||||||
|
"TaskRefreshLibraryDescription": "掃描媒體庫內新的檔案並重新整理描述資料。",
|
||||||
|
"TaskRefreshLibrary": "掃描媒體庫",
|
||||||
|
"TaskRefreshChapterImages": "擷取章節圖片",
|
||||||
|
"TaskCleanCacheDescription": "刪除系統長時間不需要的快取。",
|
||||||
|
"TaskCleanCache": "清除快取資料夾",
|
||||||
|
"TasksLibraryCategory": "媒體庫",
|
||||||
|
"TaskRefreshChannelsDescription": "重新整理網絡頻道資料。",
|
||||||
|
"TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
|
||||||
|
"TaskCleanTranscode": "清除轉碼資料夾",
|
||||||
|
"TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。",
|
||||||
|
"TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。",
|
||||||
|
"TaskRefreshChapterImagesDescription": "為有章節的視頻創建縮圖。",
|
||||||
|
"TasksChannelsCategory": "網絡頻道",
|
||||||
|
"TasksApplicationCategory": "應用程式",
|
||||||
|
"TasksMaintenanceCategory": "維修"
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,6 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
|
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
|
||||||
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
|
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _configuration manager.
|
|
||||||
/// </summary>
|
|
||||||
private readonly IServerConfigurationManager _configurationManager;
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
@ -32,22 +32,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue =
|
private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue =
|
||||||
new ConcurrentQueue<Tuple<Type, TaskOptions>>();
|
new ConcurrentQueue<Tuple<Type, TaskOptions>>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the json serializer.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The json serializer.</value>
|
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the application paths.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The application paths.</value>
|
|
||||||
private readonly IApplicationPaths _applicationPaths;
|
private readonly IApplicationPaths _applicationPaths;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the logger.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The logger.</value>
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
@ -56,17 +42,17 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="applicationPaths">The application paths.</param>
|
/// <param name="applicationPaths">The application paths.</param>
|
||||||
/// <param name="jsonSerializer">The json serializer.</param>
|
/// <param name="jsonSerializer">The json serializer.</param>
|
||||||
/// <param name="loggerFactory">The logger factory.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="fileSystem">The filesystem manager.</param>
|
/// <param name="fileSystem">The filesystem manager.</param>
|
||||||
public TaskManager(
|
public TaskManager(
|
||||||
IApplicationPaths applicationPaths,
|
IApplicationPaths applicationPaths,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<TaskManager> logger,
|
||||||
IFileSystem fileSystem)
|
IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
_applicationPaths = applicationPaths;
|
_applicationPaths = applicationPaths;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(TaskManager));
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
|
||||||
ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
|
ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
|
||||||
|
@ -55,9 +55,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
{
|
{
|
||||||
progress.Report(0);
|
progress.Report(0);
|
||||||
|
|
||||||
var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
|
var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken);
|
||||||
.ToListAsync(cancellationToken)
|
var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList();
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
progress.Report(10);
|
progress.Report(10);
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ namespace Emby.Server.Implementations.Security
|
|||||||
{
|
{
|
||||||
public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository
|
public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository
|
||||||
{
|
{
|
||||||
public AuthenticationRepository(ILoggerFactory loggerFactory, IServerConfigurationManager config)
|
public AuthenticationRepository(ILogger<AuthenticationRepository> logger, IServerConfigurationManager config)
|
||||||
: base(loggerFactory.CreateLogger(nameof(AuthenticationRepository)))
|
: base(logger)
|
||||||
{
|
{
|
||||||
DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db");
|
DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db");
|
||||||
}
|
}
|
||||||
|
@ -1414,7 +1414,7 @@ namespace Emby.Server.Implementations.Session
|
|||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request));
|
AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request));
|
||||||
throw new SecurityException("Invalid username or password entered.");
|
throw new AuthenticationException("Invalid username or password entered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(request.DeviceId)
|
if (!string.IsNullOrEmpty(request.DeviceId)
|
||||||
|
@ -3,8 +3,10 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -18,17 +20,23 @@ using MediaBrowser.Model.Events;
|
|||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Updates;
|
using MediaBrowser.Model.Updates;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Updates
|
namespace Emby.Server.Implementations.Updates
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages all install, uninstall and update operations (both plugins and system).
|
/// Manages all install, uninstall, and update operations for the system and individual plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class InstallationManager : IInstallationManager
|
public class InstallationManager : IInstallationManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _logger.
|
/// The key for a setting that specifies a URL for the plugin repository JSON manifest.
|
||||||
|
/// </summary>
|
||||||
|
public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
@ -44,6 +52,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
private readonly IApplicationHost _applicationHost;
|
private readonly IApplicationHost _applicationHost;
|
||||||
|
|
||||||
private readonly IZipClient _zipClient;
|
private readonly IZipClient _zipClient;
|
||||||
|
private readonly IConfiguration _appConfig;
|
||||||
|
|
||||||
private readonly object _currentInstallationsLock = new object();
|
private readonly object _currentInstallationsLock = new object();
|
||||||
|
|
||||||
@ -65,7 +74,8 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IZipClient zipClient)
|
IZipClient zipClient,
|
||||||
|
IConfiguration appConfig)
|
||||||
{
|
{
|
||||||
if (logger == null)
|
if (logger == null)
|
||||||
{
|
{
|
||||||
@ -83,6 +93,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
_config = config;
|
_config = config;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_zipClient = zipClient;
|
_zipClient = zipClient;
|
||||||
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -101,10 +112,10 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
|
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
|
public event EventHandler<GenericEventArgs<(IPlugin, VersionInfo)>> PluginUpdated;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
|
public event EventHandler<GenericEventArgs<VersionInfo>> PluginInstalled;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
|
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
|
||||||
@ -112,19 +123,43 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using (var response = await _httpClient.SendAsync(
|
var manifestUrl = _appConfig.GetValue<string>(PluginManifestUrlKey);
|
||||||
new HttpRequestOptions
|
|
||||||
{
|
try
|
||||||
Url = "https://repo.jellyfin.org/releases/plugin/manifest.json",
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
CacheMode = CacheMode.Unconditional,
|
|
||||||
CacheLength = TimeSpan.FromMinutes(3)
|
|
||||||
},
|
|
||||||
HttpMethod.Get).ConfigureAwait(false))
|
|
||||||
using (Stream stream = response.Content)
|
|
||||||
{
|
{
|
||||||
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(
|
using (var response = await _httpClient.SendAsync(
|
||||||
stream).ConfigureAwait(false);
|
new HttpRequestOptions
|
||||||
|
{
|
||||||
|
Url = manifestUrl,
|
||||||
|
CancellationToken = cancellationToken,
|
||||||
|
CacheMode = CacheMode.Unconditional,
|
||||||
|
CacheLength = TimeSpan.FromMinutes(3)
|
||||||
|
},
|
||||||
|
HttpMethod.Get).ConfigureAwait(false))
|
||||||
|
using (Stream stream = response.Content)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (SerializationException ex)
|
||||||
|
{
|
||||||
|
const string LogTemplate =
|
||||||
|
"Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " +
|
||||||
|
"have specified a custom plugin repository manifest URL with --plugin-manifest-url or " +
|
||||||
|
PluginManifestUrlKey + ", please ensure that it is correct.";
|
||||||
|
_logger.LogError(ex, LogTemplate, manifestUrl);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (UriFormatException ex)
|
||||||
|
{
|
||||||
|
const string LogTemplate =
|
||||||
|
"The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " +
|
||||||
|
"Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey;
|
||||||
|
_logger.LogError(ex, LogTemplate, manifestUrl);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,60 +183,56 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
|
public IEnumerable<VersionInfo> GetCompatibleVersions(
|
||||||
IEnumerable<PackageVersionInfo> availableVersions,
|
IEnumerable<VersionInfo> availableVersions,
|
||||||
Version minVersion = null,
|
Version minVersion = null)
|
||||||
PackageVersionClass classification = PackageVersionClass.Release)
|
|
||||||
{
|
{
|
||||||
var appVer = _applicationHost.ApplicationVersion;
|
var appVer = _applicationHost.ApplicationVersion;
|
||||||
availableVersions = availableVersions
|
availableVersions = availableVersions
|
||||||
.Where(x => x.classification == classification
|
.Where(x => Version.Parse(x.targetAbi) <= appVer);
|
||||||
&& Version.Parse(x.requiredVersionStr) <= appVer);
|
|
||||||
|
|
||||||
if (minVersion != null)
|
if (minVersion != null)
|
||||||
{
|
{
|
||||||
availableVersions = availableVersions.Where(x => x.Version >= minVersion);
|
availableVersions = availableVersions.Where(x => x.version >= minVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
return availableVersions.OrderByDescending(x => x.Version);
|
return availableVersions.OrderByDescending(x => x.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
|
public IEnumerable<VersionInfo> GetCompatibleVersions(
|
||||||
IEnumerable<PackageInfo> availablePackages,
|
IEnumerable<PackageInfo> availablePackages,
|
||||||
string name = null,
|
string name = null,
|
||||||
Guid guid = default,
|
Guid guid = default,
|
||||||
Version minVersion = null,
|
Version minVersion = null)
|
||||||
PackageVersionClass classification = PackageVersionClass.Release)
|
|
||||||
{
|
{
|
||||||
var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
|
var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
|
||||||
|
|
||||||
// Package not found.
|
// Package not found in repository
|
||||||
if (package == null)
|
if (package == null)
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<PackageVersionInfo>();
|
return Enumerable.Empty<VersionInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetCompatibleVersions(
|
return GetCompatibleVersions(
|
||||||
package.versions,
|
package.versions,
|
||||||
minVersion,
|
minVersion);
|
||||||
classification);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
public async Task<IEnumerable<VersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
|
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
|
||||||
|
return GetAvailablePluginUpdates(catalog);
|
||||||
|
}
|
||||||
|
|
||||||
var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
|
private IEnumerable<VersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
|
||||||
|
{
|
||||||
// Figure out what needs to be installed
|
|
||||||
foreach (var plugin in _applicationHost.Plugins)
|
foreach (var plugin in _applicationHost.Plugins)
|
||||||
{
|
{
|
||||||
var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel);
|
var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version);
|
||||||
var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
|
var version = compatibleversions.FirstOrDefault(y => y.version > plugin.Version);
|
||||||
if (version != null
|
if (version != null && !CompletedInstallations.Any(x => string.Equals(x.Guid, version.guid, StringComparison.OrdinalIgnoreCase)))
|
||||||
&& !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
|
|
||||||
{
|
{
|
||||||
yield return version;
|
yield return version;
|
||||||
}
|
}
|
||||||
@ -209,7 +240,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken)
|
public async Task InstallPackage(VersionInfo package, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (package == null)
|
if (package == null)
|
||||||
{
|
{
|
||||||
@ -218,11 +249,9 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
|
|
||||||
var installationInfo = new InstallationInfo
|
var installationInfo = new InstallationInfo
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Guid = package.guid,
|
||||||
Name = package.name,
|
Name = package.name,
|
||||||
AssemblyGuid = package.guid,
|
Version = package.version.ToString()
|
||||||
UpdateClass = package.classification,
|
|
||||||
Version = package.versionStr
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var innerCancellationTokenSource = new CancellationTokenSource();
|
var innerCancellationTokenSource = new CancellationTokenSource();
|
||||||
@ -240,7 +269,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
var installationEventArgs = new InstallationEventArgs
|
var installationEventArgs = new InstallationEventArgs
|
||||||
{
|
{
|
||||||
InstallationInfo = installationInfo,
|
InstallationInfo = installationInfo,
|
||||||
PackageVersionInfo = package
|
VersionInfo = package
|
||||||
};
|
};
|
||||||
|
|
||||||
PackageInstalling?.Invoke(this, installationEventArgs);
|
PackageInstalling?.Invoke(this, installationEventArgs);
|
||||||
@ -265,7 +294,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
_currentInstallations.Remove(tuple);
|
_currentInstallations.Remove(tuple);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.versionStr);
|
_logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.version);
|
||||||
|
|
||||||
PackageInstallationCancelled?.Invoke(this, installationEventArgs);
|
PackageInstallationCancelled?.Invoke(this, installationEventArgs);
|
||||||
|
|
||||||
@ -301,7 +330,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
/// <param name="package">The package.</param>
|
/// <param name="package">The package.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns><see cref="Task" />.</returns>
|
/// <returns><see cref="Task" />.</returns>
|
||||||
private async Task InstallPackageInternal(PackageVersionInfo package, CancellationToken cancellationToken)
|
private async Task InstallPackageInternal(VersionInfo package, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Set last update time if we were installed before
|
// Set last update time if we were installed before
|
||||||
IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
|
IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
|
||||||
@ -313,26 +342,26 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
// Do plugin-specific processing
|
// Do plugin-specific processing
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
|
_logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.version);
|
||||||
|
|
||||||
PluginInstalled?.Invoke(this, new GenericEventArgs<PackageVersionInfo>(package));
|
PluginInstalled?.Invoke(this, new GenericEventArgs<VersionInfo>(package));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
|
_logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.version);
|
||||||
|
|
||||||
PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, PackageVersionInfo)>((plugin, package)));
|
PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, VersionInfo)>((plugin, package)));
|
||||||
}
|
}
|
||||||
|
|
||||||
_applicationHost.NotifyPendingRestart();
|
_applicationHost.NotifyPendingRestart();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PerformPackageInstallation(PackageVersionInfo package, CancellationToken cancellationToken)
|
private async Task PerformPackageInstallation(VersionInfo package, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(package.targetFilename);
|
var extension = Path.GetExtension(package.filename);
|
||||||
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename);
|
_logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.filename);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,7 +408,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uninstalls a plugin
|
/// Uninstalls a plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="plugin">The plugin.</param>
|
/// <param name="plugin">The plugin.</param>
|
||||||
public void UninstallPlugin(IPlugin plugin)
|
public void UninstallPlugin(IPlugin plugin)
|
||||||
@ -437,7 +466,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
{
|
{
|
||||||
lock (_currentInstallationsLock)
|
lock (_currentInstallationsLock)
|
||||||
{
|
{
|
||||||
var install = _currentInstallations.Find(x => x.info.Id == id);
|
var install = _currentInstallations.Find(x => x.info.Guid == id.ToString());
|
||||||
if (install == default((InstallationInfo, CancellationTokenSource)))
|
if (install == default((InstallationInfo, CancellationTokenSource)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Net.Mime;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Jellyfin.Api
|
namespace Jellyfin.Api
|
||||||
@ -7,6 +8,7 @@ namespace Jellyfin.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
|
[Produces(MediaTypeNames.Application.Json)]
|
||||||
public class BaseJellyfinApiController : ControllerBase
|
public class BaseJellyfinApiController : ControllerBase
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user