Merge remote-tracking branch 'upstream/master' into external-id-type

This commit is contained in:
Mark Monteiro 2020-06-26 10:12:22 -04:00
commit 0e9164351b
1006 changed files with 16804 additions and 9076 deletions

View File

@ -33,6 +33,13 @@ jobs:
packageType: sdk packageType: sdk
version: ${{ parameters.DotNetSdkVersion }} version: ${{ parameters.DotNetSdkVersion }}
- task: DotNetCoreCLI@2
displayName: 'Install ABI CompatibilityChecker tool'
inputs:
command: custom
custom: tool
arguments: 'update compatibilitychecker -g'
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: "Download New Assembly Build Artifact" displayName: "Download New Assembly Build Artifact"
inputs: inputs:
@ -72,25 +79,11 @@ jobs:
overWrite: true overWrite: true
flattenFolders: true flattenFolders: true
- task: DownloadGitHubRelease@0
displayName: "Download ABI Compatibility Check Tool"
inputs:
connection: Jellyfin Release Download
userRepository: EraYaN/dotnet-compatibility
defaultVersionType: "latest"
itemPattern: "**-ci.zip"
downloadPath: "$(System.ArtifactsDirectory)"
- task: ExtractFiles@1
displayName: "Extract ABI Compatibility Check Tool"
inputs:
archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
destinationFolder: $(System.ArtifactsDirectory)/tools
cleanDestinationFolder: true
# The `--warnings-only` switch will swallow the return code and not emit any errors. # The `--warnings-only` switch will swallow the return code and not emit any errors.
- task: CmdLine@2 - task: DotNetCoreCLI@2
displayName: "Execute ABI Compatibility Check Tool" displayName: 'Execute ABI Compatibility Check Tool'
inputs: inputs:
script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only" command: custom
custom: compat
arguments: 'current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
workingDirectory: $(System.ArtifactsDirectory) workingDirectory: $(System.ArtifactsDirectory)

View File

@ -1,6 +1,6 @@
parameters: parameters:
LinuxImage: "ubuntu-latest" LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj" RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
DotNetSdkVersion: 3.1.100 DotNetSdkVersion: 3.1.100
jobs: jobs:
@ -13,7 +13,7 @@ jobs:
Debug: Debug:
BuildConfiguration: Debug BuildConfiguration: Debug
pool: pool:
vmImage: "${{ parameters.LinuxImage }}" vmImage: '${{ parameters.LinuxImage }}'
steps: steps:
- checkout: self - checkout: self
clean: true clean: true
@ -21,7 +21,7 @@ jobs:
persistCredentials: true persistCredentials: true
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: "Download Web Branch" displayName: 'Download Web Branch'
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion') condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
inputs: inputs:
path: '$(Agent.TempDirectory)' path: '$(Agent.TempDirectory)'
@ -32,7 +32,7 @@ jobs:
runBranch: variables['Build.SourceBranch'] runBranch: variables['Build.SourceBranch']
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: "Download Web Target" displayName: 'Download Web Target'
condition: eq(variables['Build.Reason'], 'PullRequest') condition: eq(variables['Build.Reason'], 'PullRequest')
inputs: inputs:
path: '$(Agent.TempDirectory)' path: '$(Agent.TempDirectory)'
@ -43,51 +43,51 @@ jobs:
runBranch: variables['System.PullRequest.TargetBranch'] runBranch: variables['System.PullRequest.TargetBranch']
- task: ExtractFiles@1 - task: ExtractFiles@1
displayName: "Extract Web Client" displayName: 'Extract Web Client'
inputs: inputs:
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip' archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard' destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
cleanDestinationFolder: false cleanDestinationFolder: false
- task: UseDotNet@2 - task: UseDotNet@2
displayName: "Update DotNet" displayName: 'Update DotNet'
inputs: inputs:
packageType: sdk packageType: sdk
version: ${{ parameters.DotNetSdkVersion }} version: ${{ parameters.DotNetSdkVersion }}
- task: DotNetCoreCLI@2 - task: DotNetCoreCLI@2
displayName: "Publish Server" displayName: 'Publish Server'
inputs: inputs:
command: publish command: publish
publishWebProjects: false publishWebProjects: false
projects: "${{ parameters.RestoreBuildProjects }}" projects: '${{ parameters.RestoreBuildProjects }}'
arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)" arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: false zipAfterPublish: false
- task: PublishPipelineArtifact@0 - task: PublishPipelineArtifact@0
displayName: "Publish Artifact Naming" displayName: 'Publish Artifact Naming'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs: inputs:
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll" targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
artifactName: "Jellyfin.Naming" artifactName: 'Jellyfin.Naming'
- task: PublishPipelineArtifact@0 - task: PublishPipelineArtifact@0
displayName: "Publish Artifact Controller" displayName: 'Publish Artifact Controller'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs: inputs:
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll" targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
artifactName: "Jellyfin.Controller" artifactName: 'Jellyfin.Controller'
- task: PublishPipelineArtifact@0 - task: PublishPipelineArtifact@0
displayName: "Publish Artifact Model" displayName: 'Publish Artifact Model'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs: inputs:
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll" targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
artifactName: "Jellyfin.Model" artifactName: 'Jellyfin.Model'
- task: PublishPipelineArtifact@0 - task: PublishPipelineArtifact@0
displayName: "Publish Artifact Common" displayName: 'Publish Artifact Common'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs: inputs:
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll" targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
artifactName: "Jellyfin.Common" artifactName: 'Jellyfin.Common'

View File

@ -0,0 +1,131 @@
jobs:
- job: BuildPackage
displayName: 'Build Packages'
strategy:
matrix:
CentOS.amd64:
BuildConfiguration: centos.amd64
Fedora.amd64:
BuildConfiguration: fedora.amd64
Debian.amd64:
BuildConfiguration: debian.amd64
Debian.arm64:
BuildConfiguration: debian.arm64
Debian.armhf:
BuildConfiguration: debian.armhf
Ubuntu.amd64:
BuildConfiguration: ubuntu.amd64
Ubuntu.arm64:
BuildConfiguration: ubuntu.arm64
Ubuntu.armhf:
BuildConfiguration: ubuntu.armhf
Linux.amd64:
BuildConfiguration: linux.amd64
Windows.amd64:
BuildConfiguration: windows.amd64
MacOS:
BuildConfiguration: macos
Portable:
BuildConfiguration: portable
pool:
vmImage: 'ubuntu-latest'
steps:
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
displayName: 'Run Dockerfile (unstable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
displayName: 'Run Dockerfile (stable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
artifactName: 'jellyfin-server-$(BuildConfiguration)'
- task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
sshEndpoint: repository
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
contents: '**'
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- job: BuildDocker
displayName: 'Build Docker'
strategy:
matrix:
amd64:
BuildConfiguration: amd64
arm64:
BuildConfiguration: arm64
armhf:
BuildConfiguration: armhf
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Docker@2
displayName: 'Push Unstable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
repository: 'jellyfin/jellyfin-server'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
containerRegistry: Docker Hub
tags: |
unstable-$(Build.BuildNumber)-$(BuildConfiguration)
unstable-$(BuildConfiguration)
- task: Docker@2
displayName: 'Push Stable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
repository: 'jellyfin/jellyfin-server'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
containerRegistry: Docker Hub
tags: |
stable-$(Build.BuildNumber)-$(BuildConfiguration)
stable-$(BuildConfiguration)
- job: CollectArtifacts
displayName: 'Collect Artifacts'
dependsOn:
- BuildPackage
- BuildDocker
condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: SSH@0
displayName: 'Update Unstable Repository'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable'
- task: SSH@0
displayName: 'Update Stable Repository'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)'

View File

@ -45,6 +45,7 @@ jobs:
- task: SonarCloudPrepare@1 - task: SonarCloudPrepare@1
displayName: 'Prepare analysis on SonarCloud' displayName: 'Prepare analysis on SonarCloud'
condition: eq(variables['ImageName'], 'ubuntu-latest') condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
inputs: inputs:
SonarCloud: 'Sonarcloud for Jellyfin' SonarCloud: 'Sonarcloud for Jellyfin'
organization: 'jellyfin' organization: 'jellyfin'
@ -63,14 +64,17 @@ jobs:
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@1
displayName: 'Run Code Analysis' displayName: 'Run Code Analysis'
condition: eq(variables['ImageName'], 'ubuntu-latest') condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
- task: SonarCloudPublish@1 - task: SonarCloudPublish@1
displayName: 'Publish Quality Gate Result' displayName: 'Publish Quality Gate Result'
condition: eq(variables['ImageName'], 'ubuntu-latest') condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Run ReportGenerator' displayName: 'Run ReportGenerator'
enabled: false
inputs: inputs:
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml" reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
targetdir: "$(Agent.TempDirectory)/merged/" targetdir: "$(Agent.TempDirectory)/merged/"
@ -80,10 +84,10 @@ jobs:
- task: PublishCodeCoverageResults@1 - task: PublishCodeCoverageResults@1
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Publish Code Coverage' displayName: 'Publish Code Coverage'
enabled: false
inputs: inputs:
codeCoverageTool: "cobertura" codeCoverageTool: "cobertura"
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2 #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml" summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
pathToSources: $(Build.SourcesDirectory) pathToSources: $(Build.SourcesDirectory)
failIfCoverageEmpty: true failIfCoverageEmpty: true

View File

@ -2,9 +2,9 @@ name: $(Date:yyyyMMdd)$(Rev:.r)
variables: variables:
- name: TestProjects - name: TestProjects
value: "tests/**/*Tests.csproj" value: 'tests/**/*Tests.csproj'
- name: RestoreBuildProjects - name: RestoreBuildProjects
value: "Jellyfin.Server/Jellyfin.Server.csproj" value: 'Jellyfin.Server/Jellyfin.Server.csproj'
- name: DotNetSdkVersion - name: DotNetSdkVersion
value: 3.1.100 value: 3.1.100
@ -17,17 +17,17 @@ trigger:
jobs: jobs:
- template: azure-pipelines-main.yml - template: azure-pipelines-main.yml
parameters: parameters:
LinuxImage: "ubuntu-latest" LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: $(RestoreBuildProjects) RestoreBuildProjects: $(RestoreBuildProjects)
- template: azure-pipelines-test.yml - template: azure-pipelines-test.yml
parameters: parameters:
ImageNames: ImageNames:
Linux: "ubuntu-latest" Linux: 'ubuntu-latest'
Windows: "windows-latest" Windows: 'windows-latest'
macOS: "macos-latest" macOS: 'macos-latest'
- template: azure-pipelines-compat.yml - template: azure-pipelines-abi.yml
parameters: parameters:
Packages: Packages:
Naming: Naming:
@ -42,4 +42,6 @@ jobs:
Common: Common:
NugetPackageName: Jellyfin.Common NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll AssemblyFileName: MediaBrowser.Common.dll
LinuxImage: "ubuntu-latest" LinuxImage: 'ubuntu-latest'
- template: azure-pipelines-package.yml

9
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: nuget
directory: "/"
schedule:
interval: weekly
time: '12:00'
open-pull-requests-limit: 10

3
.gitignore vendored
View File

@ -39,7 +39,6 @@ ProgramData*/
CorePlugins*/ CorePlugins*/
ProgramData-Server*/ ProgramData-Server*/
ProgramData-UI*/ ProgramData-UI*/
MediaBrowser.WebDashboard/jellyfin-web/**
################# #################
## Visual Studio ## Visual Studio
@ -276,4 +275,4 @@ BenchmarkDotNet.Artifacts
# Ignore web artifacts from native builds # Ignore web artifacts from native builds
web/ web/
web-src.* web-src.*
MediaBrowser.WebDashboard/jellyfin-web/ MediaBrowser.WebDashboard/jellyfin-web

12
.vscode/launch.json vendored
View File

@ -1,9 +1,6 @@
{ {
// Use IntelliSense to find out which attributes exist for C# debugging "version": "0.2.0",
// Use hover for the description of the existing attributes "configurations": [
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{ {
"name": ".NET Core Launch (console)", "name": ".NET Core Launch (console)",
"type": "coreclr", "type": "coreclr",
@ -24,5 +21,8 @@
"request": "attach", "request": "attach",
"processId": "${command:pickProcess}" "processId": "${command:pickProcess}"
} }
,] ],
"env": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
}
} }

19
.vscode/tasks.json vendored
View File

@ -10,6 +10,21 @@
"${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj" "${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj"
], ],
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
},
{
"label": "api tests",
"command": "dotnet",
"type": "process",
"args": [
"test",
"${workspaceFolder}/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj"
],
"problemMatcher": "$msCompile"
} }
] ],
} "options": {
"env": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
}
}
}

View File

@ -7,6 +7,7 @@
- [anthonylavado](https://github.com/anthonylavado) - [anthonylavado](https://github.com/anthonylavado)
- [Artiume](https://github.com/Artiume) - [Artiume](https://github.com/Artiume)
- [AThomsen](https://github.com/AThomsen) - [AThomsen](https://github.com/AThomsen)
- [barronpm](https://github.com/barronpm)
- [bilde2910](https://github.com/bilde2910) - [bilde2910](https://github.com/bilde2910)
- [bfayers](https://github.com/bfayers) - [bfayers](https://github.com/bfayers)
- [BnMcG](https://github.com/BnMcG) - [BnMcG](https://github.com/BnMcG)
@ -130,6 +131,7 @@
- [XVicarious](https://github.com/XVicarious) - [XVicarious](https://github.com/XVicarious)
- [YouKnowBlom](https://github.com/YouKnowBlom) - [YouKnowBlom](https://github.com/YouKnowBlom)
- [KristupasSavickas](https://github.com/KristupasSavickas) - [KristupasSavickas](https://github.com/KristupasSavickas)
- [Pusta](https://github.com/pusta)
# Emby Contributors # Emby Contributors

View File

@ -2,7 +2,7 @@ ARG DOTNET_VERSION=3.1
FROM node:alpine as web-builder FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& yarn install \ && yarn install \

View File

@ -38,7 +38,7 @@ COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
curl -s https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \ curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \ echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \ echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
apt-get update && \ apt-get update && \

View File

@ -7,6 +7,7 @@ namespace DvdLib.Ifo
public class Cell public class Cell
{ {
public CellPlaybackInfo PlaybackInfo { get; private set; } public CellPlaybackInfo PlaybackInfo { get; private set; }
public CellPositionInfo PositionInfo { get; private set; } public CellPositionInfo PositionInfo { get; private set; }
internal void ParsePlayback(BinaryReader br) internal void ParsePlayback(BinaryReader br)

View File

@ -5,7 +5,9 @@ namespace DvdLib.Ifo
public class Chapter public class Chapter
{ {
public ushort ProgramChainNumber { get; private set; } public ushort ProgramChainNumber { get; private set; }
public ushort ProgramNumber { get; private set; } public ushort ProgramNumber { get; private set; }
public uint ChapterNumber { get; private set; } public uint ChapterNumber { get; private set; }
public Chapter(ushort pgcNum, ushort programNum, uint chapterNum) public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)

View File

@ -117,12 +117,19 @@ namespace DvdLib.Ifo
uint chapNum = 1; uint chapNum = 1;
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin); vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1)); var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
if (t == null) continue; if (t == null)
{
continue;
}
do do
{ {
t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum)); t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) break; if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1]))
{
break;
}
chapNum++; chapNum++;
} }
while (vtsFs.Position < (baseAddr + endaddr)); while (vtsFs.Position < (baseAddr + endaddr));
@ -147,7 +154,10 @@ namespace DvdLib.Ifo
uint vtsPgcOffset = vtsRead.ReadUInt32(); uint vtsPgcOffset = vtsRead.ReadUInt32();
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum)); var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
if (t != null) t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum); if (t != null)
{
t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
}
} }
} }
} }

View File

@ -15,8 +15,14 @@ namespace DvdLib.Ifo
Second = GetBCDValue(data[2]); Second = GetBCDValue(data[2]);
Frames = GetBCDValue((byte)(data[3] & 0x3F)); Frames = GetBCDValue((byte)(data[3] & 0x3F));
if ((data[3] & 0x80) != 0) FrameRate = 30; if ((data[3] & 0x80) != 0)
else if ((data[3] & 0x40) != 0) FrameRate = 25; {
FrameRate = 30;
}
else if ((data[3] & 0x40) != 0)
{
FrameRate = 25;
}
} }
private static byte GetBCDValue(byte data) private static byte GetBCDValue(byte data)

View File

@ -6,7 +6,7 @@ namespace DvdLib.Ifo
{ {
public class Program public class Program
{ {
public readonly List<Cell> Cells; public IReadOnlyList<Cell> Cells { get; }
public Program(List<Cell> cells) public Program(List<Cell> cells)
{ {

View File

@ -22,7 +22,9 @@ namespace DvdLib.Ifo
public readonly List<Cell> Cells; public readonly List<Cell> Cells;
public DvdTime PlaybackTime { get; private set; } public DvdTime PlaybackTime { get; private set; }
public UserOperation ProhibitedUserOperations { get; private set; } public UserOperation ProhibitedUserOperations { get; private set; }
public byte[] AudioStreamControl { get; private set; } // 8*2 entries public byte[] AudioStreamControl { get; private set; } // 8*2 entries
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
@ -33,9 +35,11 @@ namespace DvdLib.Ifo
private ushort _goupProgramNumber; private ushort _goupProgramNumber;
public ProgramPlaybackMode PlaybackMode { get; private set; } public ProgramPlaybackMode PlaybackMode { get; private set; }
public uint ProgramCount { get; private set; } public uint ProgramCount { get; private set; }
public byte StillTime { get; private set; } public byte StillTime { get; private set; }
public byte[] Palette { get; private set; } // 16*4 entries public byte[] Palette { get; private set; } // 16*4 entries
private ushort _commandTableOffset; private ushort _commandTableOffset;
@ -71,8 +75,15 @@ namespace DvdLib.Ifo
StillTime = br.ReadByte(); StillTime = br.ReadByte();
byte pbMode = br.ReadByte(); byte pbMode = br.ReadByte();
if (pbMode == 0) PlaybackMode = ProgramPlaybackMode.Sequential; if (pbMode == 0)
else PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle; {
PlaybackMode = ProgramPlaybackMode.Sequential;
}
else
{
PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
}
ProgramCount = (uint)(pbMode & 0x7F); ProgramCount = (uint)(pbMode & 0x7F);
Palette = br.ReadBytes(64); Palette = br.ReadBytes(64);

View File

@ -8,8 +8,11 @@ namespace DvdLib.Ifo
public class Title public class Title
{ {
public uint TitleNumber { get; private set; } public uint TitleNumber { get; private set; }
public uint AngleCount { get; private set; } public uint AngleCount { get; private set; }
public ushort ChapterCount { get; private set; } public ushort ChapterCount { get; private set; }
public byte VideoTitleSetNumber { get; private set; } public byte VideoTitleSetNumber { get; private set; }
private ushort _parentalManagementMask; private ushort _parentalManagementMask;
@ -17,6 +20,7 @@ namespace DvdLib.Ifo
private uint _vtsStartSector; // relative to start of entire disk private uint _vtsStartSector; // relative to start of entire disk
public ProgramChain EntryProgramChain { get; private set; } public ProgramChain EntryProgramChain { get; private set; }
public readonly List<ProgramChain> ProgramChains; public readonly List<ProgramChain> ProgramChains;
public readonly List<Chapter> Chapters; public readonly List<Chapter> Chapters;
@ -55,7 +59,10 @@ namespace DvdLib.Ifo
var pgc = new ProgramChain(pgcNum); var pgc = new ProgramChain(pgcNum);
pgc.ParseHeader(br); pgc.ParseHeader(br);
ProgramChains.Add(pgc); ProgramChains.Add(pgc);
if (entryPgc) EntryProgramChain = pgc; if (entryPgc)
{
EntryProgramChain = pgc;
}
br.BaseStream.Seek(curPos, SeekOrigin.Begin); br.BaseStream.Seek(curPos, SeekOrigin.Begin);
} }

View File

@ -1,13 +1,15 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.TV; using MediaBrowser.Controller.TV;
@ -31,7 +33,8 @@ namespace Emby.Dlna.ContentDirectory
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ITVSeriesManager _tvSeriesManager; private readonly ITVSeriesManager _tvSeriesManager;
public ContentDirectory(IDlnaManager dlna, public ContentDirectory(
IDlnaManager dlna,
IUserDataManager userDataManager, IUserDataManager userDataManager,
IImageProcessor imageProcessor, IImageProcessor imageProcessor,
ILibraryManager libraryManager, ILibraryManager libraryManager,
@ -130,18 +133,13 @@ namespace Emby.Dlna.ContentDirectory
foreach (var user in _userManager.Users) foreach (var user in _userManager.Users)
{ {
if (user.Policy.IsAdministrator) if (user.HasPermission(PermissionKind.IsAdministrator))
{ {
return user; return user;
} }
} }
foreach (var user in _userManager.Users) return _userManager.Users.FirstOrDefault();
{
return user;
}
return null;
} }
} }
} }

View File

@ -10,6 +10,7 @@ using System.Threading;
using System.Xml; using System.Xml;
using Emby.Dlna.Didl; using Emby.Dlna.Didl;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@ -17,7 +18,6 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
@ -28,6 +28,12 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Dlna.ContentDirectory namespace Emby.Dlna.ContentDirectory
{ {
@ -460,12 +466,12 @@ namespace Emby.Dlna.ContentDirectory
} }
else if (search.SearchType == SearchType.Playlist) else if (search.SearchType == SearchType.Playlist)
{ {
//items = items.OfType<Playlist>(); // items = items.OfType<Playlist>();
isFolder = true; isFolder = true;
} }
else if (search.SearchType == SearchType.MusicAlbum) else if (search.SearchType == SearchType.MusicAlbum)
{ {
//items = items.OfType<MusicAlbum>(); // items = items.OfType<MusicAlbum>();
isFolder = true; isFolder = true;
} }
@ -731,7 +737,7 @@ namespace Emby.Dlna.ContentDirectory
return GetGenres(item, user, query); return GetGenres(item, user, query);
} }
var array = new ServerItem[] var array = new[]
{ {
new ServerItem(item) new ServerItem(item)
{ {
@ -920,7 +926,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query) private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
{ {
query.Recursive = true; query.Recursive = true;
//query.Parent = parent; // query.Parent = parent;
query.SetUser(user); query.SetUser(user);
query.IncludeItemTypes = new[] { typeof(BoxSet).Name }; query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
@ -1115,7 +1121,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query) private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query)
{ {
query.Parent = null; query.Parent = null;
query.IncludeItemTypes = new[] { typeof(Playlist).Name }; query.IncludeItemTypes = new[] { nameof(Playlist) };
query.SetUser(user); query.SetUser(user);
query.Recursive = true; query.Recursive = true;
@ -1132,10 +1138,9 @@ namespace Emby.Dlna.ContentDirectory
{ {
UserId = user.Id, UserId = user.Id,
Limit = 50, Limit = 50,
IncludeItemTypes = new[] { typeof(Audio).Name }, IncludeItemTypes = new[] { nameof(Audio) },
ParentId = parent == null ? Guid.Empty : parent.Id, ParentId = parent?.Id ?? Guid.Empty,
GroupItems = true GroupItems = true
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items); return ToResult(items);
@ -1150,7 +1155,6 @@ namespace Emby.Dlna.ContentDirectory
Limit = query.Limit, Limit = query.Limit,
StartIndex = query.StartIndex, StartIndex = query.StartIndex,
UserId = query.User.Id UserId = query.User.Id
}, new[] { parent }, query.DtoOptions); }, new[] { parent }, query.DtoOptions);
return ToResult(result); return ToResult(result);
@ -1167,7 +1171,6 @@ namespace Emby.Dlna.ContentDirectory
IncludeItemTypes = new[] { typeof(Episode).Name }, IncludeItemTypes = new[] { typeof(Episode).Name },
ParentId = parent == null ? Guid.Empty : parent.Id, ParentId = parent == null ? Guid.Empty : parent.Id,
GroupItems = false GroupItems = false
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items); return ToResult(items);
@ -1177,14 +1180,14 @@ namespace Emby.Dlna.ContentDirectory
{ {
query.OrderBy = Array.Empty<(string, SortOrder)>(); query.OrderBy = Array.Empty<(string, SortOrder)>();
var items = _userViewManager.GetLatestItems(new LatestItemsQuery var items = _userViewManager.GetLatestItems(
new LatestItemsQuery
{ {
UserId = user.Id, UserId = user.Id,
Limit = 50, Limit = 50,
IncludeItemTypes = new[] { typeof(Movie).Name }, IncludeItemTypes = new[] { nameof(Movie) },
ParentId = parent == null ? Guid.Empty : parent.Id, ParentId = parent?.Id ?? Guid.Empty,
GroupItems = true GroupItems = true
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items); return ToResult(items);
@ -1217,7 +1220,11 @@ namespace Emby.Dlna.ContentDirectory
Recursive = true, Recursive = true,
ParentId = parentId, ParentId = parentId,
GenreIds = new[] { item.Id }, GenreIds = new[] { item.Id },
IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name }, IncludeItemTypes = new[]
{
nameof(Movie),
nameof(Series)
},
Limit = limit, Limit = limit,
StartIndex = startIndex, StartIndex = startIndex,
DtoOptions = GetDtoOptions() DtoOptions = GetDtoOptions()
@ -1350,6 +1357,7 @@ namespace Emby.Dlna.ContentDirectory
internal class ServerItem internal class ServerItem
{ {
public BaseItem Item { get; set; } public BaseItem Item { get; set; }
public StubType? StubType { get; set; } public StubType? StubType { get; set; }
public ServerItem(BaseItem item) public ServerItem(BaseItem item)

View File

@ -6,14 +6,13 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using Emby.Dlna.Configuration;
using Emby.Dlna.ContentDirectory; using Emby.Dlna.ContentDirectory;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Playlists;
@ -23,6 +22,13 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
using XmlAttribute = MediaBrowser.Model.Dlna.XmlAttribute;
namespace Emby.Dlna.Didl namespace Emby.Dlna.Didl
{ {
@ -92,21 +98,21 @@ namespace Emby.Dlna.Didl
{ {
using (var writer = XmlWriter.Create(builder, settings)) using (var writer = XmlWriter.Create(builder, settings))
{ {
//writer.WriteStartDocument(); // writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC); writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC); // didl.SetAttribute("xmlns:sec", NS_SEC);
WriteXmlRootAttributes(_profile, writer); WriteXmlRootAttributes(_profile, writer);
WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo); WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
//writer.WriteEndDocument(); // writer.WriteEndDocument();
} }
return builder.ToString(); return builder.ToString();
@ -421,7 +427,6 @@ namespace Emby.Dlna.Didl
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
case StubType.Series: return _localization.GetLocalizedString("Shows"); case StubType.Series: return _localization.GetLocalizedString("Shows");
default: break;
} }
} }
@ -670,7 +675,7 @@ namespace Emby.Dlna.Didl
return; return;
} }
MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null; XmlAttribute secAttribute = null;
foreach (var attribute in _profile.XmlRootAttributes) foreach (var attribute in _profile.XmlRootAttributes)
{ {
if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase)) if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
@ -700,13 +705,13 @@ namespace Emby.Dlna.Didl
} }
/// <summary> /// <summary>
/// Adds fields used by both items and folders /// Adds fields used by both items and folders.
/// </summary> /// </summary>
private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter) private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
{ {
// Don't filter on dc:title because not all devices will include it in the filter // Don't filter on dc:title because not all devices will include it in the filter
// MediaMonkey for example won't display content without a title // MediaMonkey for example won't display content without a title
//if (filter.Contains("dc:title")) // if (filter.Contains("dc:title"))
{ {
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC); AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
} }
@ -745,7 +750,7 @@ namespace Emby.Dlna.Didl
AddValue(writer, "dc", "description", desc, NS_DC); AddValue(writer, "dc", "description", desc, NS_DC);
} }
} }
//if (filter.Contains("upnp:longDescription")) // if (filter.Contains("upnp:longDescription"))
//{ //{
// if (!string.IsNullOrWhiteSpace(item.Overview)) // if (!string.IsNullOrWhiteSpace(item.Overview))
// { // {
@ -760,6 +765,7 @@ namespace Emby.Dlna.Didl
{ {
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC); AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
} }
if (filter.Contains("upnp:rating")) if (filter.Contains("upnp:rating"))
{ {
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP); AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
@ -995,7 +1001,6 @@ namespace Emby.Dlna.Didl
} }
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN"); AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
} }
private void AddImageResElement( private void AddImageResElement(
@ -1048,10 +1053,12 @@ namespace Emby.Dlna.Didl
{ {
return GetImageInfo(item, ImageType.Primary); return GetImageInfo(item, ImageType.Primary);
} }
if (item.HasImage(ImageType.Thumb)) if (item.HasImage(ImageType.Thumb))
{ {
return GetImageInfo(item, ImageType.Thumb); return GetImageInfo(item, ImageType.Thumb);
} }
if (item.HasImage(ImageType.Backdrop)) if (item.HasImage(ImageType.Backdrop))
{ {
if (item is Channel) if (item is Channel)
@ -1131,25 +1138,24 @@ namespace Emby.Dlna.Didl
if (width == 0 || height == 0) if (width == 0 || height == 0)
{ {
//_imageProcessor.GetImageSize(item, imageInfo); // _imageProcessor.GetImageSize(item, imageInfo);
width = null; width = null;
height = null; height = null;
} }
else if (width == -1 || height == -1) else if (width == -1 || height == -1)
{ {
width = null; width = null;
height = null; height = null;
} }
//try // try
//{ //{
// var size = _imageProcessor.GetImageSize(imageInfo); // var size = _imageProcessor.GetImageSize(imageInfo);
// width = size.Width; // width = size.Width;
// height = size.Height; // height = size.Height;
//} //}
//catch // catch
//{ //{
//} //}

View File

@ -12,7 +12,6 @@ namespace Emby.Dlna.Didl
public Filter() public Filter()
: this("*") : this("*")
{ {
} }
public Filter(string filter) public Filter(string filter)
@ -26,7 +25,7 @@ namespace Emby.Dlna.Didl
{ {
// Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back. // Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back.
return true; return true;
//return _all || ListHelper.ContainsIgnoreCase(_fields, field); // return _all || ListHelper.ContainsIgnoreCase(_fields, field);
} }
} }
} }

View File

@ -31,7 +31,7 @@ namespace Emby.Dlna
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IXmlSerializer _xmlSerializer; private readonly IXmlSerializer _xmlSerializer;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger _logger; private readonly ILogger<DlnaManager> _logger;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
@ -49,7 +49,7 @@ namespace Emby.Dlna
_xmlSerializer = xmlSerializer; _xmlSerializer = xmlSerializer;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_appPaths = appPaths; _appPaths = appPaths;
_logger = loggerFactory.CreateLogger("Dlna"); _logger = loggerFactory.CreateLogger<DlnaManager>();
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_appHost = appHost; _appHost = appHost;
} }
@ -88,7 +88,6 @@ namespace Emby.Dlna
.Select(i => i.Item2) .Select(i => i.Item2)
.ToList(); .ToList();
} }
} }
public DeviceProfile GetDefaultProfile() public DeviceProfile GetDefaultProfile()
@ -141,55 +140,73 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.DeviceDescription)) if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
{ {
if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription)) if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
{ {
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{ {
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{ {
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{ {
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ModelName)) if (!string.IsNullOrEmpty(profileInfo.ModelName))
{ {
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName)) if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{ {
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
{ {
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{ {
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
{
return false; return false;
}
} }
return true; return true;
@ -251,7 +268,7 @@ namespace Emby.Dlna
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring: case HeaderMatchType.Substring:
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
//_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); // _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
return isMatch; return isMatch;
case HeaderMatchType.Regex: case HeaderMatchType.Regex:
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase); return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
@ -439,6 +456,7 @@ namespace Emby.Dlna
{ {
throw new ArgumentException("Profile is missing Id"); throw new ArgumentException("Profile is missing Id");
} }
if (string.IsNullOrEmpty(profile.Name)) if (string.IsNullOrEmpty(profile.Name))
{ {
throw new ArgumentException("Profile is missing Name"); throw new ArgumentException("Profile is missing Name");
@ -464,6 +482,7 @@ namespace Emby.Dlna
{ {
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); _profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
} }
SerializeToXml(profile, path); SerializeToXml(profile, path);
} }
@ -474,7 +493,7 @@ namespace Emby.Dlna
/// <summary> /// <summary>
/// Recreates the object using serialization, to ensure it's not a subclass. /// Recreates the object using serialization, to ensure it's not a subclass.
/// If it's a subclass it may not serlialize properly to xml (different root element tag name) /// If it's a subclass it may not serlialize properly to xml (different root element tag name).
/// </summary> /// </summary>
/// <param name="profile"></param> /// <param name="profile"></param>
/// <returns></returns> /// <returns></returns>
@ -493,6 +512,7 @@ namespace Emby.Dlna
class InternalProfileInfo class InternalProfileInfo
{ {
internal DeviceProfileInfo Info { get; set; } internal DeviceProfileInfo Info { get; set; }
internal string Path { get; set; } internal string Path { get; set; }
} }
@ -566,9 +586,9 @@ namespace Emby.Dlna
new Foobar2000Profile(), new Foobar2000Profile(),
new SharpSmartTvProfile(), new SharpSmartTvProfile(),
new MediaMonkeyProfile(), new MediaMonkeyProfile(),
//new Windows81Profile(), // new Windows81Profile(),
//new WindowsMediaCenterProfile(), // new WindowsMediaCenterProfile(),
//new WindowsPhoneProfile(), // new WindowsPhoneProfile(),
new DirectTvProfile(), new DirectTvProfile(),
new DishHopperJoeyProfile(), new DishHopperJoeyProfile(),
new DefaultProfile(), new DefaultProfile(),

View File

@ -31,18 +31,26 @@ namespace Emby.Dlna.Eventing
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl) public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
{ {
var subscription = GetSubscription(subscriptionId, false); var subscription = GetSubscription(subscriptionId, false);
if (subscription != null)
{
subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
int timeoutSeconds = subscription.TimeoutSeconds;
subscription.SubscriptionTime = DateTime.UtcNow;
subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300; _logger.LogDebug(
int timeoutSeconds = subscription.TimeoutSeconds; "Renewing event subscription for {0} with timeout of {1} to {2}",
subscription.SubscriptionTime = DateTime.UtcNow; subscription.NotificationType,
timeoutSeconds,
subscription.CallbackUrl);
_logger.LogDebug( return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
"Renewing event subscription for {0} with timeout of {1} to {2}", }
subscription.NotificationType,
timeoutSeconds,
subscription.CallbackUrl);
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds); return new EventSubscriptionResponse
{
Content = string.Empty,
ContentType = "text/plain"
};
} }
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
@ -150,6 +158,7 @@ namespace Emby.Dlna.Eventing
builder.Append("</" + key + ">"); builder.Append("</" + key + ">");
builder.Append("</e:property>"); builder.Append("</e:property>");
} }
builder.Append("</e:propertyset>"); builder.Append("</e:propertyset>");
var options = new HttpRequestOptions var options = new HttpRequestOptions
@ -169,7 +178,6 @@ namespace Emby.Dlna.Eventing
{ {
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false)) using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
{ {
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)

View File

@ -7,10 +7,13 @@ namespace Emby.Dlna.Eventing
public class EventSubscription public class EventSubscription
{ {
public string Id { get; set; } public string Id { get; set; }
public string CallbackUrl { get; set; } public string CallbackUrl { get; set; }
public string NotificationType { get; set; } public string NotificationType { get; set; }
public DateTime SubscriptionTime { get; set; } public DateTime SubscriptionTime { get; set; }
public int TimeoutSeconds { get; set; } public int TimeoutSeconds { get; set; }
public long TriggerCount { get; set; } public long TriggerCount { get; set; }

View File

@ -33,10 +33,8 @@ namespace Emby.Dlna.Main
public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
{ {
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILogger _logger; private readonly ILogger<DlnaEntryPoint> _logger;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private PlayToManager _manager;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -47,14 +45,13 @@ namespace Emby.Dlna.Main
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IDeviceDiscovery _deviceDiscovery; private readonly IDeviceDiscovery _deviceDiscovery;
private SsdpDevicePublisher _Publisher;
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly object _syncLock = new object();
private PlayToManager _manager;
private SsdpDevicePublisher _publisher;
private ISsdpCommunicationsServer _communicationsServer; private ISsdpCommunicationsServer _communicationsServer;
internal IContentDirectory ContentDirectory { get; private set; } internal IContentDirectory ContentDirectory { get; private set; }
@ -65,7 +62,8 @@ namespace Emby.Dlna.Main
public static DlnaEntryPoint Current; public static DlnaEntryPoint Current;
public DlnaEntryPoint(IServerConfigurationManager config, public DlnaEntryPoint(
IServerConfigurationManager config,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IServerApplicationHost appHost, IServerApplicationHost appHost,
ISessionManager sessionManager, ISessionManager sessionManager,
@ -99,7 +97,7 @@ namespace Emby.Dlna.Main
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_socketFactory = socketFactory; _socketFactory = socketFactory;
_networkManager = networkManager; _networkManager = networkManager;
_logger = loggerFactory.CreateLogger("Dlna"); _logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
ContentDirectory = new ContentDirectory.ContentDirectory( ContentDirectory = new ContentDirectory.ContentDirectory(
dlnaManager, dlnaManager,
@ -133,20 +131,20 @@ namespace Emby.Dlna.Main
{ {
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
ReloadComponents(); await ReloadComponents().ConfigureAwait(false);
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
} }
void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{ {
if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
{ {
ReloadComponents(); await ReloadComponents().ConfigureAwait(false);
} }
} }
private async void ReloadComponents() private async Task ReloadComponents()
{ {
var options = _config.GetDlnaConfiguration(); var options = _config.GetDlnaConfiguration();
@ -180,7 +178,7 @@ namespace Emby.Dlna.Main
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows || var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
OperatingSystem.Id == OperatingSystemId.Linux; OperatingSystem.Id == OperatingSystemId.Linux;
_communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding) _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
{ {
IsShared = true IsShared = true
}; };
@ -231,20 +229,22 @@ namespace Emby.Dlna.Main
return; return;
} }
if (_Publisher != null) if (_publisher != null)
{ {
return; return;
} }
try try
{ {
_Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost); _publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost)
_Publisher.LogFunction = LogMessage; {
_Publisher.SupportPnpRootDevice = false; LogFunction = LogMessage,
SupportPnpRootDevice = false
};
await RegisterServerEndpoints().ConfigureAwait(false); await RegisterServerEndpoints().ConfigureAwait(false);
_Publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds)); _publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -266,6 +266,12 @@ namespace Emby.Dlna.Main
continue; continue;
} }
// Limit to LAN addresses only
if (!_networkManager.IsAddressInSubnets(address, true, true))
{
continue;
}
var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
@ -275,7 +281,7 @@ namespace Emby.Dlna.Main
var device = new SsdpRootDevice var device = new SsdpRootDevice
{ {
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info. CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
Location = uri, // Must point to the URL that serves your devices UPnP description document. Location = uri, // Must point to the URL that serves your devices UPnP description document.
Address = address, Address = address,
SubnetMask = _networkManager.GetLocalIpSubnetMask(address), SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
@ -287,13 +293,13 @@ namespace Emby.Dlna.Main
}; };
SetProperies(device, fullService); SetProperies(device, fullService);
_Publisher.AddDevice(device); _publisher.AddDevice(device);
var embeddedDevices = new[] var embeddedDevices = new[]
{ {
"urn:schemas-upnp-org:service:ContentDirectory:1", "urn:schemas-upnp-org:service:ContentDirectory:1",
"urn:schemas-upnp-org:service:ConnectionManager:1", "urn:schemas-upnp-org:service:ConnectionManager:1",
//"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
}; };
foreach (var subDevice in embeddedDevices) foreach (var subDevice in embeddedDevices)
@ -319,12 +325,13 @@ namespace Emby.Dlna.Main
{ {
guid = text.GetMD5(); guid = text.GetMD5();
} }
return guid.ToString("N", CultureInfo.InvariantCulture); return guid.ToString("N", CultureInfo.InvariantCulture);
} }
private void SetProperies(SsdpDevice device, string fullDeviceType) private void SetProperies(SsdpDevice device, string fullDeviceType)
{ {
var service = fullDeviceType.Replace("urn:", string.Empty).Replace(":1", string.Empty); var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase);
var serviceParts = service.Split(':'); var serviceParts = service.Split(':');
@ -335,7 +342,6 @@ namespace Emby.Dlna.Main
device.DeviceType = serviceParts[2]; device.DeviceType = serviceParts[2];
} }
private readonly object _syncLock = new object();
private void StartPlayToManager() private void StartPlayToManager()
{ {
lock (_syncLock) lock (_syncLock)
@ -347,7 +353,8 @@ namespace Emby.Dlna.Main
try try
{ {
_manager = new PlayToManager(_logger, _manager = new PlayToManager(
_logger,
_sessionManager, _sessionManager,
_libraryManager, _libraryManager,
_userManager, _userManager,
@ -386,6 +393,7 @@ namespace Emby.Dlna.Main
{ {
_logger.LogError(ex, "Error disposing PlayTo manager"); _logger.LogError(ex, "Error disposing PlayTo manager");
} }
_manager = null; _manager = null;
} }
} }
@ -412,11 +420,11 @@ namespace Emby.Dlna.Main
public void DisposeDevicePublisher() public void DisposeDevicePublisher()
{ {
if (_Publisher != null) if (_publisher != null)
{ {
_logger.LogInformation("Disposing SsdpDevicePublisher"); _logger.LogInformation("Disposing SsdpDevicePublisher");
_Publisher.Dispose(); _publisher.Dispose();
_Publisher = null; _publisher = null;
} }
} }
} }

View File

@ -19,8 +19,6 @@ namespace Emby.Dlna.PlayTo
{ {
public class Device : IDisposable public class Device : IDisposable
{ {
#region Fields & Properties
private Timer _timer; private Timer _timer;
public DeviceInfo Properties { get; set; } public DeviceInfo Properties { get; set; }
@ -34,9 +32,10 @@ namespace Emby.Dlna.PlayTo
{ {
get get
{ {
RefreshVolumeIfNeeded(); RefreshVolumeIfNeeded().GetAwaiter().GetResult();
return _volume; return _volume;
} }
set => _volume = value; set => _volume = value;
} }
@ -52,10 +51,10 @@ namespace Emby.Dlna.PlayTo
public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED; public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED;
#endregion
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
public Action OnDeviceUnavailable { get; set; } public Action OnDeviceUnavailable { get; set; }
@ -76,24 +75,24 @@ namespace Emby.Dlna.PlayTo
private DateTime _lastVolumeRefresh; private DateTime _lastVolumeRefresh;
private bool _volumeRefreshActive; private bool _volumeRefreshActive;
private void RefreshVolumeIfNeeded() private Task RefreshVolumeIfNeeded()
{ {
if (!_volumeRefreshActive) if (_volumeRefreshActive
{ && DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
return;
}
if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
{ {
_lastVolumeRefresh = DateTime.UtcNow; _lastVolumeRefresh = DateTime.UtcNow;
RefreshVolume(CancellationToken.None); return RefreshVolume();
} }
return Task.CompletedTask;
} }
private async void RefreshVolume(CancellationToken cancellationToken) private async Task RefreshVolume(CancellationToken cancellationToken = default)
{ {
if (_disposed) if (_disposed)
{
return; return;
}
try try
{ {
@ -141,8 +140,6 @@ namespace Emby.Dlna.PlayTo
} }
} }
#region Commanding
public Task VolumeDown(CancellationToken cancellationToken) public Task VolumeDown(CancellationToken cancellationToken)
{ {
var sendVolume = Math.Max(Volume - 5, 0); var sendVolume = Math.Max(Volume - 5, 0);
@ -211,7 +208,9 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
if (command == null) if (command == null)
{
return false; return false;
}
var service = GetServiceRenderingControl(); var service = GetServiceRenderingControl();
@ -232,7 +231,7 @@ namespace Emby.Dlna.PlayTo
} }
/// <summary> /// <summary>
/// Sets volume on a scale of 0-100 /// Sets volume on a scale of 0-100.
/// </summary> /// </summary>
public async Task SetVolume(int value, CancellationToken cancellationToken) public async Task SetVolume(int value, CancellationToken cancellationToken)
{ {
@ -240,7 +239,9 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
if (command == null) if (command == null)
{
return; return;
}
var service = GetServiceRenderingControl(); var service = GetServiceRenderingControl();
@ -263,7 +264,9 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
if (command == null) if (command == null)
{
return; return;
}
var service = GetAvTransportService(); var service = GetAvTransportService();
@ -288,7 +291,9 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
if (command == null) if (command == null)
{
return; return;
}
var dictionary = new Dictionary<string, string> var dictionary = new Dictionary<string, string>
{ {
@ -401,11 +406,8 @@ namespace Emby.Dlna.PlayTo
RestartTimer(true); RestartTimer(true);
} }
#endregion
#region Get data
private int _connectFailureCount; private int _connectFailureCount;
private async void TimerCallback(object sender) private async void TimerCallback(object sender)
{ {
if (_disposed) if (_disposed)
@ -458,7 +460,9 @@ namespace Emby.Dlna.PlayTo
_connectFailureCount = 0; _connectFailureCount = 0;
if (_disposed) if (_disposed)
{
return; return;
}
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive // If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
if (transportState.Value == TRANSPORTSTATE.STOPPED) if (transportState.Value == TRANSPORTSTATE.STOPPED)
@ -478,7 +482,9 @@ namespace Emby.Dlna.PlayTo
catch (Exception ex) catch (Exception ex)
{ {
if (_disposed) if (_disposed)
{
return; return;
}
_logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name); _logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name);
@ -494,6 +500,7 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
} }
RestartTimerInactive(); RestartTimerInactive();
} }
} }
@ -578,7 +585,9 @@ namespace Emby.Dlna.PlayTo
cancellationToken: cancellationToken).ConfigureAwait(false); cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{
return; return;
}
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse") var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse")
.Select(i => i.Element("CurrentMute")) .Select(i => i.Element("CurrentMute"))
@ -750,7 +759,7 @@ namespace Emby.Dlna.PlayTo
if (track == null) if (track == null)
{ {
//If track is null, some vendors do this, use GetMediaInfo instead // If track is null, some vendors do this, use GetMediaInfo instead
return (true, null); return (true, null);
} }
@ -794,7 +803,6 @@ namespace Emby.Dlna.PlayTo
} }
catch (XmlException) catch (XmlException)
{ {
} }
// first try to add a root node with a dlna namesapce // first try to add a root node with a dlna namesapce
@ -806,7 +814,6 @@ namespace Emby.Dlna.PlayTo
} }
catch (XmlException) catch (XmlException)
{ {
} }
// some devices send back invalid xml // some devices send back invalid xml
@ -816,7 +823,6 @@ namespace Emby.Dlna.PlayTo
} }
catch (XmlException) catch (XmlException)
{ {
} }
return null; return null;
@ -871,10 +877,6 @@ namespace Emby.Dlna.PlayTo
return new string[4]; return new string[4];
} }
#endregion
#region From XML
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken) private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
{ {
if (AvCommands != null) if (AvCommands != null)
@ -1069,8 +1071,6 @@ namespace Emby.Dlna.PlayTo
return new Device(deviceProperties, httpClient, logger, config); return new Device(deviceProperties, httpClient, logger, config);
} }
#endregion
private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private static DeviceIcon CreateIcon(XElement element) private static DeviceIcon CreateIcon(XElement element)
{ {
@ -1194,8 +1194,6 @@ namespace Emby.Dlna.PlayTo
}); });
} }
#region IDisposable
bool _disposed; bool _disposed;
public void Dispose() public void Dispose()
@ -1222,8 +1220,6 @@ namespace Emby.Dlna.PlayTo
_disposed = true; _disposed = true;
} }
#endregion
public override string ToString() public override string ToString()
{ {
return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl); return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Didl; using Emby.Dlna.Didl;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@ -22,6 +23,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Photo = MediaBrowser.Controller.Entities.Photo;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
{ {
@ -146,11 +148,14 @@ namespace Emby.Dlna.PlayTo
{ {
var positionTicks = GetProgressPositionTicks(streamInfo); var positionTicks = GetProgressPositionTicks(streamInfo);
ReportPlaybackStopped(streamInfo, positionTicks); await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false);
} }
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item == null) return; if (streamInfo.Item == null)
{
return;
}
var newItemProgress = GetProgressInfo(streamInfo); var newItemProgress = GetProgressInfo(streamInfo);
@ -173,11 +178,14 @@ namespace Emby.Dlna.PlayTo
{ {
var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item == null) return; if (streamInfo.Item == null)
{
return;
}
var positionTicks = GetProgressPositionTicks(streamInfo); var positionTicks = GetProgressPositionTicks(streamInfo);
ReportPlaybackStopped(streamInfo, positionTicks); await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false);
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
@ -185,7 +193,7 @@ namespace Emby.Dlna.PlayTo
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) : (_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
mediaSource.RunTimeTicks; mediaSource.RunTimeTicks;
var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0); var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
if (!playedToCompletion && duration.HasValue && positionTicks.HasValue) if (!playedToCompletion && duration.HasValue && positionTicks.HasValue)
{ {
@ -210,7 +218,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) private async Task ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks)
{ {
try try
{ {
@ -220,7 +228,6 @@ namespace Emby.Dlna.PlayTo
SessionId = _session.Id, SessionId = _session.Id,
PositionTicks = positionTicks, PositionTicks = positionTicks,
MediaSourceId = streamInfo.MediaSourceId MediaSourceId = streamInfo.MediaSourceId
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
@ -418,6 +425,7 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
return; return;
} }
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
} }
} }
@ -441,7 +449,13 @@ namespace Emby.Dlna.PlayTo
} }
} }
private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex) private PlaylistItem CreatePlaylistItem(
BaseItem item,
User user,
long startPostionTicks,
string mediaSourceId,
int? audioStreamIndex,
int? subtitleStreamIndex)
{ {
var deviceInfo = _device.Properties; var deviceInfo = _device.Properties;
@ -700,6 +714,7 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentException("Volume argument cannot be null"); throw new ArgumentException("Volume argument cannot be null");
} }
default: default:
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -785,12 +800,15 @@ namespace Emby.Dlna.PlayTo
public int? SubtitleStreamIndex { get; set; } public int? SubtitleStreamIndex { get; set; }
public string DeviceProfileId { get; set; } public string DeviceProfileId { get; set; }
public string DeviceId { get; set; } public string DeviceId { get; set; }
public string MediaSourceId { get; set; } public string MediaSourceId { get; set; }
public string LiveStreamId { get; set; } public string LiveStreamId { get; set; }
public BaseItem Item { get; set; } public BaseItem Item { get; set; }
private MediaSourceInfo MediaSource; private MediaSourceInfo MediaSource;
private IMediaSourceManager _mediaSourceManager; private IMediaSourceManager _mediaSourceManager;

View File

@ -78,9 +78,15 @@ namespace Emby.Dlna.PlayTo
var info = e.Argument; var info = e.Argument;
if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty; if (!info.Headers.TryGetValue("USN", out string usn))
{
usn = string.Empty;
}
if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty; if (!info.Headers.TryGetValue("NT", out string nt))
{
nt = string.Empty;
}
string location = info.Location.ToString(); string location = info.Location.ToString();
@ -88,7 +94,7 @@ namespace Emby.Dlna.PlayTo
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 && if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1) nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1)
{ {
//_logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location); // _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
return; return;
} }
@ -112,7 +118,6 @@ namespace Emby.Dlna.PlayTo
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -133,6 +138,7 @@ namespace Emby.Dlna.PlayTo
usn = usn.Substring(index); usn = usn.Substring(index);
found = true; found = true;
} }
index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase); index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase);
if (index != -1) if (index != -1)
{ {
@ -184,7 +190,8 @@ namespace Emby.Dlna.PlayTo
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress); serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
} }
controller = new PlayToController(sessionInfo, controller = new PlayToController(
sessionInfo,
_sessionManager, _sessionManager,
_libraryManager, _libraryManager,
_logger, _logger,
@ -242,7 +249,6 @@ namespace Emby.Dlna.PlayTo
} }
catch catch
{ {
} }
_sessionLock.Dispose(); _sessionLock.Dispose();

View File

@ -12,6 +12,7 @@ namespace Emby.Dlna.PlayTo
public class MediaChangedEventArgs : EventArgs public class MediaChangedEventArgs : EventArgs
{ {
public uBaseObject OldMediaInfo { get; set; } public uBaseObject OldMediaInfo { get; set; }
public uBaseObject NewMediaInfo { get; set; } public uBaseObject NewMediaInfo { get; set; }
} }
} }

View File

@ -91,7 +91,6 @@ namespace Emby.Dlna.PlayTo
using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false)) using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
{ {
} }
} }

View File

@ -44,10 +44,12 @@ namespace Emby.Dlna.PlayTo
{ {
return MediaBrowser.Model.Entities.MediaType.Audio; return MediaBrowser.Model.Entities.MediaType.Audio;
} }
if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1) if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1)
{ {
return MediaBrowser.Model.Entities.MediaType.Video; return MediaBrowser.Model.Entities.MediaType.Video;
} }
if (classType.IndexOf("image", StringComparison.Ordinal) != -1) if (classType.IndexOf("image", StringComparison.Ordinal) != -1)
{ {
return MediaBrowser.Model.Entities.MediaType.Photo; return MediaBrowser.Model.Entities.MediaType.Photo;

View File

@ -134,6 +134,7 @@ namespace Emby.Dlna.Server
return result; return result;
} }
} }
return c.ToString(CultureInfo.InvariantCulture); return c.ToString(CultureInfo.InvariantCulture);
} }
@ -157,18 +158,22 @@ namespace Emby.Dlna.Server
{ {
break; break;
} }
if (stringBuilder == null) if (stringBuilder == null)
{ {
stringBuilder = new StringBuilder(); stringBuilder = new StringBuilder();
} }
stringBuilder.Append(str, num, num2 - num); stringBuilder.Append(str, num, num2 - num);
stringBuilder.Append(GetEscapeSequence(str[num2])); stringBuilder.Append(GetEscapeSequence(str[num2]));
num = num2 + 1; num = num2 + 1;
} }
if (stringBuilder == null) if (stringBuilder == null)
{ {
return str; return str;
} }
stringBuilder.Append(str, num, length - num); stringBuilder.Append(str, num, length - num);
return stringBuilder.ToString(); return stringBuilder.ToString();
} }

View File

@ -18,6 +18,7 @@ namespace Emby.Dlna.Service
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"; private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
protected IServerConfigurationManager Config { get; } protected IServerConfigurationManager Config { get; }
protected ILogger Logger { get; } protected ILogger Logger { get; }
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger) protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
@ -135,6 +136,7 @@ namespace Emby.Dlna.Service
break; break;
} }
default: default:
{ {
await reader.SkipAsync().ConfigureAwait(false); await reader.SkipAsync().ConfigureAwait(false);
@ -211,7 +213,9 @@ namespace Emby.Dlna.Service
private class ControlRequestInfo private class ControlRequestInfo
{ {
public string LocalName { get; set; } public string LocalName { get; set; }
public string NamespaceURI { get; set; } public string NamespaceURI { get; set; }
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
} }

View File

@ -17,7 +17,7 @@ namespace Emby.Dlna.Service
Logger = logger; Logger = logger;
HttpClient = httpClient; HttpClient = httpClient;
EventManager = new EventManager(Logger, HttpClient); EventManager = new EventManager(logger, HttpClient);
} }
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)

View File

@ -80,6 +80,7 @@ namespace Emby.Dlna.Service
{ {
builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>"); builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>");
} }
builder.Append("</allowedValueList>"); builder.Append("</allowedValueList>");
} }

View File

@ -77,7 +77,7 @@ namespace Emby.Dlna.Ssdp
// (Optional) Set the filter so we only see notifications for devices we care about // (Optional) Set the filter so we only see notifications for devices we care about
// (can be any search target value i.e device type, uuid value etc - any value that appears in the // (can be any search target value i.e device type, uuid value etc - any value that appears in the
// DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method). // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
//_DeviceLocator.NotificationFilter = "upnp:rootdevice"; // _DeviceLocator.NotificationFilter = "upnp:rootdevice";
// Connect our event handler so we process devices as they are found // Connect our event handler so we process devices as they are found
_deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable; _deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;
@ -100,15 +100,13 @@ namespace Emby.Dlna.Ssdp
var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
var args = new GenericEventArgs<UpnpDeviceInfo> var args = new GenericEventArgs<UpnpDeviceInfo>(
{ new UpnpDeviceInfo
Argument = new UpnpDeviceInfo
{ {
Location = e.DiscoveredDevice.DescriptionLocation, Location = e.DiscoveredDevice.DescriptionLocation,
Headers = headers, Headers = headers,
LocalIpAddress = e.LocalIpAddress LocalIpAddress = e.LocalIpAddress
} });
};
DeviceDiscoveredInternal?.Invoke(this, args); DeviceDiscoveredInternal?.Invoke(this, args);
} }
@ -121,14 +119,12 @@ namespace Emby.Dlna.Ssdp
var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
var args = new GenericEventArgs<UpnpDeviceInfo> var args = new GenericEventArgs<UpnpDeviceInfo>(
{ new UpnpDeviceInfo
Argument = new UpnpDeviceInfo
{ {
Location = e.DiscoveredDevice.DescriptionLocation, Location = e.DiscoveredDevice.DescriptionLocation,
Headers = headers Headers = headers
} });
};
DeviceLeft?.Invoke(this, args); DeviceLeft?.Invoke(this, args);
} }

View File

@ -1,5 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace Emby.Dlna.Ssdp namespace Emby.Dlna.Ssdp
@ -10,24 +11,17 @@ namespace Emby.Dlna.Ssdp
{ {
var node = container.Element(name); var node = container.Element(name);
return node == null ? null : node.Value; return node?.Value;
} }
public static string GetAttributeValue(this XElement container, XName name) public static string GetAttributeValue(this XElement container, XName name)
{ {
var node = container.Attribute(name); var node = container.Attribute(name);
return node == null ? null : node.Value; return node?.Value;
} }
public static string GetDescendantValue(this XElement container, XName name) public static string GetDescendantValue(this XElement container, XName name)
{ => container.Descendants(name).FirstOrDefault()?.Value;
foreach (var node in container.Descendants(name))
{
return node.Value;
}
return null;
}
} }
} }

View File

@ -4,6 +4,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@ -14,6 +15,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Photo = MediaBrowser.Controller.Entities.Photo;
namespace Emby.Drawing namespace Emby.Drawing
{ {
@ -28,7 +30,7 @@ namespace Emby.Drawing
private static readonly HashSet<string> _transparentImageTypes private static readonly HashSet<string> _transparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
private readonly ILogger _logger; private readonly ILogger<ImageProcessor> _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;
private readonly IImageEncoder _imageEncoder; private readonly IImageEncoder _imageEncoder;
@ -114,7 +116,7 @@ namespace Emby.Drawing
=> _transparentImageTypes.Contains(Path.GetExtension(path)); => _transparentImageTypes.Contains(Path.GetExtension(path));
/// <inheritdoc /> /// <inheritdoc />
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options) public async Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
{ {
ItemImageInfo originalImage = options.Image; ItemImageInfo originalImage = options.Image;
BaseItem item = options.Item; BaseItem item = options.Item;
@ -230,7 +232,7 @@ namespace Emby.Drawing
return ImageFormat.Jpg; return ImageFormat.Jpg;
} }
private string GetMimeType(ImageFormat format, string path) private string? GetMimeType(ImageFormat format, string path)
=> format switch => format switch
{ {
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"), ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
@ -300,7 +302,7 @@ namespace Emby.Drawing
} }
string path = info.Path; string path = info.Path;
_logger.LogInformation("Getting image size for item {ItemType} {Path}", item.GetType().Name, path); _logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
ImageDimensions size = GetImageDimensions(path); ImageDimensions size = GetImageDimensions(path);
info.Width = size.Width; info.Width = size.Width;
@ -313,6 +315,27 @@ namespace Emby.Drawing
public ImageDimensions GetImageDimensions(string path) public ImageDimensions GetImageDimensions(string path)
=> _imageEncoder.GetImageSize(path); => _imageEncoder.GetImageSize(path);
/// <inheritdoc />
public string GetImageBlurHash(string path)
{
var size = GetImageDimensions(path);
if (size.Width <= 0 || size.Height <= 0)
{
return string.Empty;
}
// We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
// One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
// See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
float xCompF = MathF.Sqrt(16.0f * size.Width / size.Height);
float yCompF = xCompF * size.Height / size.Width;
int xComp = Math.Min((int)xCompF + 1, 9);
int yComp = Math.Min((int)yCompF + 1, 9);
return _imageEncoder.GetImageBlurHash(xComp, yComp, path);
}
/// <inheritdoc /> /// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ItemImageInfo image) public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
@ -328,6 +351,13 @@ namespace Emby.Drawing
}); });
} }
/// <inheritdoc />
public string GetImageCacheTag(User user)
{
return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5()
.ToString("N", CultureInfo.InvariantCulture);
}
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{ {
var inputFormat = Path.GetExtension(originalImagePath) var inputFormat = Path.GetExtension(originalImagePath)

View File

@ -42,5 +42,11 @@ namespace Emby.Drawing
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
/// <inheritdoc />
public string GetImageBlurHash(int xComp, int yComp, string path)
{
throw new NotImplementedException();
}
} }
} }

View File

@ -64,6 +64,7 @@ namespace Emby.Naming.AudioBook
{ {
result.ChapterNumber = int.Parse(matches[0].Groups[0].Value); result.ChapterNumber = int.Parse(matches[0].Groups[0].Value);
} }
if (matches.Count > 1) if (matches.Count > 1)
{ {
result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value); result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value);

View File

@ -5,17 +5,17 @@ namespace Emby.Naming.Common
public enum MediaType public enum MediaType
{ {
/// <summary> /// <summary>
/// The audio /// The audio.
/// </summary> /// </summary>
Audio = 0, Audio = 0,
/// <summary> /// <summary>
/// The photo /// The photo.
/// </summary> /// </summary>
Photo = 1, Photo = 1,
/// <summary> /// <summary>
/// The video /// The video.
/// </summary> /// </summary>
Video = 2 Video = 2
} }

View File

@ -142,7 +142,7 @@ namespace Emby.Naming.Common
CleanStrings = new[] CleanStrings = new[]
{ {
@"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)", @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
@"(\[.*\])" @"(\[.*\])"
}; };

View File

@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Notifications;
@ -149,9 +150,7 @@ namespace Emby.Notifications.Api
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotificationsSummary request) public object Get(GetNotificationsSummary request)
{ {
return new NotificationsSummary return new NotificationsSummary();
{
};
} }
public Task Post(AddAdminNotification request) public Task Post(AddAdminNotification request)
@ -164,7 +163,10 @@ namespace Emby.Notifications.Api
Level = request.Level, Level = request.Level,
Name = request.Name, Name = request.Name,
Url = request.Url, Url = request.Url,
UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray() UserIds = _userManager.Users
.Where(user => user.HasPermission(PermissionKind.IsAdministrator))
.Select(user => user.Id)
.ToArray()
}; };
return _notificationManager.SendNotification(notification, CancellationToken.None); return _notificationManager.SendNotification(notification, CancellationToken.None);

View File

@ -25,7 +25,7 @@ namespace Emby.Notifications
/// </summary> /// </summary>
public class NotificationEntryPoint : IServerEntryPoint public class NotificationEntryPoint : IServerEntryPoint
{ {
private readonly ILogger _logger; private readonly ILogger<NotificationEntryPoint> _logger;
private readonly IActivityManager _activityManager; private readonly IActivityManager _activityManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly INotificationManager _notificationManager; private readonly INotificationManager _notificationManager;

View File

@ -4,6 +4,8 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -21,7 +23,7 @@ namespace Emby.Notifications
/// </summary> /// </summary>
public class NotificationManager : INotificationManager public class NotificationManager : INotificationManager
{ {
private readonly ILogger _logger; private readonly ILogger<NotificationManager> _logger;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
@ -101,7 +103,7 @@ namespace Emby.Notifications
switch (request.SendToUserMode.Value) switch (request.SendToUserMode.Value)
{ {
case SendToUserType.Admins: case SendToUserType.Admins:
return _userManager.Users.Where(i => i.Policy.IsAdministrator) return _userManager.Users.Where(i => i.HasPermission(PermissionKind.IsAdministrator))
.Select(i => i.Id); .Select(i => i.Id);
case SendToUserType.All: case SendToUserType.All:
return _userManager.UsersIds; return _userManager.UsersIds;
@ -117,7 +119,7 @@ namespace Emby.Notifications
var config = GetConfiguration(); var config = GetConfiguration();
return _userManager.Users return _userManager.Users
.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i.Policy)) .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i))
.Select(i => i.Id); .Select(i => i.Id);
} }
@ -142,7 +144,7 @@ namespace Emby.Notifications
User = user User = user
}; };
_logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Name); _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Username);
try try
{ {

View File

@ -22,7 +22,7 @@ namespace Emby.Photos
/// </summary> /// </summary>
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
{ {
private readonly ILogger _logger; private readonly ILogger<PhotoProvider> _logger;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
// These are causing taglib to hang // These are causing taglib to hang
@ -104,7 +104,7 @@ namespace Emby.Photos
item.Overview = image.ImageTag.Comment; item.Overview = image.ImageTag.Comment;
if (!string.IsNullOrWhiteSpace(image.ImageTag.Title) if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
&& !item.LockedFields.Contains(MetadataFields.Name)) && !item.LockedFields.Contains(MetadataField.Name))
{ {
item.Name = image.ImageTag.Title; item.Name = image.ImageTag.Title;
} }

View File

@ -88,25 +88,26 @@ namespace Emby.Server.Implementations.Activity
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure; _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
_userManager.UserCreated += OnUserCreated; _userManager.OnUserCreated += OnUserCreated;
_userManager.UserPasswordChanged += OnUserPasswordChanged; _userManager.OnUserPasswordChanged += OnUserPasswordChanged;
_userManager.UserDeleted += OnUserDeleted; _userManager.OnUserDeleted += OnUserDeleted;
_userManager.UserPolicyUpdated += OnUserPolicyUpdated; _userManager.OnUserLockedOut += OnUserLockedOut;
_userManager.UserLockedOut += OnUserLockedOut;
return Task.CompletedTask; return Task.CompletedTask;
} }
private async void OnUserLockedOut(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e) private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
{ {
await CreateLogEntry(new ActivityLog( await CreateLogEntry(new ActivityLog(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserLockedOutWithName"), _localization.GetLocalizedString("UserLockedOutWithName"),
e.Argument.Name), e.Argument.Username),
NotificationType.UserLockedOut.ToString(), NotificationType.UserLockedOut.ToString(),
e.Argument.Id)) e.Argument.Id)
.ConfigureAwait(false); {
LogSeverity = LogLevel.Error
}).ConfigureAwait(false);
} }
private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Activity
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
user.Name, user.Username,
GetItemName(item), GetItemName(item),
e.DeviceName), e.DeviceName),
GetPlaybackStoppedNotificationType(item.MediaType), GetPlaybackStoppedNotificationType(item.MediaType),
@ -187,7 +188,7 @@ namespace Emby.Server.Implementations.Activity
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), _localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
user.Name, user.Username,
GetItemName(item), GetItemName(item),
e.DeviceName), e.DeviceName),
GetPlaybackNotificationType(item.MediaType), GetPlaybackNotificationType(item.MediaType),
@ -304,49 +305,37 @@ namespace Emby.Server.Implementations.Activity
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
private async void OnUserPolicyUpdated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e) private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
{
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
e.Argument.Name),
"UserPolicyUpdated",
e.Argument.Id))
.ConfigureAwait(false);
}
private async void OnUserDeleted(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
{ {
await CreateLogEntry(new ActivityLog( await CreateLogEntry(new ActivityLog(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserDeletedWithName"), _localization.GetLocalizedString("UserDeletedWithName"),
e.Argument.Name), e.Argument.Username),
"UserDeleted", "UserDeleted",
Guid.Empty)) Guid.Empty))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
private async void OnUserPasswordChanged(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e) private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
{ {
await CreateLogEntry(new ActivityLog( await CreateLogEntry(new ActivityLog(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPasswordChangedWithName"), _localization.GetLocalizedString("UserPasswordChangedWithName"),
e.Argument.Name), e.Argument.Username),
"UserPasswordChanged", "UserPasswordChanged",
e.Argument.Id)) e.Argument.Id))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
private async void OnUserCreated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e) private async void OnUserCreated(object sender, GenericEventArgs<User> e)
{ {
await CreateLogEntry(new ActivityLog( await CreateLogEntry(new ActivityLog(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserCreatedWithName"), _localization.GetLocalizedString("UserCreatedWithName"),
e.Argument.Name), e.Argument.Username),
"UserCreated", "UserCreated",
e.Argument.Id)) e.Argument.Id))
.ConfigureAwait(false); .ConfigureAwait(false);
@ -377,50 +366,50 @@ namespace Emby.Server.Implementations.Activity
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
private async void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e) private async void OnPluginUpdated(object sender, InstallationInfo e)
{ {
await CreateLogEntry(new ActivityLog( await CreateLogEntry(new ActivityLog(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUpdatedWithName"), _localization.GetLocalizedString("PluginUpdatedWithName"),
e.Argument.Item1.Name), e.Name),
NotificationType.PluginUpdateInstalled.ToString(), NotificationType.PluginUpdateInstalled.ToString(),
Guid.Empty) Guid.Empty)
{ {
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"), _localization.GetLocalizedString("VersionNumber"),
e.Argument.Item2.version), e.Version),
Overview = e.Argument.Item2.changelog Overview = e.Changelog
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
private async void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e) private async void OnPluginUninstalled(object sender, IPlugin e)
{ {
await CreateLogEntry(new ActivityLog( await CreateLogEntry(new ActivityLog(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUninstalledWithName"), _localization.GetLocalizedString("PluginUninstalledWithName"),
e.Argument.Name), e.Name),
NotificationType.PluginUninstalled.ToString(), NotificationType.PluginUninstalled.ToString(),
Guid.Empty)) Guid.Empty))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
private async void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e) private async void OnPluginInstalled(object sender, InstallationInfo e)
{ {
await CreateLogEntry(new ActivityLog( await CreateLogEntry(new ActivityLog(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginInstalledWithName"), _localization.GetLocalizedString("PluginInstalledWithName"),
e.Argument.name), e.Name),
NotificationType.PluginInstalled.ToString(), NotificationType.PluginInstalled.ToString(),
Guid.Empty) Guid.Empty)
{ {
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"), _localization.GetLocalizedString("VersionNumber"),
e.Argument.version) e.Version)
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
@ -510,11 +499,10 @@ namespace Emby.Server.Implementations.Activity
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure; _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
_userManager.UserCreated -= OnUserCreated; _userManager.OnUserCreated -= OnUserCreated;
_userManager.UserPasswordChanged -= OnUserPasswordChanged; _userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
_userManager.UserDeleted -= OnUserDeleted; _userManager.OnUserDeleted -= OnUserDeleted;
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated; _userManager.OnUserLockedOut -= OnUserLockedOut;
_userManager.UserLockedOut -= OnUserLockedOut;
} }
/// <summary> /// <summary>

View File

@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase
CommonApplicationPaths = applicationPaths; CommonApplicationPaths = applicationPaths;
XmlSerializer = xmlSerializer; XmlSerializer = xmlSerializer;
_fileSystem = fileSystem; _fileSystem = fileSystem;
Logger = loggerFactory.CreateLogger(GetType().Name); Logger = loggerFactory.CreateLogger<BaseConfigurationManager>();
UpdateCachePath(); UpdateCachePath();
} }
@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.AppBase
/// Gets the logger. /// Gets the logger.
/// </summary> /// </summary>
/// <value>The logger.</value> /// <value>The logger.</value>
protected ILogger Logger { get; private set; } protected ILogger<BaseConfigurationManager> Logger { get; private set; }
/// <summary> /// <summary>
/// Gets the XML serializer. /// Gets the XML serializer.

View File

@ -45,6 +45,7 @@ using Emby.Server.Implementations.Services;
using Emby.Server.Implementations.Session; using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.TV; using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates; using Emby.Server.Implementations.Updates;
using Emby.Server.Implementations.SyncPlay;
using MediaBrowser.Api; using MediaBrowser.Api;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -78,6 +79,7 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Controller.TV; using MediaBrowser.Controller.TV;
using MediaBrowser.Controller.SyncPlay;
using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
@ -171,7 +173,7 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// Gets the logger. /// Gets the logger.
/// </summary> /// </summary>
protected ILogger Logger { get; } protected ILogger<ApplicationHost> Logger { get; }
private IPlugin[] _plugins; private IPlugin[] _plugins;
@ -560,11 +562,8 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>();
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>)); serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
serviceCollection.AddSingleton<IUserManager, UserManager>();
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
@ -613,6 +612,8 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>(); serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
serviceCollection.AddSingleton<LiveTvDtoService>(); serviceCollection.AddSingleton<LiveTvDtoService>();
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>(); serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
@ -655,15 +656,11 @@ namespace Emby.Server.Implementations
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize(); ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
SetStaticProperties(); SetStaticProperties();
var userManager = (UserManager)Resolve<IUserManager>();
userManager.Initialize();
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>(); var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager); ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, Resolve<IUserManager>());
FindParts(); FindParts();
} }
@ -746,7 +743,6 @@ namespace Emby.Server.Implementations
BaseItem.ProviderManager = Resolve<IProviderManager>(); BaseItem.ProviderManager = Resolve<IProviderManager>();
BaseItem.LocalizationManager = Resolve<ILocalizationManager>(); BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
BaseItem.ItemRepository = Resolve<IItemRepository>(); BaseItem.ItemRepository = Resolve<IItemRepository>();
User.UserManager = Resolve<IUserManager>();
BaseItem.FileSystem = _fileSystemManager; BaseItem.FileSystem = _fileSystemManager;
BaseItem.UserDataManager = Resolve<IUserDataManager>(); BaseItem.UserDataManager = Resolve<IUserDataManager>();
BaseItem.ChannelManager = Resolve<IChannelManager>(); BaseItem.ChannelManager = Resolve<IChannelManager>();
@ -960,7 +956,7 @@ namespace Emby.Server.Implementations
} }
/// <summary> /// <summary>
/// Notifies that the kernel that a change has been made that requires a restart /// Notifies that the kernel that a change has been made that requires a restart.
/// </summary> /// </summary>
public void NotifyPendingRestart() public void NotifyPendingRestart()
{ {
@ -1238,7 +1234,7 @@ namespace Emby.Server.Implementations
if (addresses.Count == 0) if (addresses.Count == 0)
{ {
addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); addresses.AddRange(_networkManager.GetLocalIpAddresses());
} }
var resultList = new List<IPAddress>(); var resultList = new List<IPAddress>();

View File

@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Browser
} }
catch (Exception ex) catch (Exception ex)
{ {
var logger = appHost.Resolve<ILogger>(); var logger = appHost.Resolve<ILogger<IServerApplicationHost>>();
logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl); logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
} }
} }

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
@ -13,8 +14,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Channels; using MediaBrowser.Model.Channels;
@ -24,6 +23,11 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Server.Implementations.Channels namespace Emby.Server.Implementations.Channels
{ {
@ -36,7 +40,7 @@ namespace Emby.Server.Implementations.Channels
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger; private readonly ILogger<ChannelManager> _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
@ -46,14 +50,14 @@ namespace Emby.Server.Implementations.Channels
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>(); new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ChannelManager"/> class. /// Initializes a new instance of the <see cref="ChannelManager"/> class.
/// </summary> /// </summary>
/// <param name="userManager">The user manager.</param> /// <param name="userManager">The user manager.</param>
/// <param name="dtoService">The dto service.</param> /// <param name="dtoService">The dto service.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
/// <param name="loggerFactory">The logger factory.</param> /// <param name="logger">The logger.</param>
/// <param name="config">The server configuration manager.</param> /// <param name="config">The server configuration manager.</param>
/// <param name="fileSystem">The filesystem.</param> /// <param name="fileSystem">The filesystem.</param>
/// <param name="userDataManager">The user data manager.</param> /// <param name="userDataManager">The user data manager.</param>
@ -791,7 +795,8 @@ namespace Emby.Server.Implementations.Channels
return result; return result;
} }
private async Task<ChannelItemResult> GetChannelItems(IChannel channel, private async Task<ChannelItemResult> GetChannelItems(
IChannel channel,
User user, User user,
string externalFolderId, string externalFolderId,
ChannelItemSortField? sortField, ChannelItemSortField? sortField,
@ -1067,7 +1072,7 @@ namespace Emby.Server.Implementations.Channels
} }
// was used for status // was used for status
//if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal)) // if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
//{ //{
// item.ExternalEtag = info.Etag; // item.ExternalEtag = info.Etag;
// forceUpdate = true; // forceUpdate = true;

View File

@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Channels
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{ {
private readonly IChannelManager _channelManager; private readonly IChannelManager _channelManager;
private readonly ILogger _logger; private readonly ILogger<RefreshChannelsScheduledTask> _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;

View File

@ -5,6 +5,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -29,7 +30,7 @@ namespace Emby.Server.Implementations.Collections
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _iLibraryMonitor; private readonly ILibraryMonitor _iLibraryMonitor;
private readonly ILogger _logger; private readonly ILogger<CollectionManager> _logger;
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly ILocalizationManager _localizationManager; private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
@ -56,7 +57,7 @@ namespace Emby.Server.Implementations.Collections
_libraryManager = libraryManager; _libraryManager = libraryManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_iLibraryMonitor = iLibraryMonitor; _iLibraryMonitor = iLibraryMonitor;
_logger = loggerFactory.CreateLogger(nameof(CollectionManager)); _logger = loggerFactory.CreateLogger<CollectionManager>();
_providerManager = providerManager; _providerManager = providerManager;
_localizationManager = localizationManager; _localizationManager = localizationManager;
_appPaths = appPaths; _appPaths = appPaths;
@ -370,7 +371,7 @@ namespace Emby.Server.Implementations.Collections
{ {
private readonly CollectionManager _collectionManager; private readonly CollectionManager _collectionManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILogger _logger; private readonly ILogger<CollectionManagerEntryPoint> _logger;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class. /// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.

View File

@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Configuration
ValidateMetadataPath(newConfig); ValidateMetadataPath(newConfig);
ValidateSslCertificate(newConfig); ValidateSslCertificate(newConfig);
ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration> { Argument = newConfig }); ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration>(newConfig));
base.ReplaceConfiguration(newConfiguration); base.ReplaceConfiguration(newConfiguration);
} }

View File

@ -17,7 +17,6 @@ namespace Emby.Server.Implementations
{ {
{ HostWebClientKey, bool.TrueString }, { HostWebClientKey, bool.TrueString },
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" },
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
{ FfmpegProbeSizeKey, "1G" }, { FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" }, { FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.TrueString } { PlaylistsAllowDuplicatesKey, bool.TrueString }

View File

@ -1,3 +1,5 @@
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -129,8 +131,6 @@ namespace Emby.Server.Implementations.Cryptography
_randomNumberGenerator.Dispose(); _randomNumberGenerator.Dispose();
} }
_randomNumberGenerator = null;
_disposed = true; _disposed = true;
} }
} }

View File

@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Data
/// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class. /// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
protected BaseSqliteRepository(ILogger logger) protected BaseSqliteRepository(ILogger<BaseSqliteRepository> logger)
{ {
Logger = logger; Logger = logger;
} }
@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Data
/// Gets the logger. /// Gets the logger.
/// </summary> /// </summary>
/// <value>The logger.</value> /// <value>The logger.</value>
protected ILogger Logger { get; } protected ILogger<BaseSqliteRepository> Logger { get; }
/// <summary> /// <summary>
/// Gets the default connection flags. /// Gets the default connection flags.
@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.Data
} }
return false; return false;
}, ReadTransactionMode); }, ReadTransactionMode);
} }
@ -248,12 +247,12 @@ namespace Emby.Server.Implementations.Data
public enum SynchronousMode public enum SynchronousMode
{ {
/// <summary> /// <summary>
/// SQLite continues without syncing as soon as it has handed data off to the operating system /// SQLite continues without syncing as soon as it has handed data off to the operating system.
/// </summary> /// </summary>
Off = 0, Off = 0,
/// <summary> /// <summary>
/// SQLite database engine will still sync at the most critical moments /// SQLite database engine will still sync at the most critical moments.
/// </summary> /// </summary>
Normal = 1, Normal = 1,

View File

@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Data
public class CleanDatabaseScheduledTask : ILibraryPostScanTask public class CleanDatabaseScheduledTask : ILibraryPostScanTask
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger; private readonly ILogger<CleanDatabaseScheduledTask> _logger;
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger) public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
{ {
@ -51,7 +51,6 @@ namespace Emby.Server.Implementations.Data
_libraryManager.DeleteItem(item, new DeleteOptions _libraryManager.DeleteItem(item, new DeleteOptions
{ {
DeleteFileLocation = false DeleteFileLocation = false
}); });
} }

View File

@ -1,4 +1,4 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Data
} }
/// <summary> /// <summary>
/// Opens the connection to the database /// Opens the connection to the database.
/// </summary> /// </summary>
/// <returns>Task.</returns> /// <returns>Task.</returns>
private void InitializeInternal() private void InitializeInternal()
@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Data
} }
/// <summary> /// <summary>
/// Save the display preferences associated with an item in the repo /// Save the display preferences associated with an item in the repo.
/// </summary> /// </summary>
/// <param name="displayPreferences">The display preferences.</param> /// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param> /// <param name="userId">The user id.</param>
@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Data
} }
/// <summary> /// <summary>
/// Save all display preferences associated with a user in the repo /// Save all display preferences associated with a user in the repo.
/// </summary> /// </summary>
/// <param name="displayPreferences">The display preferences.</param> /// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param> /// <param name="userId">The user id.</param>

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -134,10 +135,12 @@ namespace Emby.Server.Implementations.Data
{ {
throw new ArgumentNullException(nameof(userData)); throw new ArgumentNullException(nameof(userData));
} }
if (internalUserId <= 0) if (internalUserId <= 0)
{ {
throw new ArgumentNullException(nameof(internalUserId)); throw new ArgumentNullException(nameof(internalUserId));
} }
if (string.IsNullOrEmpty(key)) if (string.IsNullOrEmpty(key))
{ {
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
@ -152,6 +155,7 @@ namespace Emby.Server.Implementations.Data
{ {
throw new ArgumentNullException(nameof(userData)); throw new ArgumentNullException(nameof(userData));
} }
if (internalUserId <= 0) if (internalUserId <= 0)
{ {
throw new ArgumentNullException(nameof(internalUserId)); throw new ArgumentNullException(nameof(internalUserId));
@ -234,7 +238,7 @@ namespace Emby.Server.Implementations.Data
} }
/// <summary> /// <summary>
/// Persist all user data for the specified user /// Persist all user data for the specified user.
/// </summary> /// </summary>
private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken) private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken)
{ {
@ -308,7 +312,7 @@ namespace Emby.Server.Implementations.Data
} }
/// <summary> /// <summary>
/// Return all user-data associated with the given user /// Return all user-data associated with the given user.
/// </summary> /// </summary>
/// <param name="internalUserId"></param> /// <param name="internalUserId"></param>
/// <returns></returns> /// <returns></returns>
@ -338,7 +342,7 @@ namespace Emby.Server.Implementations.Data
} }
/// <summary> /// <summary>
/// Read a row from the specified reader into the provided userData object /// Read a row from the specified reader into the provided userData object.
/// </summary> /// </summary>
/// <param name="reader"></param> /// <param name="reader"></param>
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader) private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Data
var userData = new UserItemData(); var userData = new UserItemData();
userData.Key = reader[0].ToString(); userData.Key = reader[0].ToString();
//userData.UserId = reader[1].ReadGuidFromBlob(); // userData.UserId = reader[1].ReadGuidFromBlob();
if (reader[2].SQLiteType != SQLiteType.Null) if (reader[2].SQLiteType != SQLiteType.Null)
{ {

View File

@ -1,240 +0,0 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Persistence;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
{
/// <summary>
/// Class SQLiteUserRepository
/// </summary>
public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
{
private readonly JsonSerializerOptions _jsonOptions;
public SqliteUserRepository(
ILogger<SqliteUserRepository> logger,
IServerApplicationPaths appPaths)
: base(logger)
{
_jsonOptions = JsonDefaults.GetOptions();
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
}
/// <summary>
/// Gets the name of the repository
/// </summary>
/// <value>The name.</value>
public string Name => "SQLite";
/// <summary>
/// Opens the connection to the database.
/// </summary>
public void Initialize()
{
using (var connection = GetConnection())
{
var localUsersTableExists = TableExists(connection, "LocalUsersv2");
connection.RunQueries(new[] {
"create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)",
"drop index if exists idx_users"
});
if (!localUsersTableExists && TableExists(connection, "Users"))
{
TryMigrateToLocalUsersTable(connection);
}
RemoveEmptyPasswordHashes(connection);
}
}
private void TryMigrateToLocalUsersTable(ManagedConnection connection)
{
try
{
connection.RunQueries(new[]
{
"INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users"
});
}
catch (Exception ex)
{
Logger.LogError(ex, "Error migrating users database");
}
}
private void RemoveEmptyPasswordHashes(ManagedConnection connection)
{
foreach (var user in RetrieveAllUsers(connection))
{
// If the user password is the sha1 hash of the empty string, remove it
if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
&& !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
{
continue;
}
user.Password = null;
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
connection.RunInTransaction(db =>
{
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
{
statement.TryBind("@InternalId", user.InternalId);
statement.TryBind("@data", serialized);
statement.MoveNext();
}
}, TransactionMode);
}
}
/// <summary>
/// Save a user in the repo
/// </summary>
public void CreateUser(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
using (var connection = GetConnection())
{
connection.RunInTransaction(db =>
{
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
{
statement.TryBind("@guid", user.Id.ToByteArray());
statement.TryBind("@data", serialized);
statement.MoveNext();
}
var createdUser = GetUser(user.Id, connection);
if (createdUser == null)
{
throw new ApplicationException("created user should never be null");
}
user.InternalId = createdUser.InternalId;
}, TransactionMode);
}
}
public void UpdateUser(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
using (var connection = GetConnection())
{
connection.RunInTransaction(db =>
{
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
{
statement.TryBind("@InternalId", user.InternalId);
statement.TryBind("@data", serialized);
statement.MoveNext();
}
}, TransactionMode);
}
}
private User GetUser(Guid guid, ManagedConnection connection)
{
using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
{
statement.TryBind("@guid", guid);
foreach (var row in statement.ExecuteQuery())
{
return GetUser(row);
}
}
return null;
}
private User GetUser(IReadOnlyList<IResultSetValue> row)
{
var id = row[0].ToInt64();
var guid = row[1].ReadGuidFromBlob();
var user = JsonSerializer.Deserialize<User>(row[2].ToBlob(), _jsonOptions);
user.InternalId = id;
user.Id = guid;
return user;
}
/// <summary>
/// Retrieve all users from the database
/// </summary>
/// <returns>IEnumerable{User}.</returns>
public List<User> RetrieveAllUsers()
{
using (var connection = GetConnection(true))
{
return new List<User>(RetrieveAllUsers(connection));
}
}
/// <summary>
/// Retrieve all users from the database
/// </summary>
/// <returns>IEnumerable{User}.</returns>
private IEnumerable<User> RetrieveAllUsers(ManagedConnection connection)
{
foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
{
yield return GetUser(row);
}
}
/// <summary>
/// Deletes the user.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">user</exception>
public void DeleteUser(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
using (var connection = GetConnection())
{
connection.RunInTransaction(db =>
{
using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
{
statement.TryBind("@id", user.InternalId);
statement.MoveNext();
}
}, TransactionMode);
}
}
}
}

View File

@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Devices
public class DeviceId public class DeviceId
{ {
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger; private readonly ILogger<DeviceId> _logger;
private readonly object _syncLock = new object(); private readonly object _syncLock = new object();
@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Devices
public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory) public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
{ {
_appPaths = appPaths; _appPaths = appPaths;
_logger = loggerFactory.CreateLogger("SystemId"); _logger = loggerFactory.CreateLogger<DeviceId>();
} }
public string Value => _id ?? (_id = GetDeviceId()); public string Value => _id ?? (_id = GetDeviceId());

View File

@ -5,10 +5,11 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Devices; using MediaBrowser.Model.Devices;
@ -16,7 +17,6 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using MediaBrowser.Model.Users;
namespace Emby.Server.Implementations.Devices namespace Emby.Server.Implementations.Devices
{ {
@ -27,11 +27,10 @@ namespace Emby.Server.Implementations.Devices
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IAuthenticationRepository _authRepo; private readonly IAuthenticationRepository _authRepo;
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache; private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
private readonly object _capabilitiesSyncLock = new object();
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
private readonly object _capabilitiesSyncLock = new object();
public DeviceManager( public DeviceManager(
IAuthenticationRepository authRepo, IAuthenticationRepository authRepo,
IJsonSerializer json, IJsonSerializer json,
@ -62,13 +61,7 @@ namespace Emby.Server.Implementations.Devices
{ {
_authRepo.UpdateDeviceOptions(deviceId, options); _authRepo.UpdateDeviceOptions(deviceId, options);
if (DeviceOptionsUpdated != null) DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options)));
{
DeviceOptionsUpdated(this, new GenericEventArgs<Tuple<string, DeviceOptions>>()
{
Argument = new Tuple<string, DeviceOptions>(deviceId, options)
});
}
} }
public DeviceOptions GetDeviceOptions(string deviceId) public DeviceOptions GetDeviceOptions(string deviceId)
@ -119,7 +112,7 @@ namespace Emby.Server.Implementations.Devices
{ {
IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
{ {
//UserId = query.UserId // UserId = query.UserId
HasUser = true HasUser = true
}).Items; }).Items;
@ -176,12 +169,18 @@ namespace Emby.Server.Implementations.Devices
{ {
throw new ArgumentException("user not found"); throw new ArgumentException("user not found");
} }
if (string.IsNullOrEmpty(deviceId)) if (string.IsNullOrEmpty(deviceId))
{ {
throw new ArgumentNullException(nameof(deviceId)); throw new ArgumentNullException(nameof(deviceId));
} }
if (!CanAccessDevice(user.Policy, deviceId)) if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator))
{
return true;
}
if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase))
{ {
var capabilities = GetCapabilities(deviceId); var capabilities = GetCapabilities(deviceId);
@ -193,20 +192,5 @@ namespace Emby.Server.Implementations.Devices
return true; return true;
} }
private static bool CanAccessDevice(UserPolicy policy, string id)
{
if (policy.EnableAllDevices)
{
return true;
}
if (policy.IsAdministrator)
{
return true;
}
return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase);
}
} }
} }

View File

@ -6,14 +6,14 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
@ -24,12 +24,20 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Person = MediaBrowser.Controller.Entities.Person;
using Photo = MediaBrowser.Controller.Entities.Photo;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Server.Implementations.Dto namespace Emby.Server.Implementations.Dto
{ {
public class DtoService : IDtoService public class DtoService : IDtoService
{ {
private readonly ILogger _logger; private readonly ILogger<DtoService> _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IUserDataManager _userDataRepository; private readonly IUserDataManager _userDataRepository;
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
@ -66,7 +74,7 @@ namespace Emby.Server.Implementations.Dto
} }
/// <summary> /// <summary>
/// Converts a BaseItem to a DTOBaseItem /// Converts a BaseItem to a DTOBaseItem.
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <param name="fields">The fields.</param> /// <param name="fields">The fields.</param>
@ -269,6 +277,7 @@ namespace Emby.Server.Implementations.Dto
dto.EpisodeTitle = dto.Name; dto.EpisodeTitle = dto.Name;
dto.Name = dto.SeriesName; dto.Name = dto.SeriesName;
} }
liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user); liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
} }
@ -284,6 +293,7 @@ namespace Emby.Server.Implementations.Dto
{ {
continue; continue;
} }
var containers = container.Split(new[] { ',' }); var containers = container.Split(new[] { ',' });
if (containers.Length < 2) if (containers.Length < 2)
{ {
@ -384,7 +394,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.ChildCount)) if (options.ContainsField(ItemFields.ChildCount))
{ {
dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user); dto.ChildCount ??= GetChildCount(folder, user);
} }
} }
@ -398,7 +408,6 @@ namespace Emby.Server.Implementations.Dto
dto.DateLastMediaAdded = folder.DateLastMediaAdded; dto.DateLastMediaAdded = folder.DateLastMediaAdded;
} }
} }
else else
{ {
if (options.EnableUserData) if (options.EnableUserData)
@ -414,7 +423,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.BasicSyncInfo)) if (options.ContainsField(ItemFields.BasicSyncInfo))
{ {
var userCanSync = user != null && user.Policy.EnableContentDownloading; var userCanSync = user != null && user.HasPermission(PermissionKind.EnableContentDownloading);
if (userCanSync && item.SupportsExternalTransfer) if (userCanSync && item.SupportsExternalTransfer)
{ {
dto.SupportsSync = true; dto.SupportsSync = true;
@ -435,7 +444,7 @@ namespace Emby.Server.Implementations.Dto
} }
/// <summary> /// <summary>
/// Gets client-side Id of a server-side BaseItem /// Gets client-side Id of a server-side BaseItem.
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
@ -449,6 +458,7 @@ namespace Emby.Server.Implementations.Dto
{ {
dto.SeriesName = item.SeriesName; dto.SeriesName = item.SeriesName;
} }
private static void SetPhotoProperties(BaseItemDto dto, Photo item) private static void SetPhotoProperties(BaseItemDto dto, Photo item)
{ {
dto.CameraMake = item.CameraMake; dto.CameraMake = item.CameraMake;
@ -530,7 +540,7 @@ namespace Emby.Server.Implementations.Dto
} }
/// <summary> /// <summary>
/// Attaches People DTO's to a DTOBaseItem /// Attaches People DTO's to a DTOBaseItem.
/// </summary> /// </summary>
/// <param name="dto">The dto.</param> /// <param name="dto">The dto.</param>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
@ -547,22 +557,27 @@ namespace Emby.Server.Implementations.Dto
{ {
return 0; return 0;
} }
if (i.IsType(PersonType.GuestStar)) if (i.IsType(PersonType.GuestStar))
{ {
return 1; return 1;
} }
if (i.IsType(PersonType.Director)) if (i.IsType(PersonType.Director))
{ {
return 2; return 2;
} }
if (i.IsType(PersonType.Writer)) if (i.IsType(PersonType.Writer))
{ {
return 3; return 3;
} }
if (i.IsType(PersonType.Producer)) if (i.IsType(PersonType.Producer))
{ {
return 4; return 4;
} }
if (i.IsType(PersonType.Composer)) if (i.IsType(PersonType.Composer))
{ {
return 4; return 4;
@ -586,7 +601,6 @@ namespace Emby.Server.Implementations.Dto
_logger.LogError(ex, "Error getting person {Name}", c); _logger.LogError(ex, "Error getting person {Name}", c);
return null; return null;
} }
}).Where(i => i != null) }).Where(i => i != null)
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.Select(x => x.First()) .Select(x => x.First())
@ -605,8 +619,9 @@ namespace Emby.Server.Implementations.Dto
if (dictionary.TryGetValue(person.Name, out Person entity)) if (dictionary.TryGetValue(person.Name, out Person entity))
{ {
baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture); baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
baseItemPerson.ImageBlurHashes = dto.ImageBlurHashes;
list.Add(baseItemPerson); list.Add(baseItemPerson);
} }
} }
@ -654,8 +669,72 @@ namespace Emby.Server.Implementations.Dto
return _libraryManager.GetGenreId(name); return _libraryManager.GetGenreId(name);
} }
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
{
var image = item.GetImageInfo(imageType, imageIndex);
if (image != null)
{
return GetTagAndFillBlurhash(dto, item, image);
}
return null;
}
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
{
var tag = GetImageCacheTag(item, image);
if (!string.IsNullOrEmpty(image.BlurHash))
{
if (dto.ImageBlurHashes == null)
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
if (!dto.ImageBlurHashes.ContainsKey(image.Type))
{
dto.ImageBlurHashes[image.Type] = new Dictionary<string, string>();
}
dto.ImageBlurHashes[image.Type][tag] = image.BlurHash;
}
return tag;
}
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
{
return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
}
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInfo> images)
{
var tags = GetImageTags(item, images);
var hashes = new Dictionary<string, string>();
for (int i = 0; i < images.Count; i++)
{
var img = images[i];
if (!string.IsNullOrEmpty(img.BlurHash))
{
var tag = tags[i];
hashes[tag] = img.BlurHash;
}
}
if (hashes.Count > 0)
{
if (dto.ImageBlurHashes == null)
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
dto.ImageBlurHashes[imageType] = hashes;
}
return tags;
}
/// <summary> /// <summary>
/// Sets simple property values on a DTOBaseItem /// Sets simple property values on a DTOBaseItem.
/// </summary> /// </summary>
/// <param name="dto">The dto.</param> /// <param name="dto">The dto.</param>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
@ -674,8 +753,8 @@ namespace Emby.Server.Implementations.Dto
dto.LockData = item.IsLocked; dto.LockData = item.IsLocked;
dto.ForcedSortName = item.ForcedSortName; dto.ForcedSortName = item.ForcedSortName;
} }
dto.Container = item.Container;
dto.Container = item.Container;
dto.EndDate = item.EndDate; dto.EndDate = item.EndDate;
if (options.ContainsField(ItemFields.ExternalUrls)) if (options.ContainsField(ItemFields.ExternalUrls))
@ -694,10 +773,12 @@ namespace Emby.Server.Implementations.Dto
dto.AspectRatio = hasAspectRatio.AspectRatio; dto.AspectRatio = hasAspectRatio.AspectRatio;
} }
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
var backdropLimit = options.GetImageLimit(ImageType.Backdrop); var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
if (backdropLimit > 0) if (backdropLimit > 0)
{ {
dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList()); dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
} }
if (options.ContainsField(ItemFields.ScreenshotImageTags)) if (options.ContainsField(ItemFields.ScreenshotImageTags))
@ -705,7 +786,7 @@ namespace Emby.Server.Implementations.Dto
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot); var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
if (screenshotLimit > 0) if (screenshotLimit > 0)
{ {
dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList()); dto.ScreenshotImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Screenshot, screenshotLimit);
} }
} }
@ -721,12 +802,11 @@ namespace Emby.Server.Implementations.Dto
// Prevent implicitly captured closure // Prevent implicitly captured closure
var currentItem = item; var currentItem = item;
foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)) foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
.ToList())
{ {
if (options.GetImageLimit(image.Type) > 0) if (options.GetImageLimit(image.Type) > 0)
{ {
var tag = GetImageCacheTag(item, image); var tag = GetTagAndFillBlurhash(dto, item, image);
if (tag != null) if (tag != null)
{ {
@ -871,11 +951,10 @@ namespace Emby.Server.Implementations.Dto
if (albumParent != null) if (albumParent != null)
{ {
dto.AlbumId = albumParent.Id; dto.AlbumId = albumParent.Id;
dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
} }
//if (options.ContainsField(ItemFields.MediaSourceCount)) // if (options.ContainsField(ItemFields.MediaSourceCount))
//{ //{
// Songs always have one // Songs always have one
//} //}
@ -885,13 +964,13 @@ namespace Emby.Server.Implementations.Dto
{ {
dto.Artists = hasArtist.Artists; dto.Artists = hasArtist.Artists;
//var artistItems = _libraryManager.GetArtists(new InternalItemsQuery // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
//{ //{
// EnableTotalRecordCount = false, // EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
//}); //});
//dto.ArtistItems = artistItems.Items // dto.ArtistItems = artistItems.Items
// .Select(i => // .Select(i =>
// { // {
// var artist = i.Item1; // var artist = i.Item1;
@ -904,7 +983,7 @@ namespace Emby.Server.Implementations.Dto
// .ToList(); // .ToList();
// Include artists that are not in the database yet, e.g., just added via metadata editor // Include artists that are not in the database yet, e.g., just added via metadata editor
//var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
dto.ArtistItems = hasArtist.Artists dto.ArtistItems = hasArtist.Artists
//.Except(foundArtists, new DistinctNameComparer()) //.Except(foundArtists, new DistinctNameComparer())
.Select(i => .Select(i =>
@ -929,7 +1008,6 @@ namespace Emby.Server.Implementations.Dto
} }
return null; return null;
}).Where(i => i != null).ToArray(); }).Where(i => i != null).ToArray();
} }
@ -938,13 +1016,13 @@ namespace Emby.Server.Implementations.Dto
{ {
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
//var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
//{ //{
// EnableTotalRecordCount = false, // EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
//}); //});
//dto.AlbumArtists = artistItems.Items // dto.AlbumArtists = artistItems.Items
// .Select(i => // .Select(i =>
// { // {
// var artist = i.Item1; // var artist = i.Item1;
@ -980,7 +1058,6 @@ namespace Emby.Server.Implementations.Dto
} }
return null; return null;
}).Where(i => i != null).ToArray(); }).Where(i => i != null).ToArray();
} }
@ -1094,12 +1171,12 @@ namespace Emby.Server.Implementations.Dto
// this block will add the series poster for episodes without a poster // this block will add the series poster for episodes without a poster
// TODO maybe remove the if statement entirely // TODO maybe remove the if statement entirely
//if (options.ContainsField(ItemFields.SeriesPrimaryImage)) // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{ {
episodeSeries = episodeSeries ?? episode.Series; episodeSeries = episodeSeries ?? episode.Series;
if (episodeSeries != null) if (episodeSeries != null)
{ {
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary); dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
} }
} }
@ -1140,12 +1217,12 @@ namespace Emby.Server.Implementations.Dto
// this block will add the series poster for seasons without a poster // this block will add the series poster for seasons without a poster
// TODO maybe remove the if statement entirely // TODO maybe remove the if statement entirely
//if (options.ContainsField(ItemFields.SeriesPrimaryImage)) // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{ {
series = series ?? season.Series; series = series ?? season.Series;
if (series != null) if (series != null)
{ {
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
} }
} }
} }
@ -1275,9 +1352,10 @@ namespace Emby.Server.Implementations.Dto
if (image != null) if (image != null)
{ {
dto.ParentLogoItemId = GetDtoId(parent); dto.ParentLogoItemId = GetDtoId(parent);
dto.ParentLogoImageTag = GetImageCacheTag(parent, image); dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
} }
} }
if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null) if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null)
{ {
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art); var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
@ -1285,9 +1363,10 @@ namespace Emby.Server.Implementations.Dto
if (image != null) if (image != null)
{ {
dto.ParentArtItemId = GetDtoId(parent); dto.ParentArtItemId = GetDtoId(parent);
dto.ParentArtImageTag = GetImageCacheTag(parent, image); dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
} }
} }
if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView)) if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
{ {
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb); var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
@ -1295,9 +1374,10 @@ namespace Emby.Server.Implementations.Dto
if (image != null) if (image != null)
{ {
dto.ParentThumbItemId = GetDtoId(parent); dto.ParentThumbItemId = GetDtoId(parent);
dto.ParentThumbImageTag = GetImageCacheTag(parent, image); dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
} }
} }
if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0))) if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0)))
{ {
var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList(); var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
@ -1305,7 +1385,7 @@ namespace Emby.Server.Implementations.Dto
if (images.Count > 0) if (images.Count > 0)
{ {
dto.ParentBackdropItemId = GetDtoId(parent); dto.ParentBackdropItemId = GetDtoId(parent);
dto.ParentBackdropImageTags = GetImageTags(parent, images); dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
} }
} }

View File

@ -24,7 +24,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="IPNetwork2" Version="2.4.0.126" /> <PackageReference Include="IPNetwork2" Version="2.5.211" />
<PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" /> <PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" /> <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
@ -34,15 +34,16 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" /> <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
<PackageReference Include="Mono.Nat" Version="2.0.1" /> <PackageReference Include="Mono.Nat" Version="2.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
<PackageReference Include="sharpcompress" Version="0.25.0" /> <PackageReference Include="sharpcompress" Version="0.25.1" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.0.9" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -53,6 +54,7 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<!-- Code Analyzers--> <!-- Code Analyzers-->

View File

@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.EntryPoints
public class ExternalPortForwarding : IServerEntryPoint public class ExternalPortForwarding : IServerEntryPoint
{ {
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger; private readonly ILogger<ExternalPortForwarding> _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery; private readonly IDeviceDiscovery _deviceDiscovery;

View File

@ -6,6 +6,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -28,7 +29,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly ILogger _logger; private readonly ILogger<LibraryChangedNotifier> _logger;
/// <summary> /// <summary>
/// The library changed sync lock. /// The library changed sync lock.
@ -131,7 +132,6 @@ namespace Emby.Server.Implementations.EntryPoints
} }
catch catch
{ {
} }
} }
} }
@ -302,7 +302,7 @@ namespace Emby.Server.Implementations.EntryPoints
.Select(x => x.First()) .Select(x => x.First())
.ToList(); .ToList();
SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None); SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None).GetAwaiter().GetResult();
if (LibraryUpdateTimer != null) if (LibraryUpdateTimer != null)
{ {
@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.EntryPoints
/// <param name="foldersAddedTo">The folders added to.</param> /// <param name="foldersAddedTo">The folders added to.</param>
/// <param name="foldersRemovedFrom">The folders removed from.</param> /// <param name="foldersRemovedFrom">The folders removed from.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
private async void SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken) private async Task SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
{ {
var userIds = _sessionManager.Sessions var userIds = _sessionManager.Sessions
.Select(i => i.UserId) .Select(i => i.UserId)

View File

@ -4,6 +4,7 @@ using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
@ -17,7 +18,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly ILiveTvManager _liveTvManager; private readonly ILiveTvManager _liveTvManager;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly ILogger _logger; private readonly ILogger<RecordingNotifier> _logger;
public RecordingNotifier( public RecordingNotifier(
ISessionManager sessionManager, ISessionManager sessionManager,
@ -42,29 +43,29 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask; return Task.CompletedTask;
} }
private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{ {
SendMessage("SeriesTimerCreated", e.Argument); await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false);
} }
private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{ {
SendMessage("TimerCreated", e.Argument); await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false);
} }
private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{ {
SendMessage("SeriesTimerCancelled", e.Argument); await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false);
} }
private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{ {
SendMessage("TimerCancelled", e.Argument); await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false);
} }
private async void SendMessage(string name, TimerEventInfo info) private async Task SendMessage(string name, TimerEventInfo info)
{ {
var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList(); var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList();
try try
{ {

View File

@ -1,77 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
namespace Emby.Server.Implementations.EntryPoints
{
/// <summary>
/// Class RefreshUsersMetadata.
/// </summary>
public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
/// The user manager.
/// </summary>
private readonly IUserManager _userManager;
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
/// </summary>
public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
{
_userManager = userManager;
_fileSystem = fileSystem;
}
/// <inheritdoc />
public string Name => "Refresh Users";
/// <inheritdoc />
public string Key => "RefreshUsers";
/// <inheritdoc />
public string Description => "Refresh user infos";
/// <inheritdoc />
public string Category => "Library";
/// <inheritdoc />
public bool IsHidden => true;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
foreach (var user in _userManager.Users)
{
cancellationToken.ThrowIfCancellationRequested();
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
}
}
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
{
new TaskTriggerInfo
{
IntervalTicks = TimeSpan.FromDays(1).Ticks,
Type = TaskTriggerInfo.TriggerInterval
}
};
}
}
}

View File

@ -3,15 +3,16 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Updates;
namespace Emby.Server.Implementations.EntryPoints namespace Emby.Server.Implementations.EntryPoints
{ {
@ -67,10 +68,8 @@ namespace Emby.Server.Implementations.EntryPoints
/// <inheritdoc /> /// <inheritdoc />
public Task RunAsync() public Task RunAsync()
{ {
_userManager.UserDeleted += OnUserDeleted; _userManager.OnUserDeleted += OnUserDeleted;
_userManager.UserUpdated += OnUserUpdated; _userManager.OnUserUpdated += OnUserUpdated;
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
_userManager.UserConfigurationUpdated += OnUserConfigurationUpdated;
_appHost.HasPendingRestartChanged += OnHasPendingRestartChanged; _appHost.HasPendingRestartChanged += OnHasPendingRestartChanged;
@ -85,29 +84,29 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask; return Task.CompletedTask;
} }
private void OnPackageInstalling(object sender, InstallationEventArgs e) private async void OnPackageInstalling(object sender, InstallationInfo e)
{ {
SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo); await SendMessageToAdminSessions("PackageInstalling", e).ConfigureAwait(false);
} }
private void OnPackageInstallationCancelled(object sender, InstallationEventArgs e) private async void OnPackageInstallationCancelled(object sender, InstallationInfo e)
{ {
SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo); await SendMessageToAdminSessions("PackageInstallationCancelled", e).ConfigureAwait(false);
} }
private void OnPackageInstallationCompleted(object sender, InstallationEventArgs e) private async void OnPackageInstallationCompleted(object sender, InstallationInfo e)
{ {
SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo); await SendMessageToAdminSessions("PackageInstallationCompleted", e).ConfigureAwait(false);
} }
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
{ {
SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo); await SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo).ConfigureAwait(false);
} }
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e) private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
{ {
SendMessageToAdminSessions("ScheduledTaskEnded", e.Result); await SendMessageToAdminSessions("ScheduledTaskEnded", e.Result).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -115,9 +114,9 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary> /// </summary>
/// <param name="sender">The sender.</param> /// <param name="sender">The sender.</param>
/// <param name="e">The e.</param> /// <param name="e">The e.</param>
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e) private async void OnPluginUninstalled(object sender, IPlugin e)
{ {
SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo()); await SendMessageToAdminSessions("PluginUninstalled", e).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -125,9 +124,9 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary> /// </summary>
/// <param name="sender">The source of the event.</param> /// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
private void OnHasPendingRestartChanged(object sender, EventArgs e) private async void OnHasPendingRestartChanged(object sender, EventArgs e)
{ {
_sessionManager.SendRestartRequiredNotification(CancellationToken.None); await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -135,11 +134,11 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary> /// </summary>
/// <param name="sender">The sender.</param> /// <param name="sender">The sender.</param>
/// <param name="e">The e.</param> /// <param name="e">The e.</param>
private void OnUserUpdated(object sender, GenericEventArgs<User> e) private async void OnUserUpdated(object sender, GenericEventArgs<User> e)
{ {
var dto = _userManager.GetUserDto(e.Argument); var dto = _userManager.GetUserDto(e.Argument);
SendMessageToUserSession(e.Argument, "UserUpdated", dto); await SendMessageToUserSession(e.Argument, "UserUpdated", dto).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -147,26 +146,12 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary> /// </summary>
/// <param name="sender">The sender.</param> /// <param name="sender">The sender.</param>
/// <param name="e">The e.</param> /// <param name="e">The e.</param>
private void OnUserDeleted(object sender, GenericEventArgs<User> e) private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
{ {
SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)); await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false);
} }
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e) private async Task SendMessageToAdminSessions<T>(string name, T data)
{
var dto = _userManager.GetUserDto(e.Argument);
SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto);
}
private void OnUserConfigurationUpdated(object sender, GenericEventArgs<User> e)
{
var dto = _userManager.GetUserDto(e.Argument);
SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto);
}
private async void SendMessageToAdminSessions<T>(string name, T data)
{ {
try try
{ {
@ -174,11 +159,10 @@ namespace Emby.Server.Implementations.EntryPoints
} }
catch (Exception) catch (Exception)
{ {
} }
} }
private async void SendMessageToUserSession<T>(User user, string name, T data) private async Task SendMessageToUserSession<T>(User user, string name, T data)
{ {
try try
{ {
@ -190,7 +174,6 @@ namespace Emby.Server.Implementations.EntryPoints
} }
catch (Exception) catch (Exception)
{ {
} }
} }
@ -209,10 +192,8 @@ namespace Emby.Server.Implementations.EntryPoints
{ {
if (dispose) if (dispose)
{ {
_userManager.UserDeleted -= OnUserDeleted; _userManager.OnUserDeleted -= OnUserDeleted;
_userManager.UserUpdated -= OnUserUpdated; _userManager.OnUserUpdated -= OnUserUpdated;
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
_userManager.UserConfigurationUpdated -= OnUserConfigurationUpdated;
_installationManager.PluginUninstalled -= OnPluginUninstalled; _installationManager.PluginUninstalled -= OnPluginUninstalled;
_installationManager.PackageInstalling -= OnPackageInstalling; _installationManager.PackageInstalling -= OnPackageInstalling;

View File

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Emby.Server.Implementations.Udp; using Emby.Server.Implementations.Udp;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints namespace Emby.Server.Implementations.EntryPoints
@ -20,8 +21,9 @@ namespace Emby.Server.Implementations.EntryPoints
/// <summary> /// <summary>
/// The logger. /// The logger.
/// </summary> /// </summary>
private readonly ILogger _logger; private readonly ILogger<UdpServerEntryPoint> _logger;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IConfiguration _config;
/// <summary> /// <summary>
/// The UDP server. /// The UDP server.
@ -35,19 +37,20 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary> /// </summary>
public UdpServerEntryPoint( public UdpServerEntryPoint(
ILogger<UdpServerEntryPoint> logger, ILogger<UdpServerEntryPoint> logger,
IServerApplicationHost appHost) IServerApplicationHost appHost,
IConfiguration configuration)
{ {
_logger = logger; _logger = logger;
_appHost = appHost; _appHost = appHost;
_config = configuration;
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task RunAsync() public Task RunAsync()
{ {
_udpServer = new UdpServer(_logger, _appHost); _udpServer = new UdpServer(_logger, _appHost, _config);
_udpServer.Start(PortNumber, _cancellationTokenSource.Token); _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
return Task.CompletedTask;
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.HttpClientManager
/// </summary> /// </summary>
public class HttpClientManager : IHttpClient public class HttpClientManager : IHttpClient
{ {
private readonly ILogger _logger; private readonly ILogger<HttpClientManager> _logger;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IApplicationHost _appHost; private readonly IApplicationHost _appHost;
@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.HttpClientManager
=> SendAsync(options, HttpMethod.Get); => SendAsync(options, HttpMethod.Get);
/// <summary> /// <summary>
/// Performs a GET request and returns the resulting stream /// Performs a GET request and returns the resulting stream.
/// </summary> /// </summary>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <returns>Task{Stream}.</returns> /// <returns>Task{Stream}.</returns>

View File

@ -32,12 +32,12 @@ namespace Emby.Server.Implementations.HttpServer
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
/// <summary> /// <summary>
/// The _options /// The _options.
/// </summary> /// </summary>
private readonly IDictionary<string, string> _options = new Dictionary<string, string>(); private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
/// <summary> /// <summary>
/// The _requested ranges /// The _requested ranges.
/// </summary> /// </summary>
private List<KeyValuePair<long, long?>> _requestedRanges; private List<KeyValuePair<long, long?>> _requestedRanges;

View File

@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary> /// </summary>
public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
private readonly ILogger _logger; private readonly ILogger<HttpListenerHost> _logger;
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
@ -210,16 +210,8 @@ namespace Emby.Server.Implementations.HttpServer
} }
} }
private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog) private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog, bool ignoreStackTrace)
{ {
bool ignoreStackTrace =
ex is SocketException
|| ex is IOException
|| ex is OperationCanceledException
|| ex is SecurityException
|| ex is AuthenticationException
|| ex is FileNotFoundException;
if (ignoreStackTrace) if (ignoreStackTrace)
{ {
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog); _logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
@ -238,7 +230,9 @@ namespace Emby.Server.Implementations.HttpServer
httpRes.StatusCode = statusCode; httpRes.StatusCode = statusCode;
var errContent = NormalizeExceptionMessage(ex) ?? string.Empty; var errContent = _hostEnvironment.IsDevelopment()
? (NormalizeExceptionMessage(ex) ?? string.Empty)
: "Error processing request.";
httpRes.ContentType = "text/plain"; httpRes.ContentType = "text/plain";
httpRes.ContentLength = errContent.Length; httpRes.ContentLength = errContent.Length;
await httpRes.WriteAsync(errContent).ConfigureAwait(false); await httpRes.WriteAsync(errContent).ConfigureAwait(false);
@ -405,7 +399,7 @@ namespace Emby.Server.Implementations.HttpServer
var response = context.Response; var response = context.Response;
var localPath = context.Request.Path.ToString(); var localPath = context.Request.Path.ToString();
var req = new WebSocketSharpRequest(request, response, request.Path, _logger); var req = new WebSocketSharpRequest(request, response, request.Path);
return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted); return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted);
} }
@ -459,6 +453,7 @@ namespace Emby.Server.Implementations.HttpServer
{ {
httpRes.Headers.Add(key, value); httpRes.Headers.Add(key, value);
} }
httpRes.ContentType = "text/plain"; httpRes.ContentType = "text/plain";
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
return; return;
@ -505,14 +500,32 @@ namespace Emby.Server.Implementations.HttpServer
var requestInnerEx = GetActualException(requestEx); var requestInnerEx = GetActualException(requestEx);
var statusCode = GetStatusCode(requestInnerEx); var statusCode = GetStatusCode(requestInnerEx);
// Do not handle 500 server exceptions manually when in development mode foreach (var (key, value) in GetDefaultCorsHeaders(httpReq))
// The framework-defined development exception page will be returned instead {
if (statusCode == 500 && _hostEnvironment.IsDevelopment()) if (!httpRes.Headers.ContainsKey(key))
{
httpRes.Headers.Add(key, value);
}
}
bool ignoreStackTrace =
requestInnerEx is SocketException
|| requestInnerEx is IOException
|| requestInnerEx is OperationCanceledException
|| requestInnerEx is SecurityException
|| requestInnerEx is AuthenticationException
|| requestInnerEx is FileNotFoundException;
// Do not handle 500 server exceptions manually when in development mode.
// Instead, re-throw the exception so it can be handled by the DeveloperExceptionPageMiddleware.
// However, do not use the DeveloperExceptionPageMiddleware when the stack trace should be ignored,
// because it will log the stack trace when it handles the exception.
if (statusCode == 500 && !ignoreStackTrace && _hostEnvironment.IsDevelopment())
{ {
throw; throw;
} }
await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog).ConfigureAwait(false); await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog, ignoreStackTrace).ConfigureAwait(false);
} }
catch (Exception handlerException) catch (Exception handlerException)
{ {
@ -579,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer
} }
/// <summary> /// <summary>
/// Get the default CORS headers /// Get the default CORS headers.
/// </summary> /// </summary>
/// <param name="req"></param> /// <param name="req"></param>
/// <returns></returns> /// <returns></returns>

View File

@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary> /// <summary>
/// The logger. /// The logger.
/// </summary> /// </summary>
private readonly ILogger _logger; private readonly ILogger<HttpResultFactory> _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IStreamHelper _streamHelper; private readonly IStreamHelper _streamHelper;
@ -50,12 +50,13 @@ namespace Emby.Server.Implementations.HttpServer
_fileSystem = fileSystem; _fileSystem = fileSystem;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_streamHelper = streamHelper; _streamHelper = streamHelper;
_logger = loggerfactory.CreateLogger("HttpResultFactory"); _logger = loggerfactory.CreateLogger<HttpResultFactory>();
} }
/// <summary> /// <summary>
/// Gets the result. /// Gets the result.
/// </summary> /// </summary>
/// <param name="requestContext">The request context.</param>
/// <param name="content">The content.</param> /// <param name="content">The content.</param>
/// <param name="contentType">Type of the content.</param> /// <param name="contentType">Type of the content.</param>
/// <param name="responseHeaders">The response headers.</param> /// <param name="responseHeaders">The response headers.</param>
@ -255,16 +256,20 @@ namespace Emby.Server.Implementations.HttpServer
{ {
var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString(); var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString();
if (string.IsNullOrEmpty(acceptEncoding)) if (!string.IsNullOrEmpty(acceptEncoding))
{ {
//if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) // if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
// return "br"; // return "br";
if (acceptEncoding.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1) if (acceptEncoding.Contains("deflate", StringComparison.OrdinalIgnoreCase))
{
return "deflate"; return "deflate";
}
if (acceptEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1) if (acceptEncoding.Contains("gzip", StringComparison.OrdinalIgnoreCase))
{
return "gzip"; return "gzip";
}
} }
return null; return null;
@ -421,7 +426,7 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary> /// </summary>
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options) private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
{ {
bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; bool noCache = requestContext.Headers[HeaderNames.CacheControl].ToString().IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified); AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
if (!noCache) if (!noCache)
@ -575,13 +580,12 @@ namespace Emby.Server.Implementations.HttpServer
} }
catch (NotSupportedException) catch (NotSupportedException)
{ {
} }
} }
if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue) if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue)
{ {
var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest, _logger) var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest)
{ {
OnComplete = options.OnComplete OnComplete = options.OnComplete
}; };
@ -618,8 +622,11 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary> /// <summary>
/// Adds the caching responseHeaders. /// Adds the caching responseHeaders.
/// </summary> /// </summary>
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration, private void AddCachingHeaders(
bool noCache, DateTime? lastModifiedDate) IDictionary<string, string> responseHeaders,
TimeSpan? cacheDuration,
bool noCache,
DateTime? lastModifiedDate)
{ {
if (noCache) if (noCache)
{ {
@ -688,7 +695,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary> /// <summary>
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that.
/// </summary> /// </summary>
/// <param name="date">The date.</param> /// <param name="date">The date.</param>
/// <returns>DateTime.</returns> /// <returns>DateTime.</returns>

View File

@ -1,6 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -8,13 +9,45 @@ using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer namespace Emby.Server.Implementations.HttpServer
{ {
public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult
{ {
private const int BufferSize = 81920;
private readonly Dictionary<string, string> _options = new Dictionary<string, string>();
private List<KeyValuePair<long, long?>> _requestedRanges;
/// <summary>
/// Initializes a new instance of the <see cref="RangeRequestWriter" /> class.
/// </summary>
/// <param name="rangeHeader">The range header.</param>
/// <param name="contentLength">The content length.</param>
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest)
{
if (string.IsNullOrEmpty(contentType))
{
throw new ArgumentNullException(nameof(contentType));
}
RangeHeader = rangeHeader;
SourceStream = source;
IsHeadRequest = isHeadRequest;
ContentType = contentType;
Headers[HeaderNames.ContentType] = contentType;
Headers[HeaderNames.AcceptRanges] = "bytes";
StatusCode = HttpStatusCode.PartialContent;
SetRangeValues(contentLength);
}
/// <summary> /// <summary>
/// Gets or sets the source stream. /// Gets or sets the source stream.
/// </summary> /// </summary>
@ -29,19 +62,6 @@ namespace Emby.Server.Implementations.HttpServer
private long TotalContentLength { get; set; } private long TotalContentLength { get; set; }
public Action OnComplete { get; set; } public Action OnComplete { get; set; }
private readonly ILogger _logger;
private const int BufferSize = 81920;
/// <summary>
/// The _options
/// </summary>
private readonly Dictionary<string, string> _options = new Dictionary<string, string>();
/// <summary>
/// The us culture
/// </summary>
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary> /// <summary>
/// Additional HTTP Headers /// Additional HTTP Headers
@ -50,32 +70,57 @@ namespace Emby.Server.Implementations.HttpServer
public IDictionary<string, string> Headers => _options; public IDictionary<string, string> Headers => _options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RangeRequestWriter" /> class. /// Gets the requested ranges.
/// </summary> /// </summary>
/// <param name="rangeHeader">The range header.</param> /// <value>The requested ranges.</value>
/// <param name="contentLength">The content length.</param> protected List<KeyValuePair<long, long?>> RequestedRanges
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <param name="logger">The logger instance.</param>
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
{ {
if (string.IsNullOrEmpty(contentType)) get
{ {
throw new ArgumentNullException(nameof(contentType)); if (_requestedRanges == null)
{
_requestedRanges = new List<KeyValuePair<long, long?>>();
// Example: bytes=0-,32-63
var ranges = RangeHeader.Split('=')[1].Split(',');
foreach (var range in ranges)
{
var vals = range.Split('-');
long start = 0;
long? end = null;
if (!string.IsNullOrEmpty(vals[0]))
{
start = long.Parse(vals[0], CultureInfo.InvariantCulture);
}
if (!string.IsNullOrEmpty(vals[1]))
{
end = long.Parse(vals[1], CultureInfo.InvariantCulture);
}
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
}
}
return _requestedRanges;
} }
}
RangeHeader = rangeHeader; public string ContentType { get; set; }
SourceStream = source;
IsHeadRequest = isHeadRequest;
this._logger = logger;
ContentType = contentType; public IRequest RequestContext { get; set; }
Headers[HeaderNames.ContentType] = contentType;
Headers[HeaderNames.AcceptRanges] = "bytes";
StatusCode = HttpStatusCode.PartialContent;
SetRangeValues(contentLength); public object Response { get; set; }
public int Status { get; set; }
public HttpStatusCode StatusCode
{
get => (HttpStatusCode)Status;
set => Status = (int)value;
} }
/// <summary> /// <summary>
@ -109,49 +154,6 @@ namespace Emby.Server.Implementations.HttpServer
} }
} }
/// <summary>
/// The _requested ranges
/// </summary>
private List<KeyValuePair<long, long?>> _requestedRanges;
/// <summary>
/// Gets the requested ranges.
/// </summary>
/// <value>The requested ranges.</value>
protected List<KeyValuePair<long, long?>> RequestedRanges
{
get
{
if (_requestedRanges == null)
{
_requestedRanges = new List<KeyValuePair<long, long?>>();
// Example: bytes=0-,32-63
var ranges = RangeHeader.Split('=')[1].Split(',');
foreach (var range in ranges)
{
var vals = range.Split('-');
long start = 0;
long? end = null;
if (!string.IsNullOrEmpty(vals[0]))
{
start = long.Parse(vals[0], UsCulture);
}
if (!string.IsNullOrEmpty(vals[1]))
{
end = long.Parse(vals[1], UsCulture);
}
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
}
}
return _requestedRanges;
}
}
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
{ {
try try
@ -167,59 +169,44 @@ namespace Emby.Server.Implementations.HttpServer
// If the requested range is "0-", we can optimize by just doing a stream copy // If the requested range is "0-", we can optimize by just doing a stream copy
if (RangeEnd >= TotalContentLength - 1) if (RangeEnd >= TotalContentLength - 1)
{ {
await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); await source.CopyToAsync(responseStream, BufferSize, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false); await CopyToInternalAsync(source, responseStream, RangeLength, cancellationToken).ConfigureAwait(false);
} }
} }
} }
finally finally
{ {
if (OnComplete != null) OnComplete?.Invoke();
{
OnComplete();
}
} }
} }
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength) private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
{ {
var array = new byte[BufferSize]; var array = ArrayPool<byte>.Shared.Rent(BufferSize);
int bytesRead; try
while ((bytesRead = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
{ {
if (bytesRead == 0) int bytesRead;
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
{ {
break; var bytesToCopy = Math.Min(bytesRead, copyLength);
}
var bytesToCopy = Math.Min(bytesRead, copyLength); await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false);
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false); copyLength -= bytesToCopy;
copyLength -= bytesToCopy; if (copyLength <= 0)
{
if (copyLength <= 0) break;
{ }
break;
} }
} }
} finally
{
public string ContentType { get; set; } ArrayPool<byte>.Shared.Return(array);
}
public IRequest RequestContext { get; set; }
public object Response { get; set; }
public int Status { get; set; }
public HttpStatusCode StatusCode
{
get => (HttpStatusCode)Status;
set => Status = (int)value;
} }
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
@ -42,11 +41,11 @@ namespace Emby.Server.Implementations.HttpServer
res.Headers.Add(key, value); res.Headers.Add(key, value);
} }
// Try to prevent compatibility view // Try to prevent compatibility view
res.Headers["Access-Control-Allow-Headers"] = ("Accept, Accept-Language, Authorization, Cache-Control, " + res.Headers["Access-Control-Allow-Headers"] = "Accept, Accept-Language, Authorization, Cache-Control, " +
"Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " + "Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " +
"Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " + "Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " +
"Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " + "Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " +
"X-Emby-Authorization"); "X-Emby-Authorization";
if (dto is Exception exception) if (dto is Exception exception)
{ {

View File

@ -2,11 +2,12 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Security.Authentication;
using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.SocketSharp;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
@ -45,11 +46,27 @@ namespace Emby.Server.Implementations.HttpServer.Security
public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
{ {
var req = new WebSocketSharpRequest(request, null, request.Path, _logger); var req = new WebSocketSharpRequest(request, null, request.Path);
var user = ValidateUser(req, authAttributes); var user = ValidateUser(req, authAttributes);
return user; return user;
} }
public AuthorizationInfo Authenticate(HttpRequest request)
{
var auth = _authorizationContext.GetAuthorizationInfo(request);
if (auth?.User == null)
{
return null;
}
if (auth.User.HasPermission(PermissionKind.IsDisabled))
{
throw new SecurityException("User account has been disabled.");
}
return auth;
}
private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
{ {
// This code is executed before the service // This code is executed before the service
@ -90,7 +107,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
!string.IsNullOrEmpty(auth.Client) && !string.IsNullOrEmpty(auth.Client) &&
!string.IsNullOrEmpty(auth.Device)) !string.IsNullOrEmpty(auth.Device))
{ {
_sessionManager.LogSessionActivity(auth.Client, _sessionManager.LogSessionActivity(
auth.Client,
auth.Version, auth.Version,
auth.DeviceId, auth.DeviceId,
auth.Device, auth.Device,
@ -104,21 +122,21 @@ namespace Emby.Server.Implementations.HttpServer.Security
private void ValidateUserAccess( private void ValidateUserAccess(
User user, User user,
IRequest request, IRequest request,
IAuthenticationAttributes authAttribtues, IAuthenticationAttributes authAttributes,
AuthorizationInfo auth) AuthorizationInfo auth)
{ {
if (user.Policy.IsDisabled) if (user.HasPermission(PermissionKind.IsDisabled))
{ {
throw new SecurityException("User account has been disabled."); throw new SecurityException("User account has been disabled.");
} }
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp)) if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !_networkManager.IsInLocalNetwork(request.RemoteIp))
{ {
throw new SecurityException("User account has been disabled."); throw new SecurityException("User account has been disabled.");
} }
if (!user.Policy.IsAdministrator if (!user.HasPermission(PermissionKind.IsAdministrator)
&& !authAttribtues.EscapeParentalControl && !authAttributes.EscapeParentalControl
&& !user.IsParentalScheduleAllowed()) && !user.IsParentalScheduleAllowed())
{ {
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl"); request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
@ -138,6 +156,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{ {
return true; return true;
} }
if (authAttribtues.AllowLocalOnly && request.IsLocal) if (authAttribtues.AllowLocalOnly && request.IsLocal)
{ {
return true; return true;
@ -180,7 +199,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{ {
if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase)) if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
{ {
if (user == null || !user.Policy.IsAdministrator) if (user == null || !user.HasPermission(PermissionKind.IsAdministrator))
{ {
throw new SecurityException("User does not have admin access."); throw new SecurityException("User does not have admin access.");
} }
@ -188,7 +207,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase)) if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
{ {
if (user == null || !user.Policy.EnableContentDeletion) if (user == null || !user.HasPermission(PermissionKind.EnableContentDeletion))
{ {
throw new SecurityException("User does not have delete access."); throw new SecurityException("User does not have delete access.");
} }
@ -196,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
if (roles.Contains("download", StringComparer.OrdinalIgnoreCase)) if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
{ {
if (user == null || !user.Policy.EnableContentDownloading) if (user == null || !user.HasPermission(PermissionKind.EnableContentDownloading))
{ {
throw new SecurityException("User does not have download access."); throw new SecurityException("User does not have download access.");
} }
@ -223,7 +242,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
throw new AuthenticationException("Access token is invalid or expired."); throw new AuthenticationException("Access token is invalid or expired.");
} }
//if (!string.IsNullOrEmpty(info.UserId)) // if (!string.IsNullOrEmpty(info.UserId))
//{ //{
// var user = _userManager.GetUserById(info.UserId); // var user = _userManager.GetUserById(info.UserId);

View File

@ -8,6 +8,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer.Security namespace Emby.Server.Implementations.HttpServer.Security
@ -38,6 +39,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
return GetAuthorization(requestContext); return GetAuthorization(requestContext);
} }
public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext)
{
var auth = GetAuthorizationDictionary(requestContext);
var (authInfo, _) =
GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query);
return authInfo;
}
/// <summary> /// <summary>
/// Gets the authorization. /// Gets the authorization.
/// </summary> /// </summary>
@ -46,7 +55,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
private AuthorizationInfo GetAuthorization(IRequest httpReq) private AuthorizationInfo GetAuthorization(IRequest httpReq)
{ {
var auth = GetAuthorizationDictionary(httpReq); var auth = GetAuthorizationDictionary(httpReq);
var (authInfo, originalAuthInfo) =
GetAuthorizationInfoFromDictionary(auth, httpReq.Headers, httpReq.QueryString);
if (originalAuthInfo != null)
{
httpReq.Items["OriginalAuthenticationInfo"] = originalAuthInfo;
}
httpReq.Items["AuthorizationInfo"] = authInfo;
return authInfo;
}
private (AuthorizationInfo authInfo, AuthenticationInfo originalAuthenticationInfo) GetAuthorizationInfoFromDictionary(
in Dictionary<string, string> auth,
in IHeaderDictionary headers,
in IQueryCollection queryString)
{
string deviceId = null; string deviceId = null;
string device = null; string device = null;
string client = null; string client = null;
@ -64,19 +89,20 @@ namespace Emby.Server.Implementations.HttpServer.Security
if (string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))
{ {
token = httpReq.Headers["X-Emby-Token"]; token = headers["X-Emby-Token"];
} }
if (string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))
{ {
token = httpReq.Headers["X-MediaBrowser-Token"]; token = headers["X-MediaBrowser-Token"];
}
if (string.IsNullOrEmpty(token))
{
token = httpReq.QueryString["api_key"];
} }
var info = new AuthorizationInfo if (string.IsNullOrEmpty(token))
{
token = queryString["api_key"];
}
var authInfo = new AuthorizationInfo
{ {
Client = client, Client = client,
Device = device, Device = device,
@ -85,6 +111,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
Token = token Token = token
}; };
AuthenticationInfo originalAuthenticationInfo = null;
if (!string.IsNullOrWhiteSpace(token)) if (!string.IsNullOrWhiteSpace(token))
{ {
var result = _authRepo.Get(new AuthenticationInfoQuery var result = _authRepo.Get(new AuthenticationInfoQuery
@ -92,81 +119,77 @@ namespace Emby.Server.Implementations.HttpServer.Security
AccessToken = token AccessToken = token
}); });
var tokenInfo = result.Items.Count > 0 ? result.Items[0] : null; originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null;
if (tokenInfo != null) if (originalAuthenticationInfo != null)
{ {
var updateToken = false; var updateToken = false;
// TODO: Remove these checks for IsNullOrWhiteSpace // TODO: Remove these checks for IsNullOrWhiteSpace
if (string.IsNullOrWhiteSpace(info.Client)) if (string.IsNullOrWhiteSpace(authInfo.Client))
{ {
info.Client = tokenInfo.AppName; authInfo.Client = originalAuthenticationInfo.AppName;
} }
if (string.IsNullOrWhiteSpace(info.DeviceId)) if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
{ {
info.DeviceId = tokenInfo.DeviceId; authInfo.DeviceId = originalAuthenticationInfo.DeviceId;
} }
// Temporary. TODO - allow clients to specify that the token has been shared with a casting device // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
var allowTokenInfoUpdate = info.Client == null || info.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1; var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
if (string.IsNullOrWhiteSpace(info.Device)) if (string.IsNullOrWhiteSpace(authInfo.Device))
{ {
info.Device = tokenInfo.DeviceName; authInfo.Device = originalAuthenticationInfo.DeviceName;
} }
else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
{ {
if (allowTokenInfoUpdate) if (allowTokenInfoUpdate)
{ {
updateToken = true; updateToken = true;
tokenInfo.DeviceName = info.Device; originalAuthenticationInfo.DeviceName = authInfo.Device;
} }
} }
if (string.IsNullOrWhiteSpace(info.Version)) if (string.IsNullOrWhiteSpace(authInfo.Version))
{ {
info.Version = tokenInfo.AppVersion; authInfo.Version = originalAuthenticationInfo.AppVersion;
} }
else if (!string.Equals(info.Version, tokenInfo.AppVersion, StringComparison.OrdinalIgnoreCase)) else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
{ {
if (allowTokenInfoUpdate) if (allowTokenInfoUpdate)
{ {
updateToken = true; updateToken = true;
tokenInfo.AppVersion = info.Version; originalAuthenticationInfo.AppVersion = authInfo.Version;
} }
} }
if ((DateTime.UtcNow - tokenInfo.DateLastActivity).TotalMinutes > 3) if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3)
{ {
tokenInfo.DateLastActivity = DateTime.UtcNow; originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow;
updateToken = true; updateToken = true;
} }
if (!tokenInfo.UserId.Equals(Guid.Empty)) if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty))
{ {
info.User = _userManager.GetUserById(tokenInfo.UserId); authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId);
if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase))
{ {
tokenInfo.UserName = info.User.Name; originalAuthenticationInfo.UserName = authInfo.User.Username;
updateToken = true; updateToken = true;
} }
} }
if (updateToken) if (updateToken)
{ {
_authRepo.Update(tokenInfo); _authRepo.Update(originalAuthenticationInfo);
} }
} }
httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo;
} }
httpReq.Items["AuthorizationInfo"] = info; return (authInfo, originalAuthenticationInfo);
return info;
} }
/// <summary> /// <summary>
@ -186,6 +209,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
return GetAuthorization(auth); return GetAuthorization(auth);
} }
/// <summary>
/// Gets the auth.
/// </summary>
/// <param name="httpReq">The HTTP req.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq)
{
var auth = httpReq.Headers["X-Emby-Authorization"];
if (string.IsNullOrEmpty(auth))
{
auth = httpReq.Headers[HeaderNames.Authorization];
}
return GetAuthorization(auth);
}
/// <summary> /// <summary>
/// Gets the authorization. /// Gets the authorization.
/// </summary> /// </summary>

View File

@ -1,7 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using MediaBrowser.Controller.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;

View File

@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary> /// <summary>
/// The logger. /// The logger.
/// </summary> /// </summary>
private readonly ILogger _logger; private readonly ILogger<WebSocketConnection> _logger;
/// <summary> /// <summary>
/// The json serializer options. /// The json serializer options.
@ -78,6 +78,9 @@ namespace Emby.Server.Implementations.HttpServer
/// <value>The last activity date.</value> /// <value>The last activity date.</value>
public DateTime LastActivityDate { get; private set; } public DateTime LastActivityDate { get; private set; }
/// <inheritdoc />
public DateTime LastKeepAliveDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the query string. /// Gets or sets the query string.
/// </summary> /// </summary>
@ -218,7 +221,44 @@ namespace Emby.Server.Implementations.HttpServer
Connection = this Connection = this
}; };
await OnReceive(info).ConfigureAwait(false); if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
{
await SendKeepAliveResponse();
}
else
{
await OnReceive(info).ConfigureAwait(false);
}
}
private Task SendKeepAliveResponse()
{
LastKeepAliveDate = DateTime.UtcNow;
return SendAsync(
new WebSocketMessage<string>
{
MessageId = Guid.NewGuid(),
MessageType = "KeepAlive"
}, CancellationToken.None);
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
_socket.Dispose();
}
} }
} }
} }

View File

@ -11,13 +11,14 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Emby.Server.Implementations.Library;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO namespace Emby.Server.Implementations.IO
{ {
public class LibraryMonitor : ILibraryMonitor public class LibraryMonitor : ILibraryMonitor
{ {
private readonly ILogger _logger; private readonly ILogger<LibraryMonitor> _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _configurationManager; private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
@ -37,38 +38,6 @@ namespace Emby.Server.Implementations.IO
/// </summary> /// </summary>
private readonly ConcurrentDictionary<string, string> _tempIgnoredPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, string> _tempIgnoredPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Any file name ending in any of these will be ignored by the watchers.
/// </summary>
private static readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"small.jpg",
"albumart.jpg",
// WMC temp recording directories that will constantly be written to
"TempRec",
"TempSBE"
};
private static readonly string[] _alwaysIgnoreSubstrings = new string[]
{
// Synology
"eaDir",
"#recycle",
".wd_tv",
".actors"
};
private static readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
// thumbs.db
".db",
// bts sync files
".bts",
".sync"
};
/// <summary> /// <summary>
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
/// </summary> /// </summary>
@ -297,7 +266,6 @@ namespace Emby.Server.Implementations.IO
{ {
DisposeWatcher(newWatcher, false); DisposeWatcher(newWatcher, false);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -395,12 +363,7 @@ namespace Emby.Server.Implementations.IO
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
var filename = Path.GetFileName(path); var monitorPath = !IgnorePatterns.ShouldIgnore(path);
var monitorPath = !string.IsNullOrEmpty(filename) &&
!_alwaysIgnoreFiles.Contains(filename) &&
!_alwaysIgnoreExtensions.Contains(Path.GetExtension(path)) &&
_alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1);
// Ignore certain files // Ignore certain files
var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList();
@ -429,7 +392,6 @@ namespace Emby.Server.Implementations.IO
} }
return false; return false;
})) }))
{ {
monitorPath = false; monitorPath = false;

View File

@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.IO
/// </summary> /// </summary>
public class ManagedFileSystem : IFileSystem public class ManagedFileSystem : IFileSystem
{ {
protected ILogger Logger; protected ILogger<ManagedFileSystem> Logger;
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>(); private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
private readonly string _tempPath; private readonly string _tempPath;
@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.IO
{ {
result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory; result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
//if (!result.IsDirectory) // if (!result.IsDirectory)
//{ //{
// result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; // result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
//} //}
@ -628,6 +628,7 @@ namespace Emby.Server.Implementations.IO
{ {
return false; return false;
} }
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase); return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
}); });
} }
@ -682,6 +683,7 @@ namespace Emby.Server.Implementations.IO
{ {
return false; return false;
} }
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase); return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
}); });
} }

View File

@ -1,3 +1,7 @@
#pragma warning disable CS1591
using System;
namespace Emby.Server.Implementations namespace Emby.Server.Implementations
{ {
public interface IStartupOptions public interface IStartupOptions
@ -33,8 +37,8 @@ namespace Emby.Server.Implementations
string RestartArgs { get; } string RestartArgs { get; }
/// <summary> /// <summary>
/// Gets the value of the --plugin-manifest-url command line option. /// Gets the value of the --published-server-url command line option.
/// </summary> /// </summary>
string PluginManifestUrl { get; } Uri PublishedServerUrl { get; }
} }
} }

View File

@ -0,0 +1,60 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Images
{
/// <summary>
/// Class ArtistImageProvider.
/// </summary>
public class ArtistImageProvider : BaseDynamicImageProvider<MusicArtist>
{
/// <summary>
/// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Get children objects used to create an artist image.
/// </summary>
/// <param name="item">The artist used to create the image.</param>
/// <returns>Any relevant children objects.</returns>
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
return Array.Empty<BaseItem>();
// TODO enable this when BaseDynamicImageProvider objects are configurable
// return _libraryManager.GetItemList(new InternalItemsQuery
// {
// ArtistIds = new[] { item.Id },
// IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
// OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
// Limit = 4,
// Recursive = true,
// ImageTypes = new[] { ImageType.Primary },
// DtoOptions = new DtoOptions(false)
// });
}
}
}

View File

@ -194,7 +194,8 @@ namespace Emby.Server.Implementations.Images
return outputPath; return outputPath;
} }
protected virtual string CreateImage(BaseItem item, protected virtual string CreateImage(
BaseItem item,
IReadOnlyCollection<BaseItem> itemsWithImages, IReadOnlyCollection<BaseItem> itemsWithImages,
string outputPathWithoutExtension, string outputPathWithoutExtension,
ImageType imageType, ImageType imageType,
@ -214,7 +215,12 @@ namespace Emby.Server.Implementations.Images
if (imageType == ImageType.Primary) if (imageType == ImageType.Primary)
{ {
if (item is UserView || item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum) if (item is UserView
|| item is Playlist
|| item is MusicGenre
|| item is Genre
|| item is PhotoAlbum
|| item is MusicArtist)
{ {
return CreateSquareCollage(item, itemsWithImages, outputPath); return CreateSquareCollage(item, itemsWithImages, outputPath);
} }
@ -225,7 +231,7 @@ namespace Emby.Server.Implementations.Images
throw new ArgumentException("Unexpected image type", nameof(imageType)); throw new ArgumentException("Unexpected image type", nameof(imageType));
} }
public bool HasChanged(BaseItem item, IDirectoryService directoryServicee) public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{ {
if (!Supports(item)) if (!Supports(item))
{ {
@ -236,6 +242,7 @@ namespace Emby.Server.Implementations.Images
{ {
return true; return true;
} }
if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb)) if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb))
{ {
return true; return true;

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -11,7 +13,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.UserViews namespace Emby.Server.Implementations.Images
{ {
public class CollectionFolderImageProvider : BaseDynamicImageProvider<CollectionFolder> public class CollectionFolderImageProvider : BaseDynamicImageProvider<CollectionFolder>
{ {
@ -69,7 +71,6 @@ namespace Emby.Server.Implementations.UserViews
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
}, },
IncludeItemTypes = includeItemTypes IncludeItemTypes = includeItemTypes
}); });
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -14,18 +16,16 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.UserViews namespace Emby.Server.Implementations.Images
{ {
public class DynamicImageProvider : BaseDynamicImageProvider<UserView> public class DynamicImageProvider : BaseDynamicImageProvider<UserView>
{ {
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager) public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager)
: base(fileSystem, providerManager, applicationPaths, imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{ {
_userManager = userManager; _userManager = userManager;
_libraryManager = libraryManager;
} }
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
@ -78,7 +78,6 @@ namespace Emby.Server.Implementations.UserViews
} }
return i; return i;
}).GroupBy(x => x.Id) }).GroupBy(x => x.Id)
.Select(x => x.First()); .Select(x => x.First());

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Server.Implementations.Images; using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -11,7 +13,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.UserViews namespace Emby.Server.Implementations.Images
{ {
public abstract class BaseFolderImageProvider<T> : BaseDynamicImageProvider<T> public abstract class BaseFolderImageProvider<T> : BaseDynamicImageProvider<T>
where T : Folder, new() where T : Folder, new()
@ -75,16 +77,12 @@ namespace Emby.Server.Implementations.UserViews
return false; return false;
} }
var folder = item as Folder; if (item is Folder && item.IsTopParent)
if (folder != null)
{ {
if (folder.IsTopParent) return false;
{
return false;
}
} }
return true; return true;
//return item.SourceType == SourceType.Library;
} }
} }

View File

@ -1,6 +1,6 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
@ -9,66 +9,21 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Playlists namespace Emby.Server.Implementations.Images
{ {
public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist> /// <summary>
{ /// Class MusicGenreImageProvider.
public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor) /// </summary>
{
}
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (Playlist)item;
return playlist.GetManageableItems()
.Select(i =>
{
var subItem = i.Item2;
var episode = subItem as Episode;
if (episode != null)
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))
{
return series;
}
}
if (subItem.HasImage(ImageType.Primary))
{
return subItem;
}
var parent = subItem.GetOwner() ?? subItem.GetParent();
if (parent != null && parent.HasImage(ImageType.Primary))
{
if (parent is MusicAlbum)
{
return parent;
}
}
return null;
})
.Where(i => i != null)
.GroupBy(x => x.Id)
.Select(x => x.First())
.ToList();
}
}
public class MusicGenreImageProvider : BaseDynamicImageProvider<MusicGenre> public class MusicGenreImageProvider : BaseDynamicImageProvider<MusicGenre>
{ {
/// <summary>
/// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
@ -76,6 +31,11 @@ namespace Emby.Server.Implementations.Playlists
_libraryManager = libraryManager; _libraryManager = libraryManager;
} }
/// <summary>
/// Get children objects used to create an music genre image.
/// </summary>
/// <param name="item">The music genre used to create the image.</param>
/// <returns>Any relevant children objects.</returns>
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{ {
return _libraryManager.GetItemList(new InternalItemsQuery return _libraryManager.GetItemList(new InternalItemsQuery
@ -91,8 +51,14 @@ namespace Emby.Server.Implementations.Playlists
} }
} }
/// <summary>
/// Class GenreImageProvider.
/// </summary>
public class GenreImageProvider : BaseDynamicImageProvider<Genre> public class GenreImageProvider : BaseDynamicImageProvider<Genre>
{ {
/// <summary>
/// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
@ -100,6 +66,11 @@ namespace Emby.Server.Implementations.Playlists
_libraryManager = libraryManager; _libraryManager = libraryManager;
} }
/// <summary>
/// Get children objects used to create an genre image.
/// </summary>
/// <param name="item">The genre used to create the image.</param>
/// <returns>Any relevant children objects.</returns>
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{ {
return _libraryManager.GetItemList(new InternalItemsQuery return _libraryManager.GetItemList(new InternalItemsQuery

View File

@ -0,0 +1,66 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Images
{
public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist>
{
public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
}
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (Playlist)item;
return playlist.GetManageableItems()
.Select(i =>
{
var subItem = i.Item2;
var episode = subItem as Episode;
if (episode != null)
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))
{
return series;
}
}
if (subItem.HasImage(ImageType.Primary))
{
return subItem;
}
var parent = subItem.GetOwner() ?? subItem.GetParent();
if (parent != null && parent.HasImage(ImageType.Primary))
{
if (parent is MusicAlbum)
{
return parent;
}
}
return null;
})
.Where(i => i != null)
.GroupBy(x => x.Id)
.Select(x => x.First())
.ToList();
}
}
}

View File

@ -1,7 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using MediaBrowser.Controller;
using System.Text.RegularExpressions;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
@ -10,73 +9,48 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
{ {
/// <summary> /// <summary>
/// Provides the core resolver ignore rules /// Provides the core resolver ignore rules.
/// </summary> /// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IServerApplicationPaths _serverApplicationPaths;
/// <summary>
/// Any folder named in this list will be ignored
/// </summary>
private static readonly string[] _ignoreFolders =
{
"metadata",
"ps3_update",
"ps3_vprm",
"extrafanart",
"extrathumbs",
".actors",
".wd_tv",
// Synology
"@eaDir",
"eaDir",
"#recycle",
// Qnap
"@Recycle",
".@__thumb",
"$RECYCLE.BIN",
"System Volume Information",
".grab",
};
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class. /// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
/// </summary> /// </summary>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
public CoreResolutionIgnoreRule(ILibraryManager libraryManager) /// <param name="serverApplicationPaths">The server application paths.</param>
public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_serverApplicationPaths = serverApplicationPaths;
} }
/// <inheritdoc /> /// <inheritdoc />
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent) public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
{ {
// Don't ignore application folders
if (fileInfo.FullName.Contains(_serverApplicationPaths.RootFolderPath, StringComparison.InvariantCulture))
{
return false;
}
// Don't ignore top level folders // Don't ignore top level folders
if (fileInfo.IsDirectory && parent is AggregateFolder) if (fileInfo.IsDirectory && parent is AggregateFolder)
{ {
return false; return false;
} }
var filename = fileInfo.Name; if (IgnorePatterns.ShouldIgnore(fileInfo.FullName))
// Ignore hidden files on UNIX
if (Environment.OSVersion.Platform != PlatformID.Win32NT
&& filename[0] == '.')
{ {
return true; return true;
} }
var filename = fileInfo.Name;
if (fileInfo.IsDirectory) if (fileInfo.IsDirectory)
{ {
// Ignore any folders in our list
if (_ignoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
{
return true;
}
if (parent != null) if (parent != null)
{ {
// Ignore trailer folders but allow it at the collection level // Ignore trailer folders but allow it at the collection level
@ -109,11 +83,6 @@ namespace Emby.Server.Implementations.Library
return true; return true;
} }
} }
// Ignore samples
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
return m.Success;
} }
return false; return false;

View File

@ -12,11 +12,13 @@ namespace Emby.Server.Implementations.Library
public class ExclusiveLiveStream : ILiveStream public class ExclusiveLiveStream : ILiveStream
{ {
public int ConsumerCount { get; set; } public int ConsumerCount { get; set; }
public string OriginalStreamId { get; set; } public string OriginalStreamId { get; set; }
public string TunerHostId => null; public string TunerHostId => null;
public bool EnableStreamSharing { get; set; } public bool EnableStreamSharing { get; set; }
public MediaSourceInfo MediaSource { get; set; } public MediaSourceInfo MediaSource { get; set; }
public string UniqueId { get; private set; } public string UniqueId { get; private set; }

View File

@ -0,0 +1,74 @@
using System.Linq;
using DotNet.Globbing;
namespace Emby.Server.Implementations.Library
{
/// <summary>
/// Glob patterns for files to ignore.
/// </summary>
public static class IgnorePatterns
{
/// <summary>
/// Files matching these glob patterns will be ignored.
/// </summary>
public static readonly string[] Patterns = new string[]
{
"**/small.jpg",
"**/albumart.jpg",
"**/*sample*",
// Directories
"**/metadata/**",
"**/ps3_update/**",
"**/ps3_vprm/**",
"**/extrafanart/**",
"**/extrathumbs/**",
"**/.actors/**",
"**/.wd_tv/**",
"**/lost+found/**",
// WMC temp recording directories that will constantly be written to
"**/TempRec/**",
"**/TempSBE/**",
// Synology
"**/eaDir/**",
"**/@eaDir/**",
"**/#recycle/**",
// Qnap
"**/@Recycle/**",
"**/.@__thumb/**",
"**/$RECYCLE.BIN/**",
"**/System Volume Information/**",
"**/.grab/**",
// Unix hidden files and directories
"**/.*/**",
// thumbs.db
"**/thumbs.db",
// bts sync files
"**/*.bts",
"**/*.sync",
};
private static readonly GlobOptions _globOptions = new GlobOptions
{
Evaluation = {
CaseInsensitive = true
}
};
private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
/// <summary>
/// Returns true if the supplied path should be ignored.
/// </summary>
public static bool ShouldIgnore(string path)
{
return _globs.Any(g => g.IsMatch(path));
}
}
}

View File

@ -17,14 +17,16 @@ using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Validators; using Emby.Server.Implementations.Library.Validators;
using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.ScheduledTasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
@ -35,6 +37,7 @@ using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -44,17 +47,20 @@ using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Providers.MediaInfo; using MediaBrowser.Providers.MediaInfo;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
using SortOrder = MediaBrowser.Model.Entities.SortOrder; using SortOrder = MediaBrowser.Model.Entities.SortOrder;
using VideoResolver = Emby.Naming.Video.VideoResolver; using VideoResolver = Emby.Naming.Video.VideoResolver;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
{ {
/// <summary> /// <summary>
/// Class LibraryManager /// Class LibraryManager.
/// </summary> /// </summary>
public class LibraryManager : ILibraryManager public class LibraryManager : ILibraryManager
{ {
private readonly ILogger _logger; private readonly ILogger<LibraryManager> _logger;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataRepository; private readonly IUserDataManager _userDataRepository;
@ -67,6 +73,7 @@ namespace Emby.Server.Implementations.Library
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IItemRepository _itemRepository; private readonly IItemRepository _itemRepository;
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache; private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
private readonly IImageProcessor _imageProcessor;
private NamingOptions _namingOptions; private NamingOptions _namingOptions;
private string[] _videoFileExtensions; private string[] _videoFileExtensions;
@ -90,13 +97,13 @@ namespace Emby.Server.Implementations.Library
private IIntroProvider[] IntroProviders { get; set; } private IIntroProvider[] IntroProviders { get; set; }
/// <summary> /// <summary>
/// Gets or sets the list of entity resolution ignore rules /// Gets or sets the list of entity resolution ignore rules.
/// </summary> /// </summary>
/// <value>The entity resolution ignore rules.</value> /// <value>The entity resolution ignore rules.</value>
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
/// <summary> /// <summary>
/// Gets or sets the list of currently registered entity resolvers /// Gets or sets the list of currently registered entity resolvers.
/// </summary> /// </summary>
/// <value>The entity resolvers enumerable.</value> /// <value>The entity resolvers enumerable.</value>
private IItemResolver[] EntityResolvers { get; set; } private IItemResolver[] EntityResolvers { get; set; }
@ -129,12 +136,19 @@ namespace Emby.Server.Implementations.Library
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LibraryManager" /> class. /// Initializes a new instance of the <see cref="LibraryManager" /> class.
/// </summary> /// </summary>
/// <param name="appHost">The application host</param> /// <param name="appHost">The application host.</param>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="taskManager">The task manager.</param> /// <param name="taskManager">The task manager.</param>
/// <param name="userManager">The user manager.</param> /// <param name="userManager">The user manager.</param>
/// <param name="configurationManager">The configuration manager.</param> /// <param name="configurationManager">The configuration manager.</param>
/// <param name="userDataRepository">The user data repository.</param> /// <param name="userDataRepository">The user data repository.</param>
/// <param name="libraryMonitorFactory">The library monitor.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="providerManagerFactory">The provider manager.</param>
/// <param name="userviewManagerFactory">The userview manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
/// <param name="itemRepository">The item repository.</param>
/// <param name="imageProcessor">The image processor.</param>
public LibraryManager( public LibraryManager(
IServerApplicationHost appHost, IServerApplicationHost appHost,
ILogger<LibraryManager> logger, ILogger<LibraryManager> logger,
@ -147,7 +161,8 @@ namespace Emby.Server.Implementations.Library
Lazy<IProviderManager> providerManagerFactory, Lazy<IProviderManager> providerManagerFactory,
Lazy<IUserViewManager> userviewManagerFactory, Lazy<IUserViewManager> userviewManagerFactory,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IItemRepository itemRepository) IItemRepository itemRepository,
IImageProcessor imageProcessor)
{ {
_appHost = appHost; _appHost = appHost;
_logger = logger; _logger = logger;
@ -161,6 +176,7 @@ namespace Emby.Server.Implementations.Library
_userviewManagerFactory = userviewManagerFactory; _userviewManagerFactory = userviewManagerFactory;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_itemRepository = itemRepository; _itemRepository = itemRepository;
_imageProcessor = imageProcessor;
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>(); _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
@ -193,12 +209,12 @@ namespace Emby.Server.Implementations.Library
} }
/// <summary> /// <summary>
/// The _root folder /// The _root folder.
/// </summary> /// </summary>
private volatile AggregateFolder _rootFolder; private volatile AggregateFolder _rootFolder;
/// <summary> /// <summary>
/// The _root folder sync lock /// The _root folder sync lock.
/// </summary> /// </summary>
private readonly object _rootFolderSyncLock = new object(); private readonly object _rootFolderSyncLock = new object();
@ -610,7 +626,7 @@ namespace Emby.Server.Implementations.Library
} }
/// <summary> /// <summary>
/// Determines whether a path should be ignored based on its contents - called after the contents have been read /// Determines whether a path should be ignored based on its contents - called after the contents have been read.
/// </summary> /// </summary>
/// <param name="args">The args.</param> /// <param name="args">The args.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
@ -695,7 +711,9 @@ namespace Emby.Server.Implementations.Library
Directory.CreateDirectory(rootFolderPath); Directory.CreateDirectory(rootFolderPath);
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))).DeepCopy<Folder, AggregateFolder>(); var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)))
.DeepCopy<Folder, AggregateFolder>();
// In case program data folder was moved // In case program data folder was moved
if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal)) if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal))
@ -890,7 +908,7 @@ namespace Emby.Server.Implementations.Library
} }
/// <summary> /// <summary>
/// Gets a Genre /// Gets a Genre.
/// </summary> /// </summary>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns>Task{Genre}.</returns> /// <returns>Task{Genre}.</returns>
@ -971,7 +989,7 @@ namespace Emby.Server.Implementations.Library
} }
/// <summary> /// <summary>
/// Reloads the root media folder /// Reloads the root media folder.
/// </summary> /// </summary>
/// <param name="progress">The progress.</param> /// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
@ -1524,7 +1542,8 @@ namespace Emby.Server.Implementations.Library
} }
// Handle grouping // Handle grouping
if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0) if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType)
&& user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
{ {
return GetUserRootFolder() return GetUserRootFolder()
.GetChildren(user, true) .GetChildren(user, true)
@ -1773,7 +1792,7 @@ namespace Emby.Server.Implementations.Library
/// Creates the items. /// Creates the items.
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
/// <param name="parent">The parent item</param> /// <param name="parent">The parent item.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken) public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
{ {
@ -1815,10 +1834,100 @@ namespace Emby.Server.Implementations.Library
} }
} }
public void UpdateImages(BaseItem item) private bool ImageNeedsRefresh(ItemImageInfo image)
{ {
_itemRepository.SaveImages(item); if (image.Path != null && image.IsLocalFile)
{
if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash))
{
return true;
}
try
{
return _fileSystem.GetLastWriteTimeUtc(image.Path) != image.DateModified;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot get file info for {0}", image.Path);
return false;
}
}
return image.Path != null && !image.IsLocalFile;
}
public void UpdateImages(BaseItem item, bool forceUpdate = false)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
if (outdated.Length == 0)
{
RegisterItem(item);
return;
}
foreach (var img in outdated)
{
var image = img;
if (!img.IsLocalFile)
{
try
{
var index = item.GetImageIndex(img);
image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult();
}
catch (ArgumentException)
{
_logger.LogWarning("Cannot get image index for {0}", img.Path);
continue;
}
catch (InvalidOperationException)
{
_logger.LogWarning("Cannot fetch image from {0}", img.Path);
continue;
}
}
try
{
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
image.Width = size.Width;
image.Height = size.Height;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannnot get image dimensions for {0}", image.Path);
image.Width = 0;
image.Height = 0;
continue;
}
try
{
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path);
image.BlurHash = string.Empty;
}
try
{
image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot update DateModified for {0}", image.Path);
}
}
_itemRepository.SaveImages(item);
RegisterItem(item); RegisterItem(item);
} }
@ -1839,7 +1948,7 @@ namespace Emby.Server.Implementations.Library
item.DateLastSaved = DateTime.UtcNow; item.DateLastSaved = DateTime.UtcNow;
RegisterItem(item); UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
} }
_itemRepository.SaveItems(itemsList, cancellationToken); _itemRepository.SaveItems(itemsList, cancellationToken);
@ -2495,7 +2604,7 @@ namespace Emby.Server.Implementations.Library
Anime series don't generally have a season in their file name, however, Anime series don't generally have a season in their file name, however,
tvdb needs a season to correctly get the metadata. tvdb needs a season to correctly get the metadata.
Hence, a null season needs to be filled with something. */ Hence, a null season needs to be filled with something. */
//FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified // FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
episode.ParentIndexNumber = 1; episode.ParentIndexNumber = 1;
} }
@ -2684,10 +2793,12 @@ namespace Emby.Server.Implementations.Library
{ {
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
if (string.IsNullOrWhiteSpace(from)) if (string.IsNullOrWhiteSpace(from))
{ {
throw new ArgumentNullException(nameof(from)); throw new ArgumentNullException(nameof(from));
} }
if (string.IsNullOrWhiteSpace(to)) if (string.IsNullOrWhiteSpace(to))
{ {
throw new ArgumentNullException(nameof(to)); throw new ArgumentNullException(nameof(to));
@ -2761,7 +2872,6 @@ namespace Emby.Server.Implementations.Library
_logger.LogError(ex, "Error getting person"); _logger.LogError(ex, "Error getting person");
return null; return null;
} }
}).Where(i => i != null).ToList(); }).Where(i => i != null).ToList();
} }
@ -2796,7 +2906,8 @@ namespace Emby.Server.Implementations.Library
} }
catch (HttpException ex) catch (HttpException ex)
{ {
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) if (ex.StatusCode.HasValue
&& (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
{ {
continue; continue;
} }
@ -2891,7 +3002,7 @@ namespace Emby.Server.Implementations.Library
private static bool ValidateNetworkPath(string path) private static bool ValidateNetworkPath(string path)
{ {
//if (Environment.OSVersion.Platform == PlatformID.Win32NT) // if (Environment.OSVersion.Platform == PlatformID.Win32NT)
//{ //{
// // We can't validate protocol-based paths, so just allow them // // We can't validate protocol-based paths, so just allow them
// if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1) // if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)

View File

@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library
{ {
mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath); mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath);
//_logger.LogDebug("Found cached media info"); // _logger.LogDebug("Found cached media info");
} }
catch catch
{ {
@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.Library
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
_json.SerializeToFile(mediaInfo, cacheFilePath); _json.SerializeToFile(mediaInfo, cacheFilePath);
//_logger.LogDebug("Saved media info to {0}", cacheFilePath); // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
} }
} }
@ -148,17 +148,14 @@ namespace Emby.Server.Implementations.Library
{ {
videoStream.BitRate = 30000000; videoStream.BitRate = 30000000;
} }
else if (width >= 1900) else if (width >= 1900)
{ {
videoStream.BitRate = 20000000; videoStream.BitRate = 20000000;
} }
else if (width >= 1200) else if (width >= 1200)
{ {
videoStream.BitRate = 8000000; videoStream.BitRate = 8000000;
} }
else if (width >= 700) else if (width >= 700)
{ {
videoStream.BitRate = 2000000; videoStream.BitRate = 2000000;

View File

@ -7,6 +7,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -14,7 +16,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -33,7 +34,7 @@ namespace Emby.Server.Implementations.Library
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger _logger; private readonly ILogger<MediaSourceManager> _logger;
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ILocalizationManager _localizationManager; private readonly ILocalizationManager _localizationManager;
@ -190,10 +191,7 @@ namespace Emby.Server.Implementations.Library
{ {
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{ {
if (!user.Policy.EnableAudioPlaybackTranscoding) source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
{
source.SupportsTranscoding = false;
}
} }
} }
} }
@ -207,22 +205,27 @@ namespace Emby.Server.Implementations.Library
{ {
return MediaProtocol.Rtsp; return MediaProtocol.Rtsp;
} }
if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase)) if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
{ {
return MediaProtocol.Rtmp; return MediaProtocol.Rtmp;
} }
if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase)) if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
{ {
return MediaProtocol.Http; return MediaProtocol.Http;
} }
if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase)) if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
{ {
return MediaProtocol.Rtp; return MediaProtocol.Rtp;
} }
if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase)) if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
{ {
return MediaProtocol.Ftp; return MediaProtocol.Ftp;
} }
if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase)) if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
{ {
return MediaProtocol.Udp; return MediaProtocol.Udp;
@ -352,7 +355,9 @@ namespace Emby.Server.Implementations.Library
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{ {
if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) if (userData.SubtitleStreamIndex.HasValue
&& user.RememberSubtitleSelections
&& user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
{ {
var index = userData.SubtitleStreamIndex.Value; var index = userData.SubtitleStreamIndex.Value;
// Make sure the saved index is still valid // Make sure the saved index is still valid
@ -363,26 +368,27 @@ namespace Emby.Server.Implementations.Library
} }
} }
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
? Array.Empty<string>() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference); var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
var defaultAudioIndex = source.DefaultAudioStreamIndex; var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null var audioLangage = defaultAudioIndex == null
? null ? null
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams, source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(
source.MediaStreams,
preferredSubs, preferredSubs,
user.Configuration.SubtitleMode, user.SubtitleMode,
audioLangage); audioLangage);
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage);
user.Configuration.SubtitleMode, audioLangage);
} }
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{ {
if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection) if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection)
{ {
var index = userData.AudioStreamIndex.Value; var index = userData.AudioStreamIndex.Value;
// Make sure the saved index is still valid // Make sure the saved index is still valid
@ -393,11 +399,11 @@ namespace Emby.Server.Implementations.Library
} }
} }
var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference)
? Array.Empty<string>() ? Array.Empty<string>()
: NormalizeLanguage(user.Configuration.AudioLanguagePreference); : NormalizeLanguage(user.AudioLanguagePreference);
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack); source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
} }
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user)
@ -435,7 +441,6 @@ namespace Emby.Server.Implementations.Library
} }
return 1; return 1;
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
.ThenByDescending(i => .ThenByDescending(i =>
{ {
@ -521,11 +526,7 @@ namespace Emby.Server.Implementations.Library
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
} }
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);
{
MediaSource = clone
}, liveStream as IDirectStreamProvider);
} }
private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio) private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio)
@ -538,7 +539,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.RunTimeTicks = null; mediaSource.RunTimeTicks = null;
} }
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio); var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
if (audioStream == null || audioStream.Index == -1) if (audioStream == null || audioStream.Index == -1)
{ {
@ -549,7 +550,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.DefaultAudioStreamIndex = audioStream.Index; mediaSource.DefaultAudioStreamIndex = audioStream.Index;
} }
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video); var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
if (videoStream != null) if (videoStream != null)
{ {
if (!videoStream.BitRate.HasValue) if (!videoStream.BitRate.HasValue)
@ -560,17 +561,14 @@ namespace Emby.Server.Implementations.Library
{ {
videoStream.BitRate = 30000000; videoStream.BitRate = 30000000;
} }
else if (width >= 1900) else if (width >= 1900)
{ {
videoStream.BitRate = 20000000; videoStream.BitRate = 20000000;
} }
else if (width >= 1200) else if (width >= 1200)
{ {
videoStream.BitRate = 8000000; videoStream.BitRate = 8000000;
} }
else if (width >= 700) else if (width >= 700)
{ {
videoStream.BitRate = 2000000; videoStream.BitRate = 2000000;
@ -626,7 +624,6 @@ namespace Emby.Server.Implementations.Library
MediaSource = mediaSource, MediaSource = mediaSource,
ExtractChapters = false, ExtractChapters = false,
MediaType = DlnaProfileType.Video MediaType = DlnaProfileType.Video
}, cancellationToken).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false);
mediaSource.MediaStreams = info.MediaStreams; mediaSource.MediaStreams = info.MediaStreams;
@ -652,7 +649,7 @@ namespace Emby.Server.Implementations.Library
{ {
mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath); mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath);
//_logger.LogDebug("Found cached media info"); // _logger.LogDebug("Found cached media info");
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -674,20 +671,21 @@ namespace Emby.Server.Implementations.Library
mediaSource.AnalyzeDurationMs = 3000; mediaSource.AnalyzeDurationMs = 3000;
} }
mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest mediaInfo = await _mediaEncoder.GetMediaInfo(
new MediaInfoRequest
{ {
MediaSource = mediaSource, MediaSource = mediaSource,
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
ExtractChapters = false ExtractChapters = false
},
}, cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
if (cacheFilePath != null) if (cacheFilePath != null)
{ {
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
_jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath); _jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
//_logger.LogDebug("Saved media info to {0}", cacheFilePath); // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
} }
} }
@ -753,17 +751,14 @@ namespace Emby.Server.Implementations.Library
{ {
videoStream.BitRate = 30000000; videoStream.BitRate = 30000000;
} }
else if (width >= 1900) else if (width >= 1900)
{ {
videoStream.BitRate = 20000000; videoStream.BitRate = 20000000;
} }
else if (width >= 1200) else if (width >= 1200)
{ {
videoStream.BitRate = 8000000; videoStream.BitRate = 8000000;
} }
else if (width >= 700) else if (width >= 700)
{ {
videoStream.BitRate = 2000000; videoStream.BitRate = 2000000;

Some files were not shown because too many files have changed in this diff Show More