Merge branch 'master' into SSDP

This commit is contained in:
Cody Robibero 2020-06-20 15:33:13 -06:00 committed by GitHub
commit 43221fc26b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1243 changed files with 27665 additions and 13122 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

@ -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,4 @@ jobs:
Common: Common:
NugetPackageName: Jellyfin.Common NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll AssemblyFileName: MediaBrowser.Common.dll
LinuxImage: "ubuntu-latest" LinuxImage: 'ubuntu-latest'

View File

@ -1,59 +0,0 @@
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
cd deployment/fedora-package-x64; \
SOURCE_DIR=../.. \
WORKDIR="$${PWD}"; \
package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
pkg_src_dir="$${WORKDIR}/pkg-src"; \
GNU_TAR=1; \
tar \
--transform "s,^\.,jellyfin-$(VERSION)," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./ || GNU_TAR=0; \
if [ $${GNU_TAR} -eq 0 ]; then \
package_temporary_dir="$$(mktemp -d)"; \
mkdir -p "$${package_temporary_dir}/jellyfin"; \
tar \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./; \
mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}/jellyfin-$(VERSION); \
rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
rm -rf $${package_temporary_dir}; \
fi; \
rpmbuild -bs pkg-src/jellyfin.spec \
--define "_sourcedir $$PWD/pkg-src/" \
--define "_srcrpmdir $(outdir)"

1
.copr/Makefile Symbolic link
View File

@ -0,0 +1 @@
../fedora/Makefile

View File

@ -13,7 +13,7 @@ charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
end_of_line = lf end_of_line = lf
max_line_length = null max_line_length = off
# YAML indentation # YAML indentation
[*.{yml,yaml}] [*.{yml,yaml}]
@ -22,6 +22,7 @@ indent_size = 2
# XML indentation # XML indentation
[*.{csproj,xml}] [*.{csproj,xml}]
indent_size = 2 indent_size = 2
############################### ###############################
# .NET Coding Conventions # # .NET Coding Conventions #
############################### ###############################
@ -51,11 +52,12 @@ dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent
############################### ###############################
# Naming Conventions # # Naming Conventions #
############################### ###############################
@ -67,7 +69,7 @@ dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
@ -159,6 +161,7 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion
############################### ###############################
# C# Formatting Rules # # C# Formatting Rules #
############################### ###############################
@ -189,9 +192,3 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences # Wrapping preferences
csharp_preserve_single_line_statements = true csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true csharp_preserve_single_line_blocks = true
###############################
# VB Coding Conventions #
###############################
[*.vb]
# Modifier preferences
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion

3
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,3 @@
# Joshua must review all changes to deployment and build.sh
deployment/* @joshuaboniface
build.sh @joshuaboniface

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

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

19
.gitignore vendored
View File

@ -245,14 +245,14 @@ pip-log.txt
######################### #########################
# Artifacts for debian-x64 # Artifacts for debian-x64
deployment/debian-package-x64/pkg-src/.debhelper/ debian/.debhelper/
deployment/debian-package-x64/pkg-src/*.debhelper debian/*.debhelper
deployment/debian-package-x64/pkg-src/debhelper-build-stamp debian/debhelper-build-stamp
deployment/debian-package-x64/pkg-src/files debian/files
deployment/debian-package-x64/pkg-src/jellyfin.substvars debian/jellyfin.substvars
deployment/debian-package-x64/pkg-src/jellyfin/ debian/jellyfin/
# Don't ignore the debian/bin folder # Don't ignore the debian/bin folder
!deployment/debian-package-x64/pkg-src/bin/ !debian/bin/
deployment/**/dist/ deployment/**/dist/
deployment/**/pkg-dist/ deployment/**/pkg-dist/
@ -272,3 +272,8 @@ dist
# BenchmarkDotNet artifacts # BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts BenchmarkDotNet.Artifacts
# Ignore web artifacts from native builds
web/
web-src.*
MediaBrowser.WebDashboard/jellyfin-web/

10
.vscode/tasks.json vendored
View File

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

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

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;

View File

@ -13,6 +13,7 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.IO; using System.IO;
namespace DvdLib.Ifo namespace DvdLib.Ifo
@ -5,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

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.IO; using System.IO;
namespace DvdLib.Ifo namespace DvdLib.Ifo

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.IO; using System.IO;
namespace DvdLib.Ifo namespace DvdLib.Ifo

View File

@ -1,9 +1,13 @@
#pragma warning disable CS1591
namespace DvdLib.Ifo 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

@ -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;
@ -115,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));
@ -145,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

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
namespace DvdLib.Ifo namespace DvdLib.Ifo
@ -13,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

@ -1,10 +1,12 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
namespace DvdLib.Ifo namespace DvdLib.Ifo
{ {
public class Program public class Program
{ {
public readonly List<Cell> Cells; public IReadOnlyList<Cell> Cells { get; }
public Program(List<Cell> cells) public Program(List<Cell> cells)
{ {

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -20,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
@ -31,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;
@ -69,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

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -6,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;
@ -15,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;
@ -53,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,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
namespace DvdLib.Ifo namespace DvdLib.Ifo

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,61 +427,102 @@ namespace Emby.Dlna.Didl
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
case StubType.Series: return _localization.GetLocalizedString("Shows"); case StubType.Series: return _localization.GetLocalizedString("Shows");
default: break;
} }
} }
if (item is Episode episode && context is Season season) return item is Episode episode
? GetEpisodeDisplayName(episode, context)
: item.Name;
}
/// <summary>
/// Gets episode display name appropriate for the given context.
/// </summary>
/// <remarks>
/// If context is a season, this will return a string containing just episode number and name.
/// Otherwise the result will include series nams and season number.
/// </remarks>
/// <param name="episode">The episode.</param>
/// <param name="context">Current context.</param>
/// <returns>Formatted name of the episode.</returns>
private string GetEpisodeDisplayName(Episode episode, BaseItem context)
{
string[] components;
if (context is Season season)
{ {
// This is a special embedded within a season // This is a special embedded within a season
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0 if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0) && season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{ {
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ValueSpecialEpisodeName"), _localization.GetLocalizedString("ValueSpecialEpisodeName"),
item.Name); episode.Name);
} }
if (item.IndexNumber.HasValue) // inside a season use simple format (ex. '12 - Episode Name')
var epNumberName = GetEpisodeIndexFullName(episode);
components = new[] { epNumberName, episode.Name };
}
else
{ {
var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); // outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
var epNumberName = GetEpisodeNumberDisplayName(episode);
components = new[] { episode.SeriesName, epNumberName, episode.Name };
}
return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
}
/// <summary>
/// Gets complete episode number.
/// </summary>
/// <param name="episode">The episode.</param>
/// <returns>For single episodes returns just the number. For double episodes - current and ending numbers.</returns>
private string GetEpisodeIndexFullName(Episode episode)
{
var name = string.Empty;
if (episode.IndexNumber.HasValue)
{
name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
if (episode.IndexNumberEnd.HasValue) if (episode.IndexNumberEnd.HasValue)
{ {
number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture); name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
}
} }
return number + " - " + item.Name;
}
}
else if (item is Episode ep)
{
var parent = ep.GetParent();
var name = parent.Name + " - ";
if (ep.ParentIndexNumber.HasValue)
{
name += "S" + ep.ParentIndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
}
else if (!item.IndexNumber.HasValue)
{
return name + " - " + item.Name;
}
name += "E" + ep.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
if (ep.IndexNumberEnd.HasValue)
{
name += "-" + ep.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
}
name += " - " + item.Name;
return name; return name;
} }
return item.Name; /// <summary>
/// Gets episode number formatted as 'S##E##'.
/// </summary>
/// <param name="episode">The episode.</param>
/// <returns>Formatted episode number.</returns>
private string GetEpisodeNumberDisplayName(Episode episode)
{
var name = string.Empty;
var seasonNumber = episode.Season?.IndexNumber;
if (seasonNumber.HasValue)
{
name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
} }
var indexName = GetEpisodeIndexFullName(episode);
if (!string.IsNullOrWhiteSpace(indexName))
{
name += "E" + indexName;
}
return name;
}
private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null) private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{ {
writer.WriteStartElement(string.Empty, "res", NS_DIDL); writer.WriteStartElement(string.Empty, "res", NS_DIDL);
@ -628,7 +675,7 @@ namespace Emby.Dlna.Didl
return; return;
} }
MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null; XmlAttribute secAttribute = null;
foreach (var attribute in _profile.XmlRootAttributes) foreach (var attribute in _profile.XmlRootAttributes)
{ {
if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase)) if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
@ -658,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);
} }
@ -703,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))
// { // {
@ -718,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);
@ -953,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(
@ -1006,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)
@ -1089,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,56 +140,74 @@ 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,7 +31,8 @@ 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; subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
int timeoutSeconds = subscription.TimeoutSeconds; int timeoutSeconds = subscription.TimeoutSeconds;
subscription.SubscriptionTime = DateTime.UtcNow; subscription.SubscriptionTime = DateTime.UtcNow;
@ -45,6 +46,13 @@ namespace Emby.Dlna.Eventing
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds); 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)
{ {
var timeout = ParseTimeout(requestedTimeoutString) ?? 300; var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
@ -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,7 +33,7 @@ namespace Emby.Dlna.Main
public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
{ {
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILogger _logger; private readonly ILogger<DlnaEntryPoint> _logger;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
@ -97,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,

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();
}
} }
private async void RefreshVolume(CancellationToken cancellationToken) return Task.CompletedTask;
}
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;
@ -908,7 +926,8 @@ namespace Emby.Dlna.PlayTo
return 0; return 0;
} }
public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) /// <inheritdoc />
public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
{ {
if (_disposed) if (_disposed)
{ {
@ -924,10 +943,12 @@ namespace Emby.Dlna.PlayTo
{ {
return SendPlayCommand(data as PlayRequest, cancellationToken); return SendPlayCommand(data as PlayRequest, cancellationToken);
} }
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
{ {
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
} }
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
{ {
return SendGeneralCommand(data as GeneralCommand, cancellationToken); return SendGeneralCommand(data as GeneralCommand, cancellationToken);

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

@ -12,7 +12,7 @@ namespace Emby.Dlna.Profiles
{ {
Name = "Generic Device"; Name = "Generic Device";
ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*"; ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";
Manufacturer = "Jellyfin"; Manufacturer = "Jellyfin";
ModelDescription = "UPnP/AV 1.0 Compliant Media Server"; ModelDescription = "UPnP/AV 1.0 Compliant Media Server";

View File

@ -21,7 +21,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -26,7 +26,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>10</TimelineOffsetSeconds> <TimelineOffsetSeconds>10</TimelineOffsetSeconds>
<RequiresPlainVideoItems>true</RequiresPlainVideoItems> <RequiresPlainVideoItems>true</RequiresPlainVideoItems>
<RequiresPlainFolders>true</RequiresPlainFolders> <RequiresPlainFolders>true</RequiresPlainFolders>

View File

@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>10</TimelineOffsetSeconds> <TimelineOffsetSeconds>10</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -25,7 +25,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -28,7 +28,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>10</TimelineOffsetSeconds> <TimelineOffsetSeconds>10</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -21,7 +21,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>true</RequiresPlainVideoItems> <RequiresPlainVideoItems>true</RequiresPlainVideoItems>
<RequiresPlainFolders>true</RequiresPlainFolders> <RequiresPlainFolders>true</RequiresPlainFolders>

View File

@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags> <SonyAggregationFlags>10</SonyAggregationFlags>
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags> <SonyAggregationFlags>10</SonyAggregationFlags>
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags> <SonyAggregationFlags>10</SonyAggregationFlags>
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags> <SonyAggregationFlags>10</SonyAggregationFlags>
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags> <SonyAggregationFlags>10</SonyAggregationFlags>
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags> <SonyAggregationFlags>10</SonyAggregationFlags>
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -28,7 +28,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>5</TimelineOffsetSeconds> <TimelineOffsetSeconds>5</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -28,7 +28,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>40</TimelineOffsetSeconds> <TimelineOffsetSeconds>40</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

View File

@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate> <MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate> <MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" /> <MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo> <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds> <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems> <RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders> <RequiresPlainFolders>false</RequiresPlainFolders>

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

@ -1,9 +1,9 @@
#nullable enable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Emby.Naming.Common; using Emby.Naming.Common;
@ -21,8 +21,7 @@ namespace Emby.Naming.Audio
public bool IsMultiPart(string path) public bool IsMultiPart(string path)
{ {
var filename = Path.GetFileName(path); var filename = Path.GetFileName(path);
if (filename.Length == 0)
if (string.IsNullOrEmpty(filename))
{ {
return false; return false;
} }
@ -39,18 +38,22 @@ namespace Emby.Naming.Audio
filename = filename.Replace(')', ' '); filename = filename.Replace(')', ' ');
filename = Regex.Replace(filename, @"\s+", " "); filename = Regex.Replace(filename, @"\s+", " ");
filename = filename.TrimStart(); ReadOnlySpan<char> trimmedFilename = filename.TrimStart();
foreach (var prefix in _options.AlbumStackingPrefixes) foreach (var prefix in _options.AlbumStackingPrefixes)
{ {
if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0) if (!trimmedFilename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{ {
continue; continue;
} }
var tmp = filename.Substring(prefix.Length); var tmp = trimmedFilename.Slice(prefix.Length).Trim();
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty; int index = tmp.IndexOf(' ');
if (index != -1)
{
tmp = tmp.Slice(0, index);
}
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _)) if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
{ {

View File

@ -1,3 +1,4 @@
#nullable enable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -11,7 +12,7 @@ namespace Emby.Naming.Audio
{ {
public static bool IsAudioFile(string path, NamingOptions options) public static bool IsAudioFile(string path, NamingOptions options)
{ {
var extension = Path.GetExtension(path) ?? string.Empty; var extension = Path.GetExtension(path);
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
} }
} }

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

@ -23,11 +23,6 @@ namespace Emby.Naming.Common
{ {
} }
public EpisodeExpression()
: this(null)
{
}
public string Expression public string Expression
{ {
get => _expression; get => _expression;
@ -48,6 +43,6 @@ namespace Emby.Naming.Common
public string[] DateTimeFormats { get; set; } public string[] DateTimeFormats { get; set; }
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled)); public Regex Regex => _regex ??= new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
} }
} }

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

@ -1,3 +1,4 @@
#nullable enable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -16,11 +17,11 @@ namespace Emby.Naming.Subtitles
_options = options; _options = options;
} }
public SubtitleInfo ParseFile(string path) public SubtitleInfo? ParseFile(string path)
{ {
if (string.IsNullOrEmpty(path)) if (path.Length == 0)
{ {
throw new ArgumentNullException(nameof(path)); throw new ArgumentException("File path can't be empty.", nameof(path));
} }
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path);
@ -52,11 +53,6 @@ namespace Emby.Naming.Subtitles
private string[] GetFlags(string path) private string[] GetFlags(string path)
{ {
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
}
// Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _. // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
var file = Path.GetFileName(path); var file = Path.GetFileName(path);

View File

@ -227,7 +227,7 @@ namespace Emby.Naming.Video
} }
return remainingFiles return remainingFiles
.Where(i => i.ExtraType == null) .Where(i => i.ExtraType != null)
.Where(i => baseNames.Any(b => .Where(i => baseNames.Any(b =>
i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
.ToList(); .ToList();

View File

@ -89,14 +89,14 @@ namespace Emby.Naming.Video
if (parseName) if (parseName)
{ {
var cleanDateTimeResult = CleanDateTime(name); var cleanDateTimeResult = CleanDateTime(name);
name = cleanDateTimeResult.Name;
year = cleanDateTimeResult.Year;
if (extraResult.ExtraType == null if (extraResult.ExtraType == null
&& TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan<char> newName)) && TryCleanString(name, out ReadOnlySpan<char> newName))
{ {
name = newName.ToString(); name = newName.ToString();
} }
year = cleanDateTimeResult.Year;
} }
return new VideoFileInfo return new VideoFileInfo

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;
@ -143,7 +143,7 @@ namespace Emby.Notifications
var notification = new NotificationRequest var notification = new NotificationRequest
{ {
Description = "Please see jellyfin.media for details.", Description = "Please see jellyfin.org for details.",
NotificationType = type, NotificationType = type,
Name = _localization.GetLocalizedString("NewVersionIsAvailable") Name = _localization.GetLocalizedString("NewVersionIsAvailable")
}; };

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

@ -4,11 +4,10 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
@ -30,7 +29,7 @@ namespace Emby.Server.Implementations.Activity
/// </summary> /// </summary>
public sealed class ActivityLogEntryPoint : IServerEntryPoint public sealed class ActivityLogEntryPoint : IServerEntryPoint
{ {
private readonly ILogger _logger; private readonly ILogger<ActivityLogEntryPoint> _logger;
private readonly IInstallationManager _installationManager; private readonly IInstallationManager _installationManager;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
@ -38,14 +37,12 @@ namespace Emby.Server.Implementations.Activity
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly ISubtitleManager _subManager; private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IDeviceManager _deviceManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class. /// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param> /// <param name="sessionManager">The session manager.</param>
/// <param name="deviceManager">The device manager.</param>
/// <param name="taskManager">The task manager.</param> /// <param name="taskManager">The task manager.</param>
/// <param name="activityManager">The activity manager.</param> /// <param name="activityManager">The activity manager.</param>
/// <param name="localization">The localization manager.</param> /// <param name="localization">The localization manager.</param>
@ -55,7 +52,6 @@ namespace Emby.Server.Implementations.Activity
public ActivityLogEntryPoint( public ActivityLogEntryPoint(
ILogger<ActivityLogEntryPoint> logger, ILogger<ActivityLogEntryPoint> logger,
ISessionManager sessionManager, ISessionManager sessionManager,
IDeviceManager deviceManager,
ITaskManager taskManager, ITaskManager taskManager,
IActivityManager activityManager, IActivityManager activityManager,
ILocalizationManager localization, ILocalizationManager localization,
@ -65,7 +61,6 @@ namespace Emby.Server.Implementations.Activity
{ {
_logger = logger; _logger = logger;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_deviceManager = deviceManager;
_taskManager = taskManager; _taskManager = taskManager;
_activityManager = activityManager; _activityManager = activityManager;
_localization = localization; _localization = localization;
@ -93,58 +88,45 @@ namespace Emby.Server.Implementations.Activity
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure; _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
_userManager.UserCreated += OnUserCreated; _userManager.OnUserCreated += OnUserCreated;
_userManager.UserPasswordChanged += OnUserPasswordChanged; _userManager.OnUserPasswordChanged += OnUserPasswordChanged;
_userManager.UserDeleted += OnUserDeleted; _userManager.OnUserDeleted += OnUserDeleted;
_userManager.UserPolicyUpdated += OnUserPolicyUpdated; _userManager.OnUserLockedOut += OnUserLockedOut;
_userManager.UserLockedOut += OnUserLockedOut;
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
return Task.CompletedTask; return Task.CompletedTask;
} }
private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e) private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("CameraImageUploadedFrom"),
e.Argument.Device.Name),
Type = NotificationType.CameraImageUploaded.ToString()
});
}
private void OnUserLockedOut(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserLockedOutWithName"), _localization.GetLocalizedString("UserLockedOutWithName"),
e.Argument.Name), e.Argument.Username),
Type = NotificationType.UserLockedOut.ToString(), NotificationType.UserLockedOut.ToString(),
UserId = e.Argument.Id e.Argument.Id)
}); {
LogSeverity = LogLevel.Error
}).ConfigureAwait(false);
} }
private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
e.Provider, e.Provider,
Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)), Notifications.NotificationEntryPoint.GetItemName(e.Item)),
Type = "SubtitleDownloadFailure", "SubtitleDownloadFailure",
Guid.Empty)
{
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture), ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
ShortOverview = e.Exception.Message ShortOverview = e.Exception.Message
}); }).ConfigureAwait(false);
} }
private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e) private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
{ {
var item = e.MediaInfo; var item = e.MediaInfo;
@ -167,20 +149,19 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users[0]; var user = e.Users[0];
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
user.Name, user.Username,
GetItemName(item), GetItemName(item),
e.DeviceName), e.DeviceName),
Type = GetPlaybackStoppedNotificationType(item.MediaType), GetPlaybackStoppedNotificationType(item.MediaType),
UserId = user.Id user.Id))
}); .ConfigureAwait(false);
} }
private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e) private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
{ {
var item = e.MediaInfo; var item = e.MediaInfo;
@ -203,17 +184,16 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users.First(); var user = e.Users.First();
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), _localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
user.Name, user.Username,
GetItemName(item), GetItemName(item),
e.DeviceName), e.DeviceName),
Type = GetPlaybackNotificationType(item.MediaType), GetPlaybackNotificationType(item.MediaType),
UserId = user.Id user.Id))
}); .ConfigureAwait(false);
} }
private static string GetItemName(BaseItemDto item) private static string GetItemName(BaseItemDto item)
@ -263,230 +243,197 @@ namespace Emby.Server.Implementations.Activity
return null; return null;
} }
private void OnSessionEnded(object sender, SessionEventArgs e) private async void OnSessionEnded(object sender, SessionEventArgs e)
{ {
string name;
var session = e.SessionInfo; var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName)) if (string.IsNullOrEmpty(session.UserName))
{ {
name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("DeviceOfflineWithName"),
session.DeviceName);
// Causing too much spam for now
return; return;
} }
else
{ await CreateLogEntry(new ActivityLog(
name = string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOfflineFromDevice"), _localization.GetLocalizedString("UserOfflineFromDevice"),
session.UserName, session.UserName,
session.DeviceName); session.DeviceName),
} "SessionEnded",
session.UserId)
CreateLogEntry(new ActivityLogEntry
{ {
Name = name,
Type = "SessionEnded",
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"), _localization.GetLocalizedString("LabelIpAddressValue"),
session.RemoteEndPoint), session.RemoteEndPoint),
UserId = session.UserId }).ConfigureAwait(false);
});
} }
private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e) private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
{ {
var user = e.Argument.User; var user = e.Argument.User;
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), _localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
user.Name), user.Name),
Type = "AuthenticationSucceeded", "AuthenticationSucceeded",
user.Id)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"), _localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.SessionInfo.RemoteEndPoint), e.Argument.SessionInfo.RemoteEndPoint),
UserId = user.Id }).ConfigureAwait(false);
});
} }
private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e) private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), _localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
e.Argument.Username), e.Argument.Username),
Type = "AuthenticationFailed", "AuthenticationFailed",
Guid.Empty)
{
LogSeverity = LogLevel.Error,
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"), _localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.RemoteEndPoint), e.Argument.RemoteEndPoint),
Severity = LogLevel.Error }).ConfigureAwait(false);
});
} }
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e) private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
e.Argument.Name),
Type = "UserPolicyUpdated",
UserId = e.Argument.Id
});
}
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserDeletedWithName"), _localization.GetLocalizedString("UserDeletedWithName"),
e.Argument.Name), e.Argument.Username),
Type = "UserDeleted" "UserDeleted",
}); Guid.Empty))
.ConfigureAwait(false);
} }
private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e) private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPasswordChangedWithName"), _localization.GetLocalizedString("UserPasswordChangedWithName"),
e.Argument.Name), e.Argument.Username),
Type = "UserPasswordChanged", "UserPasswordChanged",
UserId = e.Argument.Id e.Argument.Id))
}); .ConfigureAwait(false);
} }
private void OnUserCreated(object sender, GenericEventArgs<User> e) private async void OnUserCreated(object sender, GenericEventArgs<User> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserCreatedWithName"), _localization.GetLocalizedString("UserCreatedWithName"),
e.Argument.Name), e.Argument.Username),
Type = "UserCreated", "UserCreated",
UserId = e.Argument.Id e.Argument.Id))
}); .ConfigureAwait(false);
} }
private void OnSessionStarted(object sender, SessionEventArgs e) private async void OnSessionStarted(object sender, SessionEventArgs e)
{ {
string name;
var session = e.SessionInfo; var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName)) if (string.IsNullOrEmpty(session.UserName))
{ {
name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("DeviceOnlineWithName"),
session.DeviceName);
// Causing too much spam for now
return; return;
} }
else
{ await CreateLogEntry(new ActivityLog(
name = string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOnlineFromDevice"), _localization.GetLocalizedString("UserOnlineFromDevice"),
session.UserName, session.UserName,
session.DeviceName); session.DeviceName),
} "SessionStarted",
session.UserId)
CreateLogEntry(new ActivityLogEntry
{ {
Name = name,
Type = "SessionStarted",
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"), _localization.GetLocalizedString("LabelIpAddressValue"),
session.RemoteEndPoint), session.RemoteEndPoint)
UserId = session.UserId }).ConfigureAwait(false);
});
} }
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e) private async void OnPluginUpdated(object sender, InstallationInfo e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUpdatedWithName"), _localization.GetLocalizedString("PluginUpdatedWithName"),
e.Argument.Item1.Name), e.Name),
Type = NotificationType.PluginUpdateInstalled.ToString(), NotificationType.PluginUpdateInstalled.ToString(),
Guid.Empty)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"), _localization.GetLocalizedString("VersionNumber"),
e.Argument.Item2.version), e.Version),
Overview = e.Argument.Item2.changelog Overview = e.Changelog
}); }).ConfigureAwait(false);
} }
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e) private async void OnPluginUninstalled(object sender, IPlugin e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUninstalledWithName"), _localization.GetLocalizedString("PluginUninstalledWithName"),
e.Argument.Name), e.Name),
Type = NotificationType.PluginUninstalled.ToString() NotificationType.PluginUninstalled.ToString(),
}); Guid.Empty))
.ConfigureAwait(false);
} }
private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e) private async void OnPluginInstalled(object sender, InstallationInfo e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginInstalledWithName"), _localization.GetLocalizedString("PluginInstalledWithName"),
e.Argument.name), e.Name),
Type = NotificationType.PluginInstalled.ToString(), NotificationType.PluginInstalled.ToString(),
Guid.Empty)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"), _localization.GetLocalizedString("VersionNumber"),
e.Argument.version) e.Version)
}); }).ConfigureAwait(false);
} }
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
{ {
var installationInfo = e.InstallationInfo; var installationInfo = e.InstallationInfo;
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("NameInstallFailed"), _localization.GetLocalizedString("NameInstallFailed"),
installationInfo.Name), installationInfo.Name),
Type = NotificationType.InstallationFailed.ToString(), NotificationType.InstallationFailed.ToString(),
Guid.Empty)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"), _localization.GetLocalizedString("VersionNumber"),
installationInfo.Version), installationInfo.Version),
Overview = e.Exception.Message Overview = e.Exception.Message
}); }).ConfigureAwait(false);
} }
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e) private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
{ {
var result = e.Result; var result = e.Result;
var task = e.Task; var task = e.Task;
@ -517,22 +464,20 @@ namespace Emby.Server.Implementations.Activity
vals.Add(e.Result.LongErrorMessage); vals.Add(e.Result.LongErrorMessage);
} }
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
NotificationType.TaskFailed.ToString(),
Guid.Empty)
{ {
Name = string.Format( LogSeverity = LogLevel.Error,
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ScheduledTaskFailedWithName"),
task.Name),
Type = NotificationType.TaskFailed.ToString(),
Overview = string.Join(Environment.NewLine, vals), Overview = string.Join(Environment.NewLine, vals),
ShortOverview = runningTime, ShortOverview = runningTime
Severity = LogLevel.Error }).ConfigureAwait(false);
});
} }
} }
private void CreateLogEntry(ActivityLogEntry entry) private async Task CreateLogEntry(ActivityLog entry)
=> _activityManager.Create(entry); => await _activityManager.CreateAsync(entry).ConfigureAwait(false);
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
@ -554,13 +499,10 @@ namespace Emby.Server.Implementations.Activity
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure; _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
_userManager.UserCreated -= OnUserCreated; _userManager.OnUserCreated -= OnUserCreated;
_userManager.UserPasswordChanged -= OnUserPasswordChanged; _userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
_userManager.UserDeleted -= OnUserDeleted; _userManager.OnUserDeleted -= OnUserDeleted;
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated; _userManager.OnUserLockedOut -= OnUserLockedOut;
_userManager.UserLockedOut -= OnUserLockedOut;
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
} }
/// <summary> /// <summary>
@ -580,7 +522,7 @@ namespace Emby.Server.Implementations.Activity
{ {
int years = days / DaysInYear; int years = days / DaysInYear;
values.Add(CreateValueString(years, "year")); values.Add(CreateValueString(years, "year"));
days = days % DaysInYear; days %= DaysInYear;
} }
// Number of months // Number of months

View File

@ -1,62 +0,0 @@
#pragma warning disable CS1591
using System;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
public class ActivityManager : IActivityManager
{
private readonly IActivityRepository _repo;
private readonly IUserManager _userManager;
public ActivityManager(IActivityRepository repo, IUserManager userManager)
{
_repo = repo;
_userManager = userManager;
}
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
public void Create(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;
_repo.Create(entry);
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
foreach (var item in result.Items)
{
if (item.UserId == Guid.Empty)
{
continue;
}
var user = _userManager.GetUserById(item.UserId);
if (user != null)
{
var dto = _userManager.GetUserDto(user);
item.UserPrimaryImageTag = dto.PrimaryImageTag;
}
}
return result;
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
return GetActivityLogEntries(minDate, null, startIndex, limit);
}
}
}

View File

@ -1,317 +0,0 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Emby.Server.Implementations.Data;
using MediaBrowser.Controller;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Activity
{
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
private readonly IFileSystem _fileSystem;
public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
: base(logger)
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
_fileSystem = fileSystem;
}
public void Initialize()
{
try
{
InitializeInternal();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
_fileSystem.DeleteFile(DbFilePath);
InitializeInternal();
}
}
private void InitializeInternal()
{
using (var connection = GetConnection())
{
connection.RunQueries(new[]
{
"create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
"drop index if exists idx_ActivityLogEntries"
});
TryMigrate(connection);
}
}
private void TryMigrate(ManagedConnection connection)
{
try
{
if (TableExists(connection, "ActivityLogEntries"))
{
connection.RunQueries(new[]
{
"INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries",
"drop table if exists ActivityLogEntries"
});
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error migrating activity log database");
}
}
public void Create(ActivityLogEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
using (var connection = GetConnection())
{
connection.RunInTransaction(
db =>
{
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
{
statement.TryBind("@Name", entry.Name);
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
if (entry.UserId.Equals(Guid.Empty))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}
},
TransactionMode);
}
}
public void Update(ActivityLogEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
using (var connection = GetConnection())
{
connection.RunInTransaction(
db =>
{
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
{
statement.TryBind("@Id", entry.Id);
statement.TryBind("@Name", entry.Name);
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
if (entry.UserId.Equals(Guid.Empty))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}
},
TransactionMode);
}
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var commandText = BaseActivitySelectText;
var whereClauses = new List<string>();
if (minDate.HasValue)
{
whereClauses.Add("DateCreated>=@DateCreated");
}
if (hasUserId.HasValue)
{
if (hasUserId.Value)
{
whereClauses.Add("UserId not null");
}
else
{
whereClauses.Add("UserId is null");
}
}
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
if (startIndex.HasValue && startIndex.Value > 0)
{
var pagingWhereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
whereClauses.Add(
string.Format(
CultureInfo.InvariantCulture,
"Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
pagingWhereText,
startIndex.Value));
}
var whereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
commandText += whereText;
commandText += " ORDER BY DateCreated DESC";
if (limit.HasValue)
{
commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
}
var statementTexts = new[]
{
commandText,
"select count (Id) from ActivityLog" + whereTextWithoutPaging
};
var list = new List<ActivityLogEntry>();
var result = new QueryResult<ActivityLogEntry>();
using (var connection = GetConnection(true))
{
connection.RunInTransaction(
db =>
{
var statements = PrepareAll(db, statementTexts).ToList();
using (var statement = statements[0])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
foreach (var row in statement.ExecuteQuery())
{
list.Add(GetEntry(row));
}
}
using (var statement = statements[1])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
},
ReadTransactionMode);
}
result.Items = list;
return result;
}
private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader)
{
var index = 0;
var info = new ActivityLogEntry
{
Id = reader[index].ToInt64()
};
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Name = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Overview = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.ShortOverview = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Type = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.ItemId = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.UserId = new Guid(reader[index].ToString());
}
index++;
info.Date = reader[index].ReadDateTime();
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
}
return info;
}
}
}

View File

@ -15,6 +15,11 @@ namespace Emby.Server.Implementations.AppBase
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class. /// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
/// </summary> /// </summary>
/// <param name="programDataPath">The program data path.</param>
/// <param name="logDirectoryPath">The log directory path.</param>
/// <param name="configurationDirectoryPath">The configuration directory path.</param>
/// <param name="cacheDirectoryPath">The cache directory path.</param>
/// <param name="webDirectoryPath">The web directory path.</param>
protected BaseApplicationPaths( protected BaseApplicationPaths(
string programDataPath, string programDataPath,
string logDirectoryPath, string logDirectoryPath,

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

@ -36,8 +36,7 @@ namespace Emby.Server.Implementations.AppBase
configuration = Activator.CreateInstance(type); configuration = Activator.CreateInstance(type);
} }
using (var stream = new MemoryStream()) using var stream = new MemoryStream();
{
xmlSerializer.SerializeToStream(configuration, stream); xmlSerializer.SerializeToStream(configuration, stream);
// Take the object we just got and serialize it back to bytes // Take the object we just got and serialize it back to bytes
@ -55,5 +54,4 @@ namespace Emby.Server.Implementations.AppBase
return configuration; return configuration;
} }
} }
}
} }

View File

@ -22,7 +22,6 @@ using Emby.Dlna.Ssdp;
using Emby.Drawing; using Emby.Drawing;
using Emby.Notifications; using Emby.Notifications;
using Emby.Photos; using Emby.Photos;
using Emby.Server.Implementations.Activity;
using Emby.Server.Implementations.Archiving; using Emby.Server.Implementations.Archiving;
using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Collections;
@ -44,9 +43,9 @@ using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Services; using Emby.Server.Implementations.Services;
using Emby.Server.Implementations.Session; using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.SocketSharp;
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;
@ -80,9 +79,9 @@ 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.Activity;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -94,7 +93,6 @@ using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Updates;
using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Chapters;
using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Plugins.TheTvdb; using MediaBrowser.Providers.Plugins.TheTvdb;
@ -102,9 +100,9 @@ using MediaBrowser.Providers.Subtitles;
using MediaBrowser.WebDashboard.Api; using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers; using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Prometheus.DotNetRuntime;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Server.Implementations namespace Emby.Server.Implementations
@ -175,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;
@ -259,6 +257,12 @@ namespace Emby.Server.Implementations
_startupOptions = options; _startupOptions = options;
// Initialize runtime stat collection
if (ServerConfigurationManager.Configuration.EnableMetrics)
{
DotNetRuntimeStatsBuilder.Default().StartCollecting();
}
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
_networkManager.NetworkChanged += OnNetworkChanged; _networkManager.NetworkChanged += OnNetworkChanged;
@ -496,32 +500,8 @@ namespace Emby.Server.Implementations
RegisterServices(serviceCollection); RegisterServices(serviceCollection);
} }
public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next) public Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
{ => _httpServer.RequestHandler(context);
if (!context.WebSockets.IsWebSocketRequest)
{
await next().ConfigureAwait(false);
return;
}
await _httpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
}
public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
{
if (context.WebSockets.IsWebSocketRequest)
{
await next().ConfigureAwait(false);
return;
}
var request = context.Request;
var response = context.Response;
var localPath = context.Request.Path.ToString();
var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger<WebSocketSharpRequest>());
await _httpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
}
/// <summary> /// <summary>
/// Registers services/resources with the service collection that will be available via DI. /// Registers services/resources with the service collection that will be available via DI.
@ -539,13 +519,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>(); serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
// TODO: Remove support for injecting ILogger completely
serviceCollection.AddSingleton((provider) =>
{
Logger.LogWarning("Injecting ILogger directly is deprecated and should be replaced with ILogger<T>");
return Logger;
});
serviceCollection.AddSingleton(_fileSystemManager); serviceCollection.AddSingleton(_fileSystemManager);
serviceCollection.AddSingleton<TvdbClientManager>(); serviceCollection.AddSingleton<TvdbClientManager>();
@ -589,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
@ -614,7 +584,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<ISearchEngine, SearchEngine>(); serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
serviceCollection.AddSingleton<ServiceController>(); serviceCollection.AddSingleton<ServiceController>();
serviceCollection.AddSingleton<IHttpListener, WebSocketSharpListener>();
serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>(); serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>(); serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
@ -643,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>();
@ -656,9 +627,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>(); serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
serviceCollection.AddSingleton<IActivityRepository, ActivityRepository>();
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>(); serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
serviceCollection.AddSingleton<ISessionContext, SessionContext>(); serviceCollection.AddSingleton<ISessionContext, SessionContext>();
@ -688,16 +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();
((ActivityRepository)Resolve<IActivityRepository>()).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();
} }
@ -780,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>();
@ -994,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()
{ {
@ -1143,9 +1105,6 @@ namespace Emby.Server.Implementations
ItemsByNamePath = ApplicationPaths.InternalMetadataPath, ItemsByNamePath = ApplicationPaths.InternalMetadataPath,
InternalMetadataPath = ApplicationPaths.InternalMetadataPath, InternalMetadataPath = ApplicationPaths.InternalMetadataPath,
CachePath = ApplicationPaths.CachePath, CachePath = ApplicationPaths.CachePath,
HttpServerPortNumber = HttpPort,
SupportsHttps = SupportsHttps,
HttpsPortNumber = HttpsPort,
OperatingSystem = OperatingSystem.Id.ToString(), OperatingSystem = OperatingSystem.Id.ToString(),
OperatingSystemDisplayName = OperatingSystem.Name, OperatingSystemDisplayName = OperatingSystem.Name,
CanSelfRestart = CanSelfRestart, CanSelfRestart = CanSelfRestart,
@ -1181,23 +1140,22 @@ namespace Emby.Server.Implementations
}; };
} }
public bool EnableHttps => SupportsHttps && ServerConfigurationManager.Configuration.EnableHttps; /// <inheritdoc/>
public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps;
public bool SupportsHttps => Certificate != null || ServerConfigurationManager.Configuration.IsBehindProxy;
/// <inheritdoc/>
public async Task<string> GetLocalApiUrl(CancellationToken cancellationToken) public async Task<string> GetLocalApiUrl(CancellationToken cancellationToken)
{ {
try try
{ {
// Return the first matched address, if found, or the first known local address // Return the first matched address, if found, or the first known local address
var addresses = await GetLocalIpAddressesInternal(false, 1, cancellationToken).ConfigureAwait(false); var addresses = await GetLocalIpAddressesInternal(false, 1, cancellationToken).ConfigureAwait(false);
if (addresses.Count == 0)
foreach (var address in addresses)
{ {
return GetLocalApiUrl(address); return null;
} }
return null; return GetLocalApiUrl(addresses.First());
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1240,22 +1198,24 @@ namespace Emby.Server.Implementations
return GetLocalApiUrl(ipAddress.ToString()); return GetLocalApiUrl(ipAddress.ToString());
} }
/// <inheritdoc /> /// <inheritdoc/>
public string GetLocalApiUrl(ReadOnlySpan<char> host) public string GetLoopbackHttpApiUrl()
{ {
var url = new StringBuilder(64); return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort);
url.Append(EnableHttps ? "https://" : "http://")
.Append(host)
.Append(':')
.Append(EnableHttps ? HttpsPort : HttpPort);
string baseUrl = ServerConfigurationManager.Configuration.BaseUrl;
if (baseUrl.Length != 0)
{
url.Append(baseUrl);
} }
return url.ToString(); /// <inheritdoc/>
public string GetLocalApiUrl(ReadOnlySpan<char> host, string scheme = null, int? port = null)
{
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
// not. For consistency, always trim the trailing slash.
return new UriBuilder
{
Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
Host = host.ToString(),
Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
Path = ServerConfigurationManager.Configuration.BaseUrl
}.ToString().TrimEnd('/');
} }
public Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken) public Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken)
@ -1289,7 +1249,7 @@ namespace Emby.Server.Implementations
} }
} }
var valid = await IsIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false); var valid = await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false);
if (valid) if (valid)
{ {
resultList.Add(address); resultList.Add(address);
@ -1323,7 +1283,7 @@ namespace Emby.Server.Implementations
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private async Task<bool> IsIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken) private async Task<bool> IsLocalIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken)
{ {
if (address.Equals(IPAddress.Loopback) if (address.Equals(IPAddress.Loopback)
|| address.Equals(IPAddress.IPv6Loopback)) || address.Equals(IPAddress.IPv6Loopback))
@ -1331,8 +1291,7 @@ namespace Emby.Server.Implementations
return true; return true;
} }
var apiUrl = GetLocalApiUrl(address); var apiUrl = GetLocalApiUrl(address) + "/system/ping";
apiUrl += "/system/ping";
if (_validAddressResults.TryGetValue(apiUrl, out var cachedResult)) if (_validAddressResults.TryGetValue(apiUrl, out var cachedResult))
{ {

View File

@ -22,11 +22,9 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles) public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{ {
using (var fileStream = File.OpenRead(sourceFile)) using var fileStream = File.OpenRead(sourceFile);
{
ExtractAll(fileStream, targetPath, overwriteExistingFiles); ExtractAll(fileStream, targetPath, overwriteExistingFiles);
} }
}
/// <summary> /// <summary>
/// Extracts all. /// Extracts all.
@ -36,10 +34,11 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles) public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
{ {
using (var reader = ReaderFactory.Open(source)) using var reader = ReaderFactory.Open(source);
var options = new ExtractionOptions
{ {
var options = new ExtractionOptions(); ExtractFullPath = true
options.ExtractFullPath = true; };
if (overwriteExistingFiles) if (overwriteExistingFiles)
{ {
@ -48,47 +47,37 @@ namespace Emby.Server.Implementations.Archiving
reader.WriteAllToDirectory(targetPath, options); reader.WriteAllToDirectory(targetPath, options);
} }
}
/// <inheritdoc /> /// <inheritdoc />
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles) public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
{ {
using (var reader = ZipReader.Open(source)) using var reader = ZipReader.Open(source);
var options = new ExtractionOptions
{ {
var options = new ExtractionOptions(); ExtractFullPath = true,
options.ExtractFullPath = true; Overwrite = overwriteExistingFiles
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options); reader.WriteAllToDirectory(targetPath, options);
} }
}
/// <inheritdoc /> /// <inheritdoc />
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles) public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
{ {
using (var reader = GZipReader.Open(source)) using var reader = GZipReader.Open(source);
var options = new ExtractionOptions
{ {
var options = new ExtractionOptions(); ExtractFullPath = true,
options.ExtractFullPath = true; Overwrite = overwriteExistingFiles
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options); reader.WriteAllToDirectory(targetPath, options);
} }
}
/// <inheritdoc /> /// <inheritdoc />
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName) public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
{ {
using (var reader = GZipReader.Open(source)) using var reader = GZipReader.Open(source);
{
if (reader.MoveToNextEntry()) if (reader.MoveToNextEntry())
{ {
var entry = reader.Entry; var entry = reader.Entry;
@ -98,10 +87,10 @@ namespace Emby.Server.Implementations.Archiving
{ {
filename = defaultFileName; filename = defaultFileName;
} }
reader.WriteEntryToFile(Path.Combine(targetPath, filename)); reader.WriteEntryToFile(Path.Combine(targetPath, filename));
} }
} }
}
/// <summary> /// <summary>
/// Extracts all from7z. /// Extracts all from7z.
@ -111,11 +100,9 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles) public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{ {
using (var fileStream = File.OpenRead(sourceFile)) using var fileStream = File.OpenRead(sourceFile);
{
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles); ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
} }
}
/// <summary> /// <summary>
/// Extracts all from7z. /// Extracts all from7z.
@ -125,22 +112,16 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles) public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
{ {
using (var archive = SevenZipArchive.Open(source)) using var archive = SevenZipArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{ {
using (var reader = archive.ExtractAllEntries()) ExtractFullPath = true,
{ Overwrite = overwriteExistingFiles
var options = new ExtractionOptions(); };
options.ExtractFullPath = true;
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options); reader.WriteAllToDirectory(targetPath, options);
} }
}
}
/// <summary> /// <summary>
/// Extracts all from tar. /// Extracts all from tar.
@ -150,11 +131,9 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles) public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{ {
using (var fileStream = File.OpenRead(sourceFile)) using var fileStream = File.OpenRead(sourceFile);
{
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles); ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
} }
}
/// <summary> /// <summary>
/// Extracts all from tar. /// Extracts all from tar.
@ -164,21 +143,15 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles) public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
{ {
using (var archive = TarArchive.Open(source)) using var archive = TarArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{ {
using (var reader = archive.ExtractAllEntries()) ExtractFullPath = true,
{ Overwrite = overwriteExistingFiles
var options = new ExtractionOptions(); };
options.ExtractFullPath = true;
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options); reader.WriteAllToDirectory(targetPath, options);
} }
} }
}
}
} }

View File

@ -1,13 +1,15 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Branding; using MediaBrowser.Model.Branding;
namespace Emby.Server.Implementations.Branding namespace Emby.Server.Implementations.Branding
{ {
/// <summary>
/// A configuration factory for <see cref="BrandingOptions"/>.
/// </summary>
public class BrandingConfigurationFactory : IConfigurationFactory public class BrandingConfigurationFactory : IConfigurationFactory
{ {
/// <inheritdoc />
public IEnumerable<ConfigurationStore> GetConfigurations() public IEnumerable<ConfigurationStore> GetConfigurations()
{ {
return new[] return new[]

View File

@ -31,18 +31,18 @@ namespace Emby.Server.Implementations.Browser
/// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored. /// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
/// </summary> /// </summary>
/// <param name="appHost">The application host.</param> /// <param name="appHost">The application host.</param>
/// <param name="url">The URL.</param> /// <param name="relativeUrl">The URL to open, relative to the server base URL.</param>
private static void TryOpenUrl(IServerApplicationHost appHost, string url) private static void TryOpenUrl(IServerApplicationHost appHost, string relativeUrl)
{ {
try try
{ {
string baseUrl = appHost.GetLocalApiUrl("localhost"); string baseUrl = appHost.GetLocalApiUrl("localhost");
appHost.LaunchUrl(baseUrl + url); appHost.LaunchUrl(baseUrl + relativeUrl);
} }
catch (Exception ex) catch (Exception ex)
{ {
var logger = appHost.Resolve<ILogger>(); var logger = appHost.Resolve<ILogger<IServerApplicationHost>>();
logger?.LogError(ex, "Failed to open browser window with URL {URL}", url); logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
} }
} }
} }

View File

@ -1,7 +1,6 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
@ -11,6 +10,9 @@ using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.Channels namespace Emby.Server.Implementations.Channels
{ {
/// <summary>
/// A media source provider for channels.
/// </summary>
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
{ {
private readonly ChannelManager _channelManager; private readonly ChannelManager _channelManager;
@ -27,12 +29,9 @@ namespace Emby.Server.Implementations.Channels
/// <inheritdoc /> /// <inheritdoc />
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken) public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
{ {
if (item.SourceType == SourceType.Channel) return item.SourceType == SourceType.Channel
{ ? _channelManager.GetDynamicMediaSources(item, cancellationToken)
return _channelManager.GetDynamicMediaSources(item, cancellationToken); : Task.FromResult(Enumerable.Empty<MediaSourceInfo>());
}
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -11,22 +9,32 @@ using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Channels namespace Emby.Server.Implementations.Channels
{ {
/// <summary>
/// An image provider for channels.
/// </summary>
public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
{ {
private readonly IChannelManager _channelManager; private readonly IChannelManager _channelManager;
/// <summary>
/// Initializes a new instance of the <see cref="ChannelImageProvider"/> class.
/// </summary>
/// <param name="channelManager">The channel manager.</param>
public ChannelImageProvider(IChannelManager channelManager) public ChannelImageProvider(IChannelManager channelManager)
{ {
_channelManager = channelManager; _channelManager = channelManager;
} }
/// <inheritdoc />
public string Name => "Channel Image Provider"; public string Name => "Channel Image Provider";
/// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{ {
return GetChannel(item).GetSupportedChannelImages(); return GetChannel(item).GetSupportedChannelImages();
} }
/// <inheritdoc />
public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken) public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
{ {
var channel = GetChannel(item); var channel = GetChannel(item);
@ -34,6 +42,7 @@ namespace Emby.Server.Implementations.Channels
return channel.GetChannelImage(type, cancellationToken); return channel.GetChannelImage(type, cancellationToken);
} }
/// <inheritdoc />
public bool Supports(BaseItem item) public bool Supports(BaseItem item)
{ {
return item is Channel; return item is Channel;
@ -46,6 +55,7 @@ namespace Emby.Server.Implementations.Channels
return ((ChannelManager)_channelManager).GetChannelProvider(channel); return ((ChannelManager)_channelManager).GetChannelProvider(channel);
} }
/// <inheritdoc />
public bool HasChanged(BaseItem item, IDirectoryService directoryService) public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{ {
return GetSupportedImages(item).Any(i => !item.HasImage(i)); return GetSupportedImages(item).Any(i => !item.HasImage(i));

View File

@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -8,6 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
@ -15,8 +14,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Channels; using MediaBrowser.Model.Channels;
@ -26,16 +23,24 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Server.Implementations.Channels namespace Emby.Server.Implementations.Channels
{ {
/// <summary>
/// The LiveTV channel manager.
/// </summary>
public class ChannelManager : IChannelManager public class ChannelManager : IChannelManager
{ {
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger; private readonly ILogger<ChannelManager> _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
@ -46,6 +51,18 @@ namespace Emby.Server.Implementations.Channels
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
/// <summary>
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
/// </summary>
/// <param name="userManager">The user manager.</param>
/// <param name="dtoService">The dto service.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="config">The server configuration manager.</param>
/// <param name="fileSystem">The filesystem.</param>
/// <param name="userDataManager">The user data manager.</param>
/// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="providerManager">The provider manager.</param>
public ChannelManager( public ChannelManager(
IUserManager userManager, IUserManager userManager,
IDtoService dtoService, IDtoService dtoService,
@ -72,11 +89,13 @@ namespace Emby.Server.Implementations.Channels
private static TimeSpan CacheLength => TimeSpan.FromHours(3); private static TimeSpan CacheLength => TimeSpan.FromHours(3);
/// <inheritdoc />
public void AddParts(IEnumerable<IChannel> channels) public void AddParts(IEnumerable<IChannel> channels)
{ {
Channels = channels.ToArray(); Channels = channels.ToArray();
} }
/// <inheritdoc />
public bool EnableMediaSourceDisplay(BaseItem item) public bool EnableMediaSourceDisplay(BaseItem item)
{ {
var internalChannel = _libraryManager.GetItemById(item.ChannelId); var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@ -85,6 +104,7 @@ namespace Emby.Server.Implementations.Channels
return !(channel is IDisableMediaSourceDisplay); return !(channel is IDisableMediaSourceDisplay);
} }
/// <inheritdoc />
public bool CanDelete(BaseItem item) public bool CanDelete(BaseItem item)
{ {
var internalChannel = _libraryManager.GetItemById(item.ChannelId); var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@ -93,6 +113,7 @@ namespace Emby.Server.Implementations.Channels
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item); return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
} }
/// <inheritdoc />
public bool EnableMediaProbe(BaseItem item) public bool EnableMediaProbe(BaseItem item)
{ {
var internalChannel = _libraryManager.GetItemById(item.ChannelId); var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@ -101,6 +122,7 @@ namespace Emby.Server.Implementations.Channels
return channel is ISupportsMediaProbe; return channel is ISupportsMediaProbe;
} }
/// <inheritdoc />
public Task DeleteItem(BaseItem item) public Task DeleteItem(BaseItem item)
{ {
var internalChannel = _libraryManager.GetItemById(item.ChannelId); var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@ -127,11 +149,16 @@ namespace Emby.Server.Implementations.Channels
.OrderBy(i => i.Name); .OrderBy(i => i.Name);
} }
/// <summary>
/// Get the installed channel IDs.
/// </summary>
/// <returns>An <see cref="IEnumerable{T}"/> containing installed channel IDs.</returns>
public IEnumerable<Guid> GetInstalledChannelIds() public IEnumerable<Guid> GetInstalledChannelIds()
{ {
return GetAllChannels().Select(i => GetInternalChannelId(i.Name)); return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
} }
/// <inheritdoc />
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query) public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
{ {
var user = query.UserId.Equals(Guid.Empty) var user = query.UserId.Equals(Guid.Empty)
@ -249,6 +276,7 @@ namespace Emby.Server.Implementations.Channels
}; };
} }
/// <inheritdoc />
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query) public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
{ {
var user = query.UserId.Equals(Guid.Empty) var user = query.UserId.Equals(Guid.Empty)
@ -271,6 +299,12 @@ namespace Emby.Server.Implementations.Channels
return result; return result;
} }
/// <summary>
/// Refreshes the associated channels.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>The completed task.</returns>
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken) public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
{ {
var allChannelsList = GetAllChannels().ToList(); var allChannelsList = GetAllChannels().ToList();
@ -304,14 +338,7 @@ namespace Emby.Server.Implementations.Channels
private Channel GetChannelEntity(IChannel channel) private Channel GetChannelEntity(IChannel channel)
{ {
var item = GetChannel(GetInternalChannelId(channel.Name)); return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
if (item == null)
{
item = GetChannel(channel, CancellationToken.None).Result;
}
return item;
} }
private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item) private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item)
@ -350,6 +377,7 @@ namespace Emby.Server.Implementations.Channels
_jsonSerializer.SerializeToFile(mediaSources, path); _jsonSerializer.SerializeToFile(mediaSources, path);
} }
/// <inheritdoc />
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken) public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
{ {
IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item); IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item);
@ -359,6 +387,12 @@ namespace Emby.Server.Implementations.Channels
.ToList(); .ToList();
} }
/// <summary>
/// Gets the dynamic media sources based on the provided item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>The task representing the operation to get the media sources.</returns>
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken) public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken)
{ {
var channel = GetChannel(item.ChannelId); var channel = GetChannel(item.ChannelId);
@ -403,7 +437,7 @@ namespace Emby.Server.Implementations.Channels
private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info) private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info)
{ {
info.RunTimeTicks = info.RunTimeTicks ?? item.RunTimeTicks; info.RunTimeTicks ??= item.RunTimeTicks;
return info; return info;
} }
@ -481,31 +515,33 @@ namespace Emby.Server.Implementations.Channels
private static string GetOfficialRating(ChannelParentalRating rating) private static string GetOfficialRating(ChannelParentalRating rating)
{ {
switch (rating) return rating switch
{ {
case ChannelParentalRating.Adult: ChannelParentalRating.Adult => "XXX",
return "XXX"; ChannelParentalRating.UsR => "R",
case ChannelParentalRating.UsR: ChannelParentalRating.UsPG13 => "PG-13",
return "R"; ChannelParentalRating.UsPG => "PG",
case ChannelParentalRating.UsPG13: _ => null
return "PG-13"; };
case ChannelParentalRating.UsPG:
return "PG";
default:
return null;
}
} }
/// <summary>
/// Gets a channel with the provided Guid.
/// </summary>
/// <param name="id">The Guid.</param>
/// <returns>The corresponding channel.</returns>
public Channel GetChannel(Guid id) public Channel GetChannel(Guid id)
{ {
return _libraryManager.GetItemById(id) as Channel; return _libraryManager.GetItemById(id) as Channel;
} }
/// <inheritdoc />
public Channel GetChannel(string id) public Channel GetChannel(string id)
{ {
return _libraryManager.GetItemById(id) as Channel; return _libraryManager.GetItemById(id) as Channel;
} }
/// <inheritdoc />
public ChannelFeatures[] GetAllChannelFeatures() public ChannelFeatures[] GetAllChannelFeatures()
{ {
return _libraryManager.GetItemIds( return _libraryManager.GetItemIds(
@ -516,6 +552,7 @@ namespace Emby.Server.Implementations.Channels
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray(); }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
} }
/// <inheritdoc />
public ChannelFeatures GetChannelFeatures(string id) public ChannelFeatures GetChannelFeatures(string id)
{ {
if (string.IsNullOrEmpty(id)) if (string.IsNullOrEmpty(id))
@ -529,6 +566,11 @@ namespace Emby.Server.Implementations.Channels
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures()); return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
} }
/// <summary>
/// Checks whether the provided Guid supports external transfer.
/// </summary>
/// <param name="channelId">The Guid.</param>
/// <returns>Whether or not the provided Guid supports external transfer.</returns>
public bool SupportsExternalTransfer(Guid channelId) public bool SupportsExternalTransfer(Guid channelId)
{ {
var channelProvider = GetChannelProvider(channelId); var channelProvider = GetChannelProvider(channelId);
@ -536,6 +578,13 @@ namespace Emby.Server.Implementations.Channels
return channelProvider.GetChannelFeatures().SupportsContentDownloading; return channelProvider.GetChannelFeatures().SupportsContentDownloading;
} }
/// <summary>
/// Gets the provided channel's supported features.
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="provider">The provider.</param>
/// <param name="features">The features.</param>
/// <returns>The supported features.</returns>
public ChannelFeatures GetChannelFeaturesDto( public ChannelFeatures GetChannelFeaturesDto(
Channel channel, Channel channel,
IChannel provider, IChannel provider,
@ -570,6 +619,7 @@ namespace Emby.Server.Implementations.Channels
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel)); return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
} }
/// <inheritdoc />
public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken) public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
{ {
var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false); var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
@ -588,6 +638,7 @@ namespace Emby.Server.Implementations.Channels
return result; return result;
} }
/// <inheritdoc />
public async Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken) public async Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken)
{ {
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray(); var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
@ -666,6 +717,7 @@ namespace Emby.Server.Implementations.Channels
} }
} }
/// <inheritdoc />
public async Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken) public async Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken)
{ {
// Get the internal channel entity // Get the internal channel entity
@ -727,6 +779,7 @@ namespace Emby.Server.Implementations.Channels
return _libraryManager.GetItemsResult(query); return _libraryManager.GetItemsResult(query);
} }
/// <inheritdoc />
public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken) public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
{ {
var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false); var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
@ -742,7 +795,8 @@ namespace Emby.Server.Implementations.Channels
return result; return result;
} }
private async Task<ChannelItemResult> GetChannelItems(IChannel channel, private async Task<ChannelItemResult> GetChannelItems(
IChannel channel,
User user, User user,
string externalFolderId, string externalFolderId,
ChannelItemSortField? sortField, ChannelItemSortField? sortField,
@ -796,7 +850,7 @@ namespace Emby.Server.Implementations.Channels
var query = new InternalChannelItemQuery var query = new InternalChannelItemQuery
{ {
UserId = user == null ? Guid.Empty : user.Id, UserId = user?.Id ?? Guid.Empty,
SortBy = sortField, SortBy = sortField,
SortDescending = sortDescending, SortDescending = sortDescending,
FolderId = externalFolderId FolderId = externalFolderId
@ -923,60 +977,32 @@ namespace Emby.Server.Implementations.Channels
if (info.Type == ChannelItemType.Folder) if (info.Type == ChannelItemType.Folder)
{ {
if (info.FolderType == ChannelFolderType.MusicAlbum) item = info.FolderType switch
{ {
item = GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew); ChannelFolderType.MusicAlbum => GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew),
} ChannelFolderType.MusicArtist => GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew),
else if (info.FolderType == ChannelFolderType.MusicArtist) ChannelFolderType.PhotoAlbum => GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew),
{ ChannelFolderType.Series => GetItemById<Series>(info.Id, channelProvider.Name, out isNew),
item = GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew); ChannelFolderType.Season => GetItemById<Season>(info.Id, channelProvider.Name, out isNew),
} _ => GetItemById<Folder>(info.Id, channelProvider.Name, out isNew)
else if (info.FolderType == ChannelFolderType.PhotoAlbum) };
{
item = GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew);
}
else if (info.FolderType == ChannelFolderType.Series)
{
item = GetItemById<Series>(info.Id, channelProvider.Name, out isNew);
}
else if (info.FolderType == ChannelFolderType.Season)
{
item = GetItemById<Season>(info.Id, channelProvider.Name, out isNew);
}
else
{
item = GetItemById<Folder>(info.Id, channelProvider.Name, out isNew);
}
} }
else if (info.MediaType == ChannelMediaType.Audio) else if (info.MediaType == ChannelMediaType.Audio)
{ {
if (info.ContentType == ChannelMediaContentType.Podcast) item = info.ContentType == ChannelMediaContentType.Podcast
{ ? GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew)
item = GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew); : GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
} }
else else
{ {
item = GetItemById<Audio>(info.Id, channelProvider.Name, out isNew); item = info.ContentType switch
}
}
else
{ {
if (info.ContentType == ChannelMediaContentType.Episode) ChannelMediaContentType.Episode => GetItemById<Episode>(info.Id, channelProvider.Name, out isNew),
{ ChannelMediaContentType.Movie => GetItemById<Movie>(info.Id, channelProvider.Name, out isNew),
item = GetItemById<Episode>(info.Id, channelProvider.Name, out isNew); var x when x == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer
} => GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew),
else if (info.ContentType == ChannelMediaContentType.Movie) _ => GetItemById<Video>(info.Id, channelProvider.Name, out isNew)
{ };
item = GetItemById<Movie>(info.Id, channelProvider.Name, out isNew);
}
else if (info.ContentType == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer)
{
item = GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew);
}
else
{
item = GetItemById<Video>(info.Id, channelProvider.Name, out isNew);
}
} }
var enableMediaProbe = channelProvider is ISupportsMediaProbe; var enableMediaProbe = channelProvider is ISupportsMediaProbe;
@ -1046,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

@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System; using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -11,12 +9,21 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels namespace Emby.Server.Implementations.Channels
{ {
/// <summary>
/// A task to remove all non-installed channels from the database.
/// </summary>
public class ChannelPostScanTask public class ChannelPostScanTask
{ {
private readonly IChannelManager _channelManager; private readonly IChannelManager _channelManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="ChannelPostScanTask"/> class.
/// </summary>
/// <param name="channelManager">The channel manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="libraryManager">The library manager.</param>
public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager) public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
{ {
_channelManager = channelManager; _channelManager = channelManager;
@ -24,6 +31,12 @@ namespace Emby.Server.Implementations.Channels
_libraryManager = libraryManager; _libraryManager = libraryManager;
} }
/// <summary>
/// Runs this task.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The completed task.</returns>
public Task Run(IProgress<double> progress, CancellationToken cancellationToken) public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{ {
CleanDatabase(cancellationToken); CleanDatabase(cancellationToken);

View File

@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
@ -13,13 +11,23 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels namespace Emby.Server.Implementations.Channels
{ {
/// <summary>
/// The "Refresh Channels" scheduled task.
/// </summary>
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{ {
private readonly IChannelManager _channelManager; private readonly IChannelManager _channelManager;
private readonly ILogger _logger; private readonly ILogger<RefreshChannelsScheduledTask> _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshChannelsScheduledTask"/> class.
/// </summary>
/// <param name="channelManager">The channel manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="localization">The localization manager.</param>
public RefreshChannelsScheduledTask( public RefreshChannelsScheduledTask(
IChannelManager channelManager, IChannelManager channelManager,
ILogger<RefreshChannelsScheduledTask> logger, ILogger<RefreshChannelsScheduledTask> logger,

View File

@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Emby.Server.Implementations.Images; using Emby.Server.Implementations.Images;
@ -15,8 +13,18 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Collections namespace Emby.Server.Implementations.Collections
{ {
/// <summary>
/// A collection image provider.
/// </summary>
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet> public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
{ {
/// <summary>
/// Initializes a new instance of the <see cref="CollectionImageProvider"/> class.
/// </summary>
/// <param name="fileSystem">The filesystem.</param>
/// <param name="providerManager">The provider manager.</param>
/// <param name="applicationPaths">The application paths.</param>
/// <param name="imageProcessor">The image processor.</param>
public CollectionImageProvider( public CollectionImageProvider(
IFileSystem fileSystem, IFileSystem fileSystem,
IProviderManager providerManager, IProviderManager providerManager,
@ -26,6 +34,7 @@ namespace Emby.Server.Implementations.Collections
{ {
} }
/// <inheritdoc />
protected override bool Supports(BaseItem item) protected override bool Supports(BaseItem item)
{ {
// Right now this is the only way to prevent this image from getting created ahead of internet image providers // Right now this is the only way to prevent this image from getting created ahead of internet image providers
@ -37,6 +46,7 @@ namespace Emby.Server.Implementations.Collections
return base.Supports(item); return base.Supports(item);
} }
/// <inheritdoc />
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{ {
var playlist = (BoxSet)item; var playlist = (BoxSet)item;
@ -46,14 +56,13 @@ namespace Emby.Server.Implementations.Collections
{ {
var subItem = i; var subItem = i;
if (subItem is Episode episode) var episode = subItem as Episode;
{
var series = episode.Series; var series = episode?.Series;
if (series != null && series.HasImage(ImageType.Primary)) if (series != null && series.HasImage(ImageType.Primary))
{ {
return series; return series;
} }
}
if (subItem.HasImage(ImageType.Primary)) if (subItem.HasImage(ImageType.Primary))
{ {
@ -78,6 +87,7 @@ namespace Emby.Server.Implementations.Collections
.ToList(); .ToList();
} }
/// <inheritdoc />
protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{ {
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);

View File

@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -7,6 +5,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -23,16 +22,29 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Collections namespace Emby.Server.Implementations.Collections
{ {
/// <summary>
/// The collection manager.
/// </summary>
public class CollectionManager : ICollectionManager public class CollectionManager : ICollectionManager
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _iLibraryMonitor; private readonly ILibraryMonitor _iLibraryMonitor;
private readonly ILogger _logger; private readonly ILogger<CollectionManager> _logger;
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly ILocalizationManager _localizationManager; private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
/// <summary>
/// Initializes a new instance of the <see cref="CollectionManager"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="appPaths">The application paths.</param>
/// <param name="localizationManager">The localization manager.</param>
/// <param name="fileSystem">The filesystem.</param>
/// <param name="iLibraryMonitor">The library monitor.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="providerManager">The provider manager.</param>
public CollectionManager( public CollectionManager(
ILibraryManager libraryManager, ILibraryManager libraryManager,
IApplicationPaths appPaths, IApplicationPaths appPaths,
@ -45,16 +57,19 @@ namespace Emby.Server.Implementations.Collections
_libraryManager = libraryManager; _libraryManager = libraryManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_iLibraryMonitor = iLibraryMonitor; _iLibraryMonitor = iLibraryMonitor;
_logger = loggerFactory.CreateLogger(nameof(CollectionManager)); _logger = loggerFactory.CreateLogger<CollectionManager>();
_providerManager = providerManager; _providerManager = providerManager;
_localizationManager = localizationManager; _localizationManager = localizationManager;
_appPaths = appPaths; _appPaths = appPaths;
} }
/// <inheritdoc />
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated; public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
/// <inheritdoc />
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection; public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
/// <inheritdoc />
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection; public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
private IEnumerable<Folder> FindFolders(string path) private IEnumerable<Folder> FindFolders(string path)
@ -116,6 +131,7 @@ namespace Emby.Server.Implementations.Collections
: folder.GetChildren(user, true).OfType<BoxSet>(); : folder.GetChildren(user, true).OfType<BoxSet>();
} }
/// <inheritdoc />
public BoxSet CreateCollection(CollectionCreationOptions options) public BoxSet CreateCollection(CollectionCreationOptions options)
{ {
var name = options.Name; var name = options.Name;
@ -180,11 +196,13 @@ namespace Emby.Server.Implementations.Collections
} }
} }
/// <inheritdoc />
public void AddToCollection(Guid collectionId, IEnumerable<string> ids) public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
{ {
AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem))); AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
} }
/// <inheritdoc />
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids) public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
{ {
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem))); AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
@ -247,11 +265,13 @@ namespace Emby.Server.Implementations.Collections
} }
} }
/// <inheritdoc />
public void RemoveFromCollection(Guid collectionId, IEnumerable<string> itemIds) public void RemoveFromCollection(Guid collectionId, IEnumerable<string> itemIds)
{ {
RemoveFromCollection(collectionId, itemIds.Select(i => new Guid(i))); RemoveFromCollection(collectionId, itemIds.Select(i => new Guid(i)));
} }
/// <inheritdoc />
public void RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds) public void RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
{ {
var collection = _libraryManager.GetItemById(collectionId) as BoxSet; var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
@ -305,6 +325,7 @@ namespace Emby.Server.Implementations.Collections
}); });
} }
/// <inheritdoc />
public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user) public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
{ {
var results = new Dictionary<Guid, BaseItem>(); var results = new Dictionary<Guid, BaseItem>();
@ -313,9 +334,7 @@ namespace Emby.Server.Implementations.Collections
foreach (var item in items) foreach (var item in items)
{ {
var grouping = item as ISupportsBoxSetGrouping; if (!(item is ISupportsBoxSetGrouping))
if (grouping == null)
{ {
results[item.Id] = item; results[item.Id] = item;
} }
@ -345,12 +364,21 @@ namespace Emby.Server.Implementations.Collections
} }
} }
/// <summary>
/// The collection manager entry point.
/// </summary>
public sealed class CollectionManagerEntryPoint : IServerEntryPoint public sealed class CollectionManagerEntryPoint : IServerEntryPoint
{ {
private readonly CollectionManager _collectionManager; private readonly CollectionManager _collectionManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILogger _logger; private readonly ILogger<CollectionManagerEntryPoint> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.
/// </summary>
/// <param name="collectionManager">The collection manager.</param>
/// <param name="config">The server configuration manager.</param>
/// <param name="logger">The logger.</param>
public CollectionManagerEntryPoint( public CollectionManagerEntryPoint(
ICollectionManager collectionManager, ICollectionManager collectionManager,
IServerConfigurationManager config, IServerConfigurationManager config,

View File

@ -67,23 +67,22 @@ namespace Emby.Server.Implementations.Configuration
/// <summary> /// <summary>
/// Updates the metadata path. /// Updates the metadata path.
/// </summary> /// </summary>
/// <exception cref="UnauthorizedAccessException">If the directory does not exist, and the caller does not have the required permission to create it.</exception>
/// <exception cref="NotSupportedException">If there is a custom path transcoding path specified, but it is invalid.</exception>
/// <exception cref="IOException">If the directory does not exist, and it also could not be created.</exception>
private void UpdateMetadataPath() private void UpdateMetadataPath()
{ {
if (string.IsNullOrWhiteSpace(Configuration.MetadataPath)) ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = string.IsNullOrWhiteSpace(Configuration.MetadataPath)
{ ? ApplicationPaths.DefaultInternalMetadataPath
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Path.Combine(ApplicationPaths.ProgramDataPath, "metadata"); : Configuration.MetadataPath;
} Directory.CreateDirectory(ApplicationPaths.InternalMetadataPath);
else
{
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Configuration.MetadataPath;
}
} }
/// <summary> /// <summary>
/// Replaces the configuration. /// Replaces the configuration.
/// </summary> /// </summary>
/// <param name="newConfiguration">The new configuration.</param> /// <param name="newConfiguration">The new configuration.</param>
/// <exception cref="DirectoryNotFoundException"></exception> /// <exception cref="DirectoryNotFoundException">If the configuration path doesn't exist.</exception>
public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration) public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
{ {
var newConfig = (ServerConfiguration)newConfiguration; var newConfig = (ServerConfiguration)newConfiguration;
@ -91,7 +90,7 @@ namespace Emby.Server.Implementations.Configuration
ValidateMetadataPath(newConfig); ValidateMetadataPath(newConfig);
ValidateSslCertificate(newConfig); ValidateSslCertificate(newConfig);
ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration> { Argument = newConfig }); ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration>(newConfig));
base.ReplaceConfiguration(newConfiguration); base.ReplaceConfiguration(newConfiguration);
} }
@ -194,12 +193,6 @@ namespace Emby.Server.Implementations.Configuration
changed = true; changed = true;
} }
if (!config.CameraUploadUpgraded)
{
config.CameraUploadUpgraded = true;
changed = true;
}
if (!config.CollectionsUpgraded) if (!config.CollectionsUpgraded)
{ {
config.CollectionsUpgraded = true; config.CollectionsUpgraded = true;

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