mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Merge branch 'master' into nullable4
This commit is contained in:
commit
118f30059c
@ -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);
|
||||||
|
|
||||||
|
@ -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,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>{08FFF49B-F175-4807-A2B5-73B0EBD9F716}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
@ -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,21 +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)
|
||||||
{
|
{
|
||||||
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;
|
||||||
@ -307,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;
|
||||||
@ -327,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,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;
|
||||||
@ -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
|
||||||
});
|
});
|
||||||
@ -416,7 +422,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 +434,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 +451,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 +463,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 +491,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 +566,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;
|
||||||
|
@ -11,22 +11,17 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
{
|
{
|
||||||
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(
|
public ActivityManager(IActivityRepository repo, IUserManager userManager)
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
IActivityRepository repo,
|
|
||||||
IUserManager userManager)
|
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger(nameof(ActivityManager));
|
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
public void Create(ActivityLogEntry entry)
|
||||||
{
|
{
|
||||||
entry.Date = DateTime.UtcNow;
|
entry.Date = DateTime.UtcNow;
|
||||||
|
@ -17,11 +17,12 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
{
|
{
|
||||||
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)
|
public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
||||||
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
|
: base(logger)
|
||||||
{
|
{
|
||||||
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
|
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
@ -76,8 +77,6 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
|
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
public void Create(ActivityLogEntry entry)
|
||||||
{
|
{
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
@ -87,32 +86,34 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
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);
|
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("@Overview", entry.Overview);
|
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
|
||||||
{
|
{
|
||||||
statement.TryBindNull("@UserId");
|
statement.TryBind("@Name", entry.Name);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
statement.MoveNext();
|
if (entry.UserId.Equals(Guid.Empty))
|
||||||
}
|
{
|
||||||
}, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,33 +126,35 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
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);
|
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("@Name", entry.Name);
|
|
||||||
statement.TryBind("@Overview", entry.Overview);
|
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
|
||||||
{
|
{
|
||||||
statement.TryBindNull("@UserId");
|
statement.TryBind("@Id", entry.Id);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
statement.TryBind("@Name", entry.Name);
|
||||||
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
statement.TryBind("@Overview", entry.Overview);
|
||||||
|
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
||||||
|
statement.TryBind("@Type", entry.Type);
|
||||||
|
statement.TryBind("@ItemId", entry.ItemId);
|
||||||
|
|
||||||
statement.MoveNext();
|
if (entry.UserId.Equals(Guid.Empty))
|
||||||
}
|
{
|
||||||
}, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +167,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
{
|
{
|
||||||
whereClauses.Add("DateCreated>=@DateCreated");
|
whereClauses.Add("DateCreated>=@DateCreated");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserId.HasValue)
|
if (hasUserId.HasValue)
|
||||||
{
|
{
|
||||||
if (hasUserId.Value)
|
if (hasUserId.Value)
|
||||||
@ -204,7 +208,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[]
|
||||||
@ -304,7 +308,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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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))
|
||||||
@ -66,6 +67,7 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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))
|
||||||
@ -82,6 +84,7 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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))
|
||||||
|
@ -20,6 +20,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Name => "Channel Image Provider";
|
||||||
|
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return GetChannel(item).GetSupportedChannelImages();
|
return GetChannel(item).GetSupportedChannelImages();
|
||||||
@ -32,8 +34,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
return channel.GetChannelImage(type, cancellationToken);
|
return channel.GetChannelImage(type, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Channel Image Provider";
|
|
||||||
|
|
||||||
public bool Supports(BaseItem item)
|
public bool Supports(BaseItem item)
|
||||||
{
|
{
|
||||||
return item is Channel;
|
return item is Channel;
|
||||||
|
@ -31,8 +31,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
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 +41,16 @@ 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);
|
||||||
|
|
||||||
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 +60,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,6 +68,8 @@ 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);
|
||||||
|
|
||||||
public void AddParts(IEnumerable<IChannel> channels)
|
public void AddParts(IEnumerable<IChannel> channels)
|
||||||
@ -85,8 +90,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EnableMediaProbe(BaseItem item)
|
public bool EnableMediaProbe(BaseItem item)
|
||||||
@ -146,15 +150,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 +173,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,9 +189,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 +216,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +226,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();
|
||||||
@ -256,11 +257,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>
|
||||||
@ -341,8 +340,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,11 +364,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
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 +381,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))
|
||||||
@ -444,18 +438,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,10 +469,12 @@ 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;
|
||||||
}
|
}
|
||||||
@ -509,12 +508,12 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelFeatures GetChannelFeatures(string id)
|
public ChannelFeatures GetChannelFeatures(string id)
|
||||||
@ -532,13 +531,13 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
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,
|
public ChannelFeatures GetChannelFeaturesDto(
|
||||||
|
Channel channel,
|
||||||
IChannel provider,
|
IChannel provider,
|
||||||
InternalChannelFeatures features)
|
InternalChannelFeatures features)
|
||||||
{
|
{
|
||||||
@ -567,6 +566,7 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,7 +614,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 +640,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,13 +653,15 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -672,7 +676,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 +689,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 +711,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -735,7 +742,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 +749,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 +767,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,11 +789,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = new InternalChannelItemQuery
|
var query = new InternalChannelItemQuery
|
||||||
@ -833,7 +835,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 +846,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 +860,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
filename += "-sortField-" + sortField.Value;
|
filename += "-sortField-" + sortField.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortDescending)
|
if (sortDescending)
|
||||||
{
|
{
|
||||||
filename += "-sortDescending";
|
filename += "-sortDescending";
|
||||||
@ -865,7 +868,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,
|
||||||
@ -981,7 +985,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 +1017,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 +1058,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 +1066,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 +1085,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 +1158,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);
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,12 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
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)
|
public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
@ -7,29 +7,26 @@ 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
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|
||||||
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 +60,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 +69,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
|
|
||||||
// Every so often
|
// Every so often
|
||||||
new TaskTriggerInfo
|
new TaskTriggerInfo
|
||||||
{
|
{
|
||||||
|
@ -46,9 +46,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
{
|
{
|
||||||
var subItem = i;
|
var subItem = i;
|
||||||
|
|
||||||
var episode = subItem as Episode;
|
if (subItem is Episode episode)
|
||||||
|
|
||||||
if (episode != null)
|
|
||||||
{
|
{
|
||||||
var series = episode.Series;
|
var series = episode.Series;
|
||||||
if (series != null && series.HasImage(ImageType.Primary))
|
if (series != null && series.HasImage(ImageType.Primary))
|
||||||
|
@ -52,7 +52,9 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
|
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
|
||||||
|
|
||||||
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
|
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
|
||||||
|
|
||||||
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
|
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
|
||||||
|
|
||||||
private IEnumerable<Folder> FindFolders(string path)
|
private IEnumerable<Folder> FindFolders(string path)
|
||||||
@ -109,9 +111,9 @@ 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>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoxSet CreateCollection(CollectionCreationOptions options)
|
public BoxSet CreateCollection(CollectionCreationOptions options)
|
||||||
@ -191,7 +193,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");
|
||||||
@ -289,10 +290,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
|
||||||
{
|
{
|
||||||
|
@ -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 }
|
||||||
|
@ -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>
|
||||||
@ -1993,7 +1992,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
chapter.ImageTag = ImageProcessor.GetImageCacheTag(item, chapter);
|
chapter.ImageTag = _imageProcessor.GetImageCacheTag(item, chapter);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -3322,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;
|
||||||
}
|
}
|
||||||
@ -3346,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)
|
||||||
{
|
{
|
||||||
@ -3358,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3387,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)
|
||||||
@ -3525,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();
|
||||||
@ -3539,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)
|
||||||
{
|
{
|
||||||
@ -3553,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)
|
||||||
{
|
{
|
||||||
@ -3567,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)
|
||||||
{
|
{
|
||||||
@ -3581,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;
|
||||||
@ -3693,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)
|
||||||
@ -3767,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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3799,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);
|
||||||
}
|
}
|
||||||
@ -3813,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
|
||||||
@ -3872,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)
|
||||||
@ -3909,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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3934,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)
|
||||||
@ -3950,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))
|
||||||
@ -3982,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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4017,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);
|
||||||
}
|
}
|
||||||
@ -4036,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);
|
||||||
}
|
}
|
||||||
@ -4769,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);
|
||||||
@ -4854,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),
|
||||||
@ -4923,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 };
|
||||||
}
|
}
|
||||||
@ -4935,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)
|
||||||
{
|
{
|
||||||
@ -4952,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)
|
||||||
{
|
{
|
||||||
@ -4988,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))
|
||||||
{
|
{
|
||||||
@ -5548,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);
|
||||||
@ -5589,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)
|
||||||
{
|
{
|
||||||
@ -5680,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);
|
||||||
@ -5731,7 +5595,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex += limit;
|
startIndex += Limit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5766,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);
|
||||||
@ -5811,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))
|
||||||
{
|
{
|
||||||
@ -5927,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);
|
||||||
@ -5948,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()))
|
||||||
@ -6014,7 +5883,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex += limit;
|
startIndex += Limit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6031,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.EnableHttps).Append(Separator)
|
.Append(_appHost.EnableHttps).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>
|
||||||
@ -451,7 +455,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
|
||||||
@ -497,8 +501,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)
|
||||||
@ -530,22 +532,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",
|
||||||
|
@ -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",
|
||||||
@ -92,5 +92,27 @@
|
|||||||
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
|
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
|
||||||
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
|
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
|
||||||
"ValueSpecialEpisodeName": "Özel - {0}",
|
"ValueSpecialEpisodeName": "Özel - {0}",
|
||||||
"VersionNumber": "Versiyon {0}"
|
"VersionNumber": "Versiyon {0}",
|
||||||
|
"TaskCleanCache": "Geçici dosya klasörünü temizle",
|
||||||
|
"TasksChannelsCategory": "İnternet kanalları",
|
||||||
|
"TasksApplicationCategory": "Uygulama",
|
||||||
|
"TasksLibraryCategory": "Kütüphane",
|
||||||
|
"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,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>{DFBEFB4C-DA19-4143-98B7-27320C7F7163}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
@ -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>{154872D9-6C12-4007-96E3-8F70A58386CE}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
@ -78,12 +78,21 @@ namespace Jellyfin.Drawing.Skia
|
|||||||
=> new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
|
=> new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test to determine if the native lib is available.
|
/// Check if the native lib is available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void TestSkia()
|
/// <returns>True if the native lib is available, otherwise false.</returns>
|
||||||
|
public static bool IsNativeLibAvailable()
|
||||||
{
|
{
|
||||||
// test an operation that requires the native library
|
try
|
||||||
SKPMColor.PreMultiply(SKColors.Black);
|
{
|
||||||
|
// test an operation that requires the native library
|
||||||
|
SKPMColor.PreMultiply(SKColors.Black);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsTransparent(SKColor color)
|
private static bool IsTransparent(SKColor color)
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Emby.Drawing;
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
|
using Jellyfin.Drawing.Skia;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Server
|
namespace Jellyfin.Server
|
||||||
@ -20,27 +24,40 @@ namespace Jellyfin.Server
|
|||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="imageEncoder">The <see cref="IImageEncoder" /> to be used by the <see cref="CoreAppHost" />.</param>
|
|
||||||
/// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
public CoreAppHost(
|
public CoreAppHost(
|
||||||
ServerApplicationPaths applicationPaths,
|
ServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
StartupOptions options,
|
StartupOptions options,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IImageEncoder imageEncoder,
|
|
||||||
INetworkManager networkManager)
|
INetworkManager networkManager)
|
||||||
: base(
|
: base(
|
||||||
applicationPaths,
|
applicationPaths,
|
||||||
loggerFactory,
|
loggerFactory,
|
||||||
options,
|
options,
|
||||||
fileSystem,
|
fileSystem,
|
||||||
imageEncoder,
|
|
||||||
networkManager)
|
networkManager)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc/>
|
||||||
public override bool CanSelfRestart => StartupOptions.RestartPath != null;
|
protected override void RegisterServices(IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
// Register an image encoder
|
||||||
|
bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable();
|
||||||
|
Type imageEncoderType = useSkiaEncoder
|
||||||
|
? typeof(SkiaEncoder)
|
||||||
|
: typeof(NullImageEncoder);
|
||||||
|
serviceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType);
|
||||||
|
|
||||||
|
// Log a warning if the Skia encoder could not be used
|
||||||
|
if (!useSkiaEncoder)
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
base.RegisterServices(serviceCollection);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void RestartInternal() => Program.Restart();
|
protected override void RestartInternal() => Program.Restart();
|
||||||
|
@ -71,6 +71,11 @@ namespace Jellyfin.Server.Extensions
|
|||||||
// Clear app parts to avoid other assemblies being picked up
|
// Clear app parts to avoid other assemblies being picked up
|
||||||
.ConfigureApplicationPartManager(a => a.ApplicationParts.Clear())
|
.ConfigureApplicationPartManager(a => a.ApplicationParts.Clear())
|
||||||
.AddApplicationPart(typeof(StartupController).Assembly)
|
.AddApplicationPart(typeof(StartupController).Assembly)
|
||||||
|
.AddJsonOptions(options =>
|
||||||
|
{
|
||||||
|
// Setting the naming policy to null leaves the property names as-is when serializing objects to JSON.
|
||||||
|
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||||
|
})
|
||||||
.AddControllersAsServices();
|
.AddControllersAsServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>{07E39F42-A2C6-4B32-AF8C-725F957A73FF}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>jellyfin</AssemblyName>
|
<AssemblyName>jellyfin</AssemblyName>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
|
@ -184,7 +184,6 @@ namespace Jellyfin.Server
|
|||||||
_loggerFactory,
|
_loggerFactory,
|
||||||
options,
|
options,
|
||||||
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
||||||
GetImageEncoder(appPaths),
|
|
||||||
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()));
|
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()));
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -204,14 +203,13 @@ namespace Jellyfin.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
ServiceCollection serviceCollection = new ServiceCollection();
|
ServiceCollection serviceCollection = new ServiceCollection();
|
||||||
await appHost.InitAsync(serviceCollection, startupConfig).ConfigureAwait(false);
|
appHost.Init(serviceCollection);
|
||||||
|
|
||||||
var webHost = CreateWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
|
var webHost = CreateWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
|
||||||
|
|
||||||
// Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection.
|
// Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection.
|
||||||
appHost.ServiceProvider = webHost.Services;
|
appHost.ServiceProvider = webHost.Services;
|
||||||
appHost.InitializeServices();
|
await appHost.InitializeServices().ConfigureAwait(false);
|
||||||
appHost.FindParts();
|
|
||||||
Migrations.MigrationRunner.Run(appHost, _loggerFactory);
|
Migrations.MigrationRunner.Run(appHost, _loggerFactory);
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -571,25 +569,6 @@ namespace Jellyfin.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IImageEncoder GetImageEncoder(IApplicationPaths appPaths)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Test if the native lib is available
|
|
||||||
SkiaEncoder.TestSkia();
|
|
||||||
|
|
||||||
return new SkiaEncoder(
|
|
||||||
_loggerFactory.CreateLogger<SkiaEncoder>(),
|
|
||||||
appPaths);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, $"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NullImageEncoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void StartNewInstance(StartupOptions options)
|
private static void StartNewInstance(StartupOptions options)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Starting new instance");
|
_logger.LogInformation("Starting new instance");
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
},
|
},
|
||||||
"Jellyfin.Server (nowebclient)": {
|
"Jellyfin.Server (nowebclient)": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "--nowebclient",
|
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
},
|
||||||
|
"commandLineArgs": "--nowebclient"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
|
using Emby.Server.Implementations.Updates;
|
||||||
using MediaBrowser.Controller.Extensions;
|
using MediaBrowser.Controller.Extensions;
|
||||||
|
|
||||||
namespace Jellyfin.Server
|
namespace Jellyfin.Server
|
||||||
@ -76,6 +76,10 @@ namespace Jellyfin.Server
|
|||||||
[Option("restartargs", Required = false, HelpText = "Arguments for restart script.")]
|
[Option("restartargs", Required = false, HelpText = "Arguments for restart script.")]
|
||||||
public string? RestartArgs { get; set; }
|
public string? RestartArgs { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")]
|
||||||
|
public string? PluginManifestUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the command line options as a dictionary that can be used in the .NET configuration system.
|
/// Gets the command line options as a dictionary that can be used in the .NET configuration system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -84,6 +88,11 @@ namespace Jellyfin.Server
|
|||||||
{
|
{
|
||||||
var config = new Dictionary<string, string>();
|
var config = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
if (PluginManifestUrl != null)
|
||||||
|
{
|
||||||
|
config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl);
|
||||||
|
}
|
||||||
|
|
||||||
if (NoWebClient)
|
if (NoWebClient)
|
||||||
{
|
{
|
||||||
config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
|
config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
|
||||||
|
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