Merge branch 'master' into nullable4

This commit is contained in:
Bond_009 2020-04-23 11:29:19 +02:00
commit 118f30059c
207 changed files with 3223 additions and 5136 deletions

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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
View File

@ -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
View 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": [
]
}

View File

@ -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

View File

@ -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"]

View File

@ -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"]

View File

@ -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;
} }
} }
} }

View File

@ -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>

View File

@ -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);

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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[]

View File

@ -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>

View File

@ -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;
} }

View File

@ -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; }
} }
} }

View File

@ -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,
} }
} }

View File

@ -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>

View File

@ -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" />

View File

@ -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)
{ {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
} }
} }

View File

@ -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))

View File

@ -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;

View File

@ -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);
} }

View File

@ -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;
} }

View File

@ -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
{ {

View File

@ -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))

View File

@ -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
{ {

View File

@ -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 }

View File

@ -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;
} }
} }
} }

View File

@ -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)
{ {

View File

@ -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");

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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>

View File

@ -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 />

View File

@ -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 />

View File

@ -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)

View File

@ -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
{ {

View File

@ -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);
} }
} }

View File

@ -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))

View File

@ -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())
{ {

View File

@ -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; }
} }
} }

View File

@ -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;

View File

@ -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))

View File

@ -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,

View File

@ -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;
} }

View File

@ -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)

View File

@ -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)
{ {

View File

@ -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)
{ {

View File

@ -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();
} }
} }

View File

@ -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)));
}
} }
} }

View File

@ -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;

View File

@ -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,

View File

@ -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": "حذف دليل السجل"
} }

View File

@ -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",

View File

@ -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."
} }

View File

@ -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",

View File

@ -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"
} }

View File

@ -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",

View File

@ -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",

View File

@ -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": "تعمیر"
} }

View File

@ -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"
} }

View File

@ -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"
} }

View File

@ -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",

View File

@ -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}",

View File

@ -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": "不足している字幕をダウンロードする"
} }

View 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}"
}

View File

@ -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"
} }

View File

@ -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"
} }

View File

@ -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"
} }

View File

@ -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": "Удаляются файлы кэша, которые больше не нужны системе."
} }

View File

@ -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"
} }

View File

@ -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."
} }

View 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}"
}

View File

@ -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": "維修"
} }

View File

@ -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;

View File

@ -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>();

View File

@ -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);

View File

@ -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");
} }

View File

@ -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)

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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)

View File

@ -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();

View File

@ -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();
} }

View File

@ -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>

View File

@ -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");

View File

@ -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"
} }
} }
} }

View File

@ -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