Compare commits

..

1 Commits

Author SHA1 Message Date
Vasily
52c399d4d1 Add back taglib-sharp submodule
This fixes v10.1.0 build
2019-01-25 23:46:25 +03:00
821 changed files with 36726 additions and 14877 deletions

View File

@@ -1,192 +0,0 @@
name: $(Date:yyyyMMdd)$(Rev:.r)
variables:
- name: TestProjects
value: 'Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj'
- name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
pr:
autoCancel: true
trigger:
batch: true
jobs:
- job: main_build
displayName: Main Build
pool:
vmImage: ubuntu-16.04
strategy:
matrix:
release:
BuildConfiguration: Release
debug:
BuildConfiguration: Debug
maxParallel: 2
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: false
- task: DotNetCoreCLI@2
displayName: Restore
inputs:
command: restore
projects: '$(RestoreBuildProjects)'
- task: DotNetCoreCLI@2
displayName: Build
inputs:
projects: '$(RestoreBuildProjects)'
arguments: '--configuration $(BuildConfiguration)'
- task: DotNetCoreCLI@2
displayName: Test
inputs:
command: test
projects: '$(RestoreBuildProjects)'
arguments: '--configuration $(BuildConfiguration)'
enabled: false
- task: DotNetCoreCLI@2
displayName: Publish
inputs:
command: publish
publishWebProjects: false
projects: '$(RestoreBuildProjects)'
arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
zipAfterPublish: false
# - task: PublishBuildArtifacts@1
# displayName: 'Publish Artifact'
# inputs:
# PathtoPublish: '$(build.artifactstagingdirectory)'
# artifactName: 'jellyfin-build-$(BuildConfiguration)'
# zipAfterPublish: true
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact Naming'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
artifactName: 'Jellyfin.Naming'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact Controller'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
artifactName: 'Jellyfin.Controller'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact Model'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
artifactName: 'Jellyfin.Model'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact Common'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
artifactName: 'Jellyfin.Common'
- job: dotnet_compat
displayName: Compatibility Check
pool:
vmImage: ubuntu-16.04
dependsOn: main_build
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber']) # Only execute if the pullrequest numer is defined. (So not for normal CI builds)
strategy:
matrix:
Naming:
NugetPackageName: Jellyfin.Naming
AssemblyFileName: Emby.Naming.dll
Controller:
NugetPackageName: Jellyfin.Controller
AssemblyFileName: MediaBrowser.Controller.dll
Model:
NugetPackageName: Jellyfin.Model
AssemblyFileName: MediaBrowser.Model.dll
Common:
NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll
maxParallel: 2
steps:
- checkout: none
- task: DownloadBuildArtifacts@0
displayName: Download the Reference Assembly Build Artifact
inputs:
buildType: 'specific' # Options: current, specific
project: $(System.TeamProjectId) # Required when buildType == Specific
pipeline: $(System.DefinitionId) # Required when buildType == Specific, not sure if this will take a name too
#specificBuildWithTriggering: false # Optional
buildVersionToDownload: 'latestFromBranch' # Required when buildType == Specific# Options: latest, latestFromBranch, specific
allowPartiallySucceededBuilds: false # Optional
branchName: '$(System.PullRequest.TargetBranch)' # Required when buildType == Specific && BuildVersionToDownload == LatestFromBranch
#buildId: # Required when buildType == Specific && BuildVersionToDownload == Specific
#tags: # Optional
downloadType: 'single' # Options: single, specific
artifactName: '$(NugetPackageName)'# Required when downloadType == Single
#itemPattern: '**' # Optional
downloadPath: '$(System.ArtifactsDirectory)/current-artifacts'
#parallelizationLimit: '8' # Optional
- task: CopyFiles@2
displayName: Copy Nuget Assembly to current-release folder
inputs:
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/current-release
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: DownloadBuildArtifacts@0
displayName: Download the New Assembly Build Artifact
inputs:
buildType: 'current' # Options: current, specific
allowPartiallySucceededBuilds: false # Optional
downloadType: 'single' # Options: single, specific
artifactName: '$(NugetPackageName)' # Required when downloadType == Single
downloadPath: '$(System.ArtifactsDirectory)/new-artifacts'
- task: CopyFiles@2
displayName: Copy Artifact Assembly to new-release folder
inputs:
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/new-release
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: DownloadGitHubRelease@0
displayName: Download ABI compatibility check tool from GitHub
inputs:
connection: Jellyfin GitHub
userRepository: EraYaN/dotnet-compatibility
defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
#version: # Required when defaultVersionType != Latest
itemPattern: '**-ci.zip' # Optional
downloadPath: '$(System.ArtifactsDirectory)'
- task: ExtractFiles@1
displayName: Extract ABI compatibility check tool
inputs:
archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
destinationFolder: $(System.ArtifactsDirectory)/tools
cleanDestinationFolder: true
- task: CmdLine@2
displayName: Execute ABI compatibility check tool
inputs:
script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName)'
workingDirectory: $(System.ArtifactsDirectory) # Optional
#failOnStderr: false # Optional

View File

@@ -1,8 +0,0 @@
srpm:
dnf -y install git
git submodule update --init --recursive
cd deployment/fedora-package-x64; \
./create_tarball.sh; \
rpmbuild -bs pkg-src/jellyfin.spec \
--define "_sourcedir $$PWD/pkg-src/" \
--define "_srcrpmdir $(outdir)"

View File

@@ -8,4 +8,3 @@ README.md
deployment/*/dist
deployment/*/pkg-dist
deployment/collect-dist/
ci/

View File

@@ -1,30 +1,12 @@
---
kind: pipeline
name: build-debug
name: build
steps:
- name: submodules
image: docker:git
commands:
- git submodule update --init --recursive
- name: build
image: microsoft/dotnet:2-sdk
commands:
- dotnet publish "Jellyfin.Server" --configuration Debug --output "../ci/ci-debug"
---
kind: pipeline
name: build-release
steps:
- name: submodules
image: docker:git
commands:
- git submodule update --init --recursive
- name: build
image: microsoft/dotnet:2-sdk
commands:
- dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
- dotnet publish --configuration release --output /release Jellyfin.Server

View File

@@ -15,10 +15,6 @@ insert_final_newline = true
end_of_line = lf
max_line_length = null
# YAML indentation
[*.{yml,yaml}]
indent_size = 2
# XML indentation
[*.{csproj,xml}]
indent_size = 2
@@ -59,77 +55,15 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
# Style Definitions (From Roslyn)
# Non-private static fields are PascalCase
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
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_accessibilities = public, protected, internal, protected internal, private protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
# Constants are PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
dotnet_naming_symbols.constants.applicable_kinds = field, local
dotnet_naming_symbols.constants.required_modifiers = const
dotnet_naming_style.constant_style.capitalization = pascal_case
# Static fields are camelCase and start with s_
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_style.static_field_style.capitalization = camel_case
dotnet_naming_style.static_field_style.required_prefix = _
# Instance fields are camelCase and start with _
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
dotnet_naming_symbols.instance_fields.applicable_kinds = field
dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Locals and parameters are camelCase
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
dotnet_naming_style.camel_case_style.capitalization = camel_case
# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_style.local_function_style.capitalization = pascal_case
# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.all_members.applicable_kinds = *
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Style Definitions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const
###############################
# C# Coding Conventions #
###############################

View File

@@ -8,29 +8,28 @@ assignees: ''
---
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
A clear and concise description of what the bug is.
**To Reproduce**
<!-- Steps to reproduce the behavior: -->
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
A clear and concise description of what you expected to happen.
**Logs**
<!-- Please paste any log errors. -->
Please paste any log errors.
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem. -->
If applicable, add screenshots to help explain your problem.
**System (please complete the following information):**
- OS: [e.g. Docker, Debian, Windows]
- Browser: [e.g. Firefox, Chrome, Safari]
- Jellyfin Version: [e.g. 10.0.1]
- Reverse proxy: [e.g. no, nginx, apache, etc.]
**Additional context**
<!-- Add any other context about the problem here. -->
Add any other context about the problem here.

View File

@@ -8,13 +8,13 @@ assignees: ''
---
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Add any other context or screenshots about the feature request here.

View File

@@ -8,7 +8,7 @@ assignees: ''
---
**Describe the feature you'd like**
<!-- A clear and concise description of what you want to happen. -->
A clear and concise description of what you want to happen.
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Add any other context or screenshots about the feature request here.

View File

@@ -1,11 +1,9 @@
<!--
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page.
-->
**Changes**
<!-- Describe your changes here in 1-5 sentences. -->
Describe your changes here in 1-5 sentences.
**Issues**
<!-- Tag any issues that this PR solves here.
ex. Fixes # -->
Tag any issues that this PR solves here.
Fixes #

5
.gitignore vendored
View File

@@ -264,8 +264,3 @@ deployment/**/pkg-dist-tmp/
deployment/collect-dist/
jellyfin_version.ini
ci/
# Doxygen
doc/

4
.gitmodules vendored
View File

@@ -1,4 +1,6 @@
[submodule "ThirdParty/taglib-sharp"]
path = ThirdParty/taglib-sharp
url = https://github.com/mono/taglib-sharp
[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
path = MediaBrowser.WebDashboard/jellyfin-web
url = https://github.com/jellyfin/jellyfin-web.git
branch = .

View File

@@ -92,7 +92,7 @@ namespace BDInfo
}
DirectoryRoot =
_fileSystem.GetDirectoryInfo(Path.GetDirectoryName(DirectoryBDMV.FullName));
_fileSystem.GetDirectoryInfo(_fileSystem.GetDirectoryName(DirectoryBDMV.FullName));
DirectoryBDJO =
GetDirectory("BDJO", DirectoryBDMV, 0);
DirectoryCLIPINF =
@@ -150,7 +150,7 @@ namespace BDInfo
Is3D = true;
}
if (File.Exists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml")))
if (_fileSystem.FileExists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml")))
{
IsDBOX = true;
}
@@ -165,7 +165,7 @@ namespace BDInfo
foreach (var file in files)
{
PlaylistFiles.Add(
file.Name.ToUpper(), new TSPlaylistFile(this, file));
file.Name.ToUpper(), new TSPlaylistFile(this, file, _fileSystem));
}
}
@@ -185,7 +185,7 @@ namespace BDInfo
foreach (var file in files)
{
StreamClipFiles.Add(
file.Name.ToUpper(), new TSStreamClipFile(file));
file.Name.ToUpper(), new TSStreamClipFile(file, _fileSystem));
}
}
@@ -345,7 +345,7 @@ namespace BDInfo
{
return dir;
}
var parentFolder = Path.GetDirectoryName(dir.FullName);
var parentFolder = _fileSystem.GetDirectoryName(dir.FullName);
if (string.IsNullOrEmpty(parentFolder))
{
dir = null;

View File

@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]

View File

@@ -1,4 +1,4 @@
//============================================================================
//============================================================================
// BDInfo - Blu-ray Video and Audio Analysis Tool
// Copyright © 2010 Cinema Squid
//
@@ -28,6 +28,7 @@ namespace BDInfo
{
public class TSPlaylistFile
{
private readonly IFileSystem _fileSystem;
private FileSystemMetadata FileInfo = null;
public string FileType = null;
public bool IsInitialized = false;
@@ -63,19 +64,21 @@ namespace BDInfo
new List<TSGraphicsStream>();
public TSPlaylistFile(BDROM bdrom,
FileSystemMetadata fileInfo)
FileSystemMetadata fileInfo, IFileSystem fileSystem)
{
BDROM = bdrom;
FileInfo = fileInfo;
_fileSystem = fileSystem;
Name = fileInfo.Name.ToUpper();
}
public TSPlaylistFile(BDROM bdrom,
string name,
List<TSStreamClip> clips)
List<TSStreamClip> clips, IFileSystem fileSystem)
{
BDROM = bdrom;
Name = name;
_fileSystem = fileSystem;
IsCustom = true;
foreach (var clip in clips)
{
@@ -228,7 +231,7 @@ namespace BDInfo
Streams.Clear();
StreamClips.Clear();
fileStream = File.OpenRead(FileInfo.FullName);
fileStream = _fileSystem.OpenRead(FileInfo.FullName);
fileReader = new BinaryReader(fileStream);
byte[] data = new byte[fileStream.Length];

View File

@@ -1,4 +1,4 @@
//============================================================================
//============================================================================
// BDInfo - Blu-ray Video and Audio Analysis Tool
// Copyright © 2010 Cinema Squid
//
@@ -28,6 +28,7 @@ namespace BDInfo
{
public class TSStreamClipFile
{
private readonly IFileSystem _fileSystem;
public FileSystemMetadata FileInfo = null;
public string FileType = null;
public bool IsValid = false;
@@ -36,9 +37,10 @@ namespace BDInfo
public Dictionary<ushort, TSStream> Streams =
new Dictionary<ushort, TSStream>();
public TSStreamClipFile(FileSystemMetadata fileInfo)
public TSStreamClipFile(FileSystemMetadata fileInfo, IFileSystem fileSystem)
{
FileInfo = fileInfo;
_fileSystem = fileSystem;
Name = fileInfo.Name.ToUpper();
}
@@ -55,7 +57,7 @@ namespace BDInfo
#endif
Streams.Clear();
fileStream = File.OpenRead(FileInfo.FullName);
fileStream = _fileSystem.OpenRead(FileInfo.FullName);
fileReader = new BinaryReader(fileStream);
byte[] data = new byte[fileStream.Length];

View File

@@ -15,15 +15,6 @@
- [cvium](https://github.com/cvium)
- [wtayl0r](https://github.com/wtayl0r)
- [TtheCreator](https://github.com/Tthecreator)
- [dkanada](https://github.com/dkanada)
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
- [RazeLighter777](https://github.com/RazeLighter777)
- [WillWill56](https://github.com/WillWill56)
- [Liggy](https://github.com/Liggy)
- [fruhnow](https://github.com/fruhnow)
- [Lynxy](https://github.com/Lynxy)
- [fasheng](https://github.com/fasheng)
- [ploughpuff](https://github.com/ploughpuff)
# Emby Contributors

View File

@@ -1,34 +1,37 @@
ARG DOTNET_VERSION=2
# Download ffmpeg first to allow quicker rebuild of other layers
FROM alpine as ffmpeg
ARG FFMPEG_URL=https://www.johnvansickle.com/ffmpeg/old-releases/ffmpeg-4.0.3-64bit-static.tar.xz
RUN wget ${FFMPEG_URL} -O - | tar Jxf - \
&& mkdir ffmpeg-bin \
&& mv ffmpeg*/ffmpeg ffmpeg-bin \
&& mv ffmpeg*/ffprobe ffmpeg-bin
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-x64 /jellyfin"
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
&& dotnet clean \
&& dotnet publish \
--configuration release \
--output /jellyfin \
Jellyfin.Server
FROM jellyfin/ffmpeg as ffmpeg
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime
COPY --from=builder /jellyfin /jellyfin
COPY --from=ffmpeg /ffmpeg-bin/* /usr/bin/
EXPOSE 8096
VOLUME /config /media
# libfontconfig1 is required for Skia
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
libfontconfig1 \
&& apt-get clean autoclean \
&& apt-get autoremove \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
COPY --from=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin
ARG JELLYFIN_WEB_VERSION=10.2.2
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/local/bin/ffmpeg
&& rm -rf /var/lib/{apt,dpkg,cache,log}
ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config

View File

@@ -1,43 +1,24 @@
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.0
FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM alpine as qemu_extract
COPY --from=qemu /usr/bin qemu-arm-static.tar.gz
RUN tar -xzvf qemu-arm-static.tar.gz
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch-arm32v7 as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# TODO Remove or update the sed line when we update dotnet version.
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-arm /jellyfin"
#TODO Remove or update the sed line when we update dotnet version.
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
&& find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; \
&& dotnet clean -maxcpucount:1 \
&& dotnet publish \
-maxcpucount:1 \
--configuration release \
--output /jellyfin \
Jellyfin.Server
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7
COPY --from=qemu_extract qemu-arm-static /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin
ARG JELLYFIN_WEB_VERSION=10.2.2
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/bin/ffmpeg
RUN apt-get update \
&& apt-get install -y ffmpeg
VOLUME /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config

View File

@@ -1,44 +1,33 @@
# Requires binfm_misc registration
# Requires binfm_misc registration for aarch64
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.0
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM alpine as qemu_extract
COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz
RUN tar -xzvf qemu-aarch64-static.tar.gz
COPY --from=qemu /usr/bin qemu_user_static.tgz
RUN tar -xzvf qemu_user_static.tgz
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch-arm64v8 as builder
COPY --from=qemu_extract qemu-* /usr/bin
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# TODO Remove or update the sed line when we update dotnet version.
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin"
#TODO Remove or update the sed line when we update dotnet version.
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
&& find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; \
&& dotnet clean \
&& dotnet publish \
--configuration release \
--output /jellyfin \
Jellyfin.Server
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8
COPY --from=qemu_extract qemu-aarch64-static /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
COPY --from=qemu_extract qemu-* /usr/bin
COPY --from=builder /jellyfin /jellyfin
ARG JELLYFIN_WEB_VERSION=10.2.2
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/bin/ffmpeg
RUN apt-get update \
&& apt-get install -y ffmpeg
VOLUME /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config

2565
Doxyfile

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
namespace DvdLib.Ifo
{
public enum AudioCodec
{
AC3 = 0,
MPEG1 = 2,
MPEG2ext = 3,
LPCM = 4,
DTS = 6,
}
public enum ApplicationMode
{
Unspecified = 0,
Karaoke = 1,
Surround = 2,
}
public class AudioAttributes
{
public readonly AudioCodec Codec;
public readonly bool MultichannelExtensionPresent;
public readonly ApplicationMode Mode;
public readonly byte QuantDRC;
public readonly byte SampleRate;
public readonly byte Channels;
public readonly ushort LanguageCode;
public readonly byte LanguageExtension;
public readonly byte CodeExtension;
}
public class MultiChannelExtension
{
}
}

View File

@@ -26,17 +26,17 @@ namespace DvdLib.Ifo
if (vmgPath == null)
{
foreach (var ifo in allFiles)
{
if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var allIfos = allFiles.Where(i => string.Equals(i.Extension, ".ifo", StringComparison.OrdinalIgnoreCase));
var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
foreach (var ifo in allIfos)
{
var num = ifo.Name.Split('_').ElementAtOrDefault(1);
var numbersRead = new List<ushort>();
if (!string.IsNullOrEmpty(num) && ushort.TryParse(num, out var ifoNumber) && !numbersRead.Contains(ifoNumber))
{
ReadVTS(ifoNumber, ifo.FullName);
numbersRead.Add(ifoNumber);
}
}
}
@@ -76,7 +76,7 @@ namespace DvdLib.Ifo
}
}
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
private void ReadVTS(ushort vtsNum, List<FileSystemMetadata> allFiles)
{
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);

View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace DvdLib.Ifo
{
public class ProgramChainCommandTable
{
public readonly ushort LastByteAddress;
public readonly List<VirtualMachineCommand> PreCommands;
public readonly List<VirtualMachineCommand> PostCommands;
public readonly List<VirtualMachineCommand> CellCommands;
}
public class VirtualMachineCommand
{
public readonly byte[] Command;
}
}

View File

@@ -25,10 +25,13 @@ namespace DvdLib.Ifo
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
private ushort _nextProgramNumber;
public readonly ProgramChain Next;
private ushort _prevProgramNumber;
public readonly ProgramChain Previous;
private ushort _goupProgramNumber;
public readonly ProgramChain Goup; // ?? maybe Group
public ProgramPlaybackMode PlaybackMode { get; private set; }
public uint ProgramCount { get; private set; }
@@ -37,6 +40,7 @@ namespace DvdLib.Ifo
public byte[] Palette { get; private set; } // 16*4 entries
private ushort _commandTableOffset;
public readonly ProgramChainCommandTable CommandTable;
private ushort _programMapOffset;
private ushort _cellPlaybackOffset;

View File

@@ -0,0 +1,46 @@
namespace DvdLib.Ifo
{
public enum VideoCodec
{
MPEG1 = 0,
MPEG2 = 1,
}
public enum VideoFormat
{
NTSC = 0,
PAL = 1,
}
public enum AspectRatio
{
ar4to3 = 0,
ar16to9 = 3
}
public enum FilmMode
{
None = -1,
Camera = 0,
Film = 1,
}
public class VideoAttributes
{
public readonly VideoCodec Codec;
public readonly VideoFormat Format;
public readonly AspectRatio Aspect;
public readonly bool AutomaticPanScan;
public readonly bool AutomaticLetterBox;
public readonly bool Line21CCField1;
public readonly bool Line21CCField2;
public readonly int Width;
public readonly int Height;
public readonly bool Letterboxed;
public readonly FilmMode FilmMode;
public VideoAttributes()
{
}
}
}

View File

@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]

View File

@@ -136,7 +136,7 @@ namespace Emby.Dlna.Api
{
var url = Request.AbsoluteUri;
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress);
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress);
var cacheLength = TimeSpan.FromDays(1);
var cacheKey = Request.RawUrl.GetMD5();
@@ -147,21 +147,21 @@ namespace Emby.Dlna.Api
public object Get(GetContentDirectory request)
{
var xml = ContentDirectory.GetServiceXml();
var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary());
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
public object Get(GetMediaReceiverRegistrar request)
{
var xml = MediaReceiverRegistrar.GetServiceXml();
var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary());
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
public object Get(GetConnnectionManager request)
{
var xml = ConnectionManager.GetServiceXml();
var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary());
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
@@ -193,7 +193,7 @@ namespace Emby.Dlna.Api
return service.ProcessControlRequest(new ControlRequest
{
Headers = Request.Headers,
Headers = Request.Headers.ToDictionary(),
InputXml = requestStream,
TargetServerUuId = id,
RequestedUrl = Request.AbsoluteUri
@@ -236,9 +236,7 @@ namespace Emby.Dlna.Api
public object Get(GetIcon request)
{
var contentType = "image/" + Path.GetExtension(request.Filename)
.TrimStart('.')
.ToLowerInvariant();
var contentType = "image/" + Path.GetExtension(request.Filename).TrimStart('.').ToLower();
var cacheLength = TimeSpan.FromDays(365);
var cacheKey = Request.RawUrl.GetMD5();

View File

@@ -7,7 +7,6 @@ namespace Emby.Dlna.Configuration
public bool EnableServer { get; set; }
public bool EnableDebugLog { get; set; }
public bool BlastAliveMessages { get; set; }
public bool SendOnlyMatchedHost { get; set; }
public int ClientDiscoveryIntervalSeconds { get; set; }
public int BlastAliveMessageIntervalSeconds { get; set; }
public string DefaultUserId { get; set; }
@@ -17,7 +16,6 @@ namespace Emby.Dlna.Configuration
EnablePlayTo = true;
EnableServer = true;
BlastAliveMessages = true;
SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60;
BlastAliveMessageIntervalSeconds = 1800;
}

View File

@@ -1,7 +1,9 @@
using System.Collections.Generic;
using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ConnectionManager
@@ -11,16 +13,18 @@ namespace Emby.Dlna.ConnectionManager
private readonly IDlnaManager _dlna;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
: base(logger, httpClient)
{
_dlna = dlna;
_config = config;
_logger = logger;
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
}
public string GetServiceXml()
public string GetServiceXml(IDictionary<string, string> headers)
{
return new ConnectionManagerXmlBuilder().GetXml();
}
@@ -30,7 +34,7 @@ namespace Emby.Dlna.ConnectionManager
var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile();
return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request);
}
}
}

View File

@@ -4,6 +4,7 @@ using Emby.Dlna.Service;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ConnectionManager
@@ -31,8 +32,7 @@ namespace Emby.Dlna.ConnectionManager
};
}
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
: base(config, logger)
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory)
{
_profile = profile;
}

View File

@@ -11,6 +11,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ContentDirectory
@@ -27,6 +28,7 @@ namespace Emby.Dlna.ContentDirectory
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IUserViewManager _userViewManager;
private readonly IMediaEncoder _mediaEncoder;
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
private readonly ITVSeriesManager _tvSeriesManager;
public ContentDirectory(IDlnaManager dlna,
@@ -36,12 +38,7 @@ namespace Emby.Dlna.ContentDirectory
IServerConfigurationManager config,
IUserManager userManager,
ILogger logger,
IHttpClient httpClient,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
IUserViewManager userViewManager,
IMediaEncoder mediaEncoder,
ITVSeriesManager tvSeriesManager)
IHttpClient httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
: base(logger, httpClient)
{
_dlna = dlna;
@@ -54,6 +51,7 @@ namespace Emby.Dlna.ContentDirectory
_mediaSourceManager = mediaSourceManager;
_userViewManager = userViewManager;
_mediaEncoder = mediaEncoder;
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
_tvSeriesManager = tvSeriesManager;
}
@@ -67,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory
}
}
public string GetServiceXml()
public string GetServiceXml(IDictionary<string, string> headers)
{
return new ContentDirectoryXmlBuilder().GetXml();
}
@@ -78,6 +76,7 @@ namespace Emby.Dlna.ContentDirectory
_dlna.GetDefaultProfile();
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
string accessToken = null;
var user = GetUser(profile);
@@ -86,7 +85,7 @@ namespace Emby.Dlna.ContentDirectory
_libraryManager,
profile,
serverAddress,
null,
accessToken,
_imageProcessor,
_userDataManager,
user,
@@ -96,6 +95,7 @@ namespace Emby.Dlna.ContentDirectory
_mediaSourceManager,
_userViewManager,
_mediaEncoder,
XmlReaderSettingsFactory,
_tvSeriesManager)
.ProcessControlRequest(request);
}

View File

@@ -25,6 +25,7 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ContentDirectory
@@ -50,22 +51,8 @@ namespace Emby.Dlna.ContentDirectory
private readonly DeviceProfile _profile;
public ControlHandler(
ILogger logger,
ILibraryManager libraryManager,
DeviceProfile profile,
string serverAddress,
string accessToken,
IImageProcessor imageProcessor,
IUserDataManager userDataManager,
User user, int systemUpdateId,
IServerConfigurationManager config,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
IUserViewManager userViewManager,
IMediaEncoder mediaEncoder,
ITVSeriesManager tvSeriesManager)
: base(config, logger)
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
: base(config, logger, xmlReaderSettingsFactory)
{
_libraryManager = libraryManager;
_userDataManager = userDataManager;
@@ -76,7 +63,7 @@ namespace Emby.Dlna.ContentDirectory
_profile = profile;
_config = config;
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, mediaEncoder);
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, libraryManager, mediaEncoder);
}
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
@@ -273,7 +260,7 @@ namespace Emby.Dlna.ContentDirectory
if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
{
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
_didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
}
@@ -286,7 +273,7 @@ namespace Emby.Dlna.ContentDirectory
}
else
{
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Length;
@@ -467,7 +454,7 @@ namespace Emby.Dlna.ContentDirectory
User = user,
Recursive = true,
IsMissing = false,
ExcludeItemTypes = new[] { typeof(Book).Name },
ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
IsFolder = isFolder,
MediaTypes = mediaTypes.ToArray(),
DtoOptions = GetDtoOptions()
@@ -496,26 +483,27 @@ namespace Emby.Dlna.ContentDirectory
return GetGenreItems(item, Guid.Empty, user, sort, startIndex, limit);
}
if ((!stubType.HasValue || stubType.Value != StubType.Folder)
&& item is IHasCollectionType collectionFolder)
if (!stubType.HasValue || stubType.Value != StubType.Folder)
{
if (string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
var collectionFolder = item as IHasCollectionType;
if (collectionFolder != null && string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
}
else if (string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
if (collectionFolder != null && string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
}
else if (string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
if (collectionFolder != null && string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
return GetTvFolders(item, user, stubType, sort, startIndex, limit);
}
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
if (collectionFolder != null && string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
return GetFolders(item, user, stubType, sort, startIndex, limit);
}
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
if (collectionFolder != null && string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
}
@@ -536,7 +524,7 @@ namespace Emby.Dlna.ContentDirectory
Limit = limit,
StartIndex = startIndex,
IsVirtualItem = false,
ExcludeItemTypes = new[] { typeof(Book).Name },
ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
IsPlaceHolder = false,
DtoOptions = GetDtoOptions()
};

View File

@@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Http;
namespace Emby.Dlna
{
public class ControlRequest
{
public IHeaderDictionary Headers { get; set; }
public IDictionary<string, string> Headers { get; set; }
public Stream InputXml { get; set; }
@@ -15,7 +15,7 @@ namespace Emby.Dlna
public ControlRequest()
{
Headers = new HeaderDictionary();
Headers = new Dictionary<string, string>();
}
}
}

View File

@@ -43,30 +43,22 @@ namespace Emby.Dlna.Didl
private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IMediaEncoder _mediaEncoder;
public DidlBuilder(
DeviceProfile profile,
User user,
IImageProcessor imageProcessor,
string serverAddress,
string accessToken,
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
ILogger logger,
IMediaEncoder mediaEncoder)
public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
{
_profile = profile;
_user = user;
_imageProcessor = imageProcessor;
_serverAddress = serverAddress;
_accessToken = accessToken;
_userDataManager = userDataManager;
_localization = localization;
_mediaSourceManager = mediaSourceManager;
_logger = logger;
_libraryManager = libraryManager;
_mediaEncoder = mediaEncoder;
_accessToken = accessToken;
_user = user;
}
public static string NormalizeDlnaMediaUrl(string url)
@@ -125,8 +117,7 @@ namespace Emby.Dlna.Didl
}
}
public void WriteItemElement(
DlnaOptions options,
public void WriteItemElement(DlnaOptions options,
XmlWriter writer,
BaseItem item,
User user,
@@ -241,15 +232,12 @@ namespace Emby.Dlna.Didl
AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
}
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken)
.Where(subtitle => subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
.ToList();
foreach (var subtitle in subtitleProfiles)
{
if (subtitle.DeliveryMethod != SubtitleDeliveryMethod.External)
{
continue;
}
var subtitleAdded = AddSubtitleElement(writer, subtitle);
if (subtitleAdded && _profile.EnableSingleSubtitleLimit)
@@ -262,8 +250,7 @@ namespace Emby.Dlna.Didl
private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info)
{
var subtitleProfile = _profile.SubtitleProfiles
.FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase)
&& i.Method == SubtitleDeliveryMethod.External);
.FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) && i.Method == SubtitleDeliveryMethod.External);
if (subtitleProfile == null)
{
@@ -278,7 +265,7 @@ namespace Emby.Dlna.Didl
// <sec:CaptionInfo sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfo>
writer.WriteStartElement("sec", "CaptionInfoEx", null);
writer.WriteAttributeString("sec", "type", null, info.Format.ToLowerInvariant());
writer.WriteAttributeString("sec", "type", null, info.Format.ToLower());
writer.WriteString(info.Url);
writer.WriteFullEndElement();
@@ -295,7 +282,7 @@ namespace Emby.Dlna.Didl
else
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant());
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLower());
writer.WriteAttributeString("protocolInfo", protocolInfo);
writer.WriteString(info.Url);
@@ -400,39 +387,91 @@ namespace Emby.Dlna.Didl
private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context)
{
if (itemStubType.HasValue)
if (itemStubType.HasValue && itemStubType.Value == StubType.Latest)
{
switch (itemStubType.Value)
{
case StubType.Latest: return _localization.GetLocalizedString("Latest");
case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
case StubType.Albums: return _localization.GetLocalizedString("Albums");
case StubType.Artists: return _localization.GetLocalizedString("Artists");
case StubType.Songs: return _localization.GetLocalizedString("Songs");
case StubType.Genres: return _localization.GetLocalizedString("Genres");
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
case StubType.Movies: return _localization.GetLocalizedString("Movies");
case StubType.Collections: return _localization.GetLocalizedString("Collections");
case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
case StubType.Series: return _localization.GetLocalizedString("Shows");
default: break;
}
return _localization.GetLocalizedString("Latest");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.Playlists)
{
return _localization.GetLocalizedString("Playlists");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.AlbumArtists)
{
return _localization.GetLocalizedString("HeaderAlbumArtists");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.Albums)
{
return _localization.GetLocalizedString("Albums");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.Artists)
{
return _localization.GetLocalizedString("Artists");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.Songs)
{
return _localization.GetLocalizedString("Songs");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.Genres)
{
return _localization.GetLocalizedString("Genres");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteAlbums)
{
return _localization.GetLocalizedString("HeaderFavoriteAlbums");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteArtists)
{
return _localization.GetLocalizedString("HeaderFavoriteArtists");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteSongs)
{
return _localization.GetLocalizedString("HeaderFavoriteSongs");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.ContinueWatching)
{
return _localization.GetLocalizedString("HeaderContinueWatching");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.Movies)
{
return _localization.GetLocalizedString("Movies");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.Collections)
{
return _localization.GetLocalizedString("Collections");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.Favorites)
{
return _localization.GetLocalizedString("Favorites");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.NextUp)
{
return _localization.GetLocalizedString("HeaderNextUp");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteSeries)
{
return _localization.GetLocalizedString("HeaderFavoriteShows");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteEpisodes)
{
return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
}
if (itemStubType.HasValue && itemStubType.Value == StubType.Series)
{
return _localization.GetLocalizedString("Shows");
}
if (item is Episode episode && context is Season season)
var episode = item as Episode;
var season = context as Season;
if (episode != null && season != null)
{
// This is a special embedded within a season
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0)
{
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
if (season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
}
}
if (item.IndexNumber.HasValue)
@@ -546,8 +585,10 @@ namespace Emby.Dlna.Didl
public static bool IsIdRoot(string id)
{
if (string.IsNullOrWhiteSpace(id)
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
if (string.IsNullOrWhiteSpace(id) ||
string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
// Samsung sometimes uses 1 as root
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
{
@@ -767,7 +808,7 @@ namespace Emby.Dlna.Didl
{
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre");
}
else if (item is Genre)
else if (item is Genre || item is GameGenre)
{
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre");
}
@@ -803,7 +844,7 @@ namespace Emby.Dlna.Didl
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
// ?? PersonType.Actor;
// AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
// AddValue(writer, "upnp", type.ToLower(), actor.Name, NS_UPNP);
// index++;
@@ -818,9 +859,10 @@ namespace Emby.Dlna.Didl
{
AddCommonFields(item, itemStubType, context, writer, filter);
var hasArtists = item as IHasArtist;
var hasAlbumArtists = item as IHasAlbumArtist;
if (item is IHasArtist hasArtists)
if (hasArtists != null)
{
foreach (var artist in hasArtists.Artists)
{
@@ -890,7 +932,13 @@ namespace Emby.Dlna.Didl
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
{
ImageDownloadInfo imageInfo = GetImageInfo(item);
ImageDownloadInfo imageInfo = null;
// Finally, just use the image from the item
if (imageInfo == null)
{
imageInfo = GetImageInfo(item);
}
if (imageInfo == null)
{
@@ -1046,8 +1094,8 @@ namespace Emby.Dlna.Didl
//{
// var size = _imageProcessor.GetImageSize(imageInfo);
// width = size.Width;
// height = size.Height;
// width = Convert.ToInt32(size.Width);
// height = Convert.ToInt32(size.Height);
//}
//catch
//{
@@ -1070,7 +1118,7 @@ namespace Emby.Dlna.Didl
};
}
private class ImageDownloadInfo
class ImageDownloadInfo
{
internal Guid ItemId;
internal string ImageTag;
@@ -1086,7 +1134,7 @@ namespace Emby.Dlna.Didl
internal ItemImageInfo ItemImageInfo;
}
private class ImageUrlInfo
class ImageUrlInfo
{
internal string Url;
@@ -1105,7 +1153,7 @@ namespace Emby.Dlna.Didl
if (stubType.HasValue)
{
id = stubType.Value.ToString().ToLowerInvariant() + "_" + id;
id = stubType.Value.ToString().ToLower() + "_" + id;
}
return id;
@@ -1120,7 +1168,8 @@ namespace Emby.Dlna.Didl
info.ImageTag,
format,
maxWidth.ToString(CultureInfo.InvariantCulture),
maxHeight.ToString(CultureInfo.InvariantCulture));
maxHeight.ToString(CultureInfo.InvariantCulture)
);
var width = info.Width;
var height = info.Height;
@@ -1129,11 +1178,15 @@ namespace Emby.Dlna.Didl
if (width.HasValue && height.HasValue)
{
var newSize = DrawingUtils.Resize(
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
var newSize = DrawingUtils.Resize(new ImageSize
{
Height = height.Value,
Width = width.Value
width = newSize.Width;
height = newSize.Height;
}, 0, 0, maxWidth, maxHeight);
width = Convert.ToInt32(newSize.Width);
height = Convert.ToInt32(newSize.Height);
var normalizedFormat = format
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);

View File

@@ -2,10 +2,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Emby.Dlna.Profiles;
using Emby.Dlna.Server;
using MediaBrowser.Common.Configuration;
@@ -16,10 +14,9 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Reflection;
using MediaBrowser.Model.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Emby.Dlna
{
@@ -31,7 +28,7 @@ namespace Emby.Dlna
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
private readonly IAssemblyInfo _assemblyInfo;
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
@@ -40,8 +37,7 @@ namespace Emby.Dlna
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILoggerFactory loggerFactory,
IJsonSerializer jsonSerializer,
IServerApplicationHost appHost)
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
@@ -49,13 +45,14 @@ namespace Emby.Dlna
_logger = loggerFactory.CreateLogger("Dlna");
_jsonSerializer = jsonSerializer;
_appHost = appHost;
_assemblyInfo = assemblyInfo;
}
public async Task InitProfilesAsync()
public void InitProfiles()
{
try
{
await ExtractSystemProfilesAsync();
ExtractSystemProfiles();
LoadProfiles();
}
catch (Exception ex)
@@ -205,13 +202,16 @@ namespace Emby.Dlna
}
}
public DeviceProfile GetProfile(IHeaderDictionary headers)
public DeviceProfile GetProfile(IDictionary<string, string> headers)
{
if (headers == null)
{
throw new ArgumentNullException(nameof(headers));
}
// Convert to case insensitive
headers = new Dictionary<string, string>(headers, StringComparer.OrdinalIgnoreCase);
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
if (profile != null)
@@ -227,12 +227,12 @@ namespace Emby.Dlna
return profile;
}
private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo)
private bool IsMatch(IDictionary<string, string> headers, DeviceIdentification profileInfo)
{
return profileInfo.Headers.Any(i => IsMatch(headers, i));
}
private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header)
private bool IsMatch(IDictionary<string, string> headers, HttpHeaderInfo header)
{
// Handle invalid user setup
if (string.IsNullOrEmpty(header.Name))
@@ -240,14 +240,14 @@ namespace Emby.Dlna
return false;
}
if (headers.TryGetValue(header.Name, out StringValues value))
if (headers.TryGetValue(header.Name, out string value))
{
switch (header.Match)
{
case HeaderMatchType.Equals:
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring:
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
//_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
return isMatch;
case HeaderMatchType.Regex:
@@ -300,7 +300,7 @@ namespace Emby.Dlna
profile = ReserializeProfile(tempProfile);
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N");
profile.Id = path.ToLower().GetMD5().ToString("N");
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
@@ -352,48 +352,45 @@ namespace Emby.Dlna
Info = new DeviceProfileInfo
{
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"),
Id = file.FullName.ToLower().GetMD5().ToString("N"),
Name = _fileSystem.GetFileNameWithoutExtension(file),
Type = type
}
};
}
private async Task ExtractSystemProfilesAsync()
private void ExtractSystemProfiles()
{
var namespaceName = GetType().Namespace + ".Profiles.Xml.";
var systemProfilesPath = SystemProfilesPath;
foreach (var name in _assembly.GetManifestResourceNames())
foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType())
.Where(i => i.StartsWith(namespaceName))
.ToList())
{
if (!name.StartsWith(namespaceName))
{
continue;
}
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
var path = Path.Combine(systemProfilesPath, filename);
using (var stream = _assembly.GetManifestResourceStream(name))
using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name))
{
var fileInfo = _fileSystem.GetFileInfo(path);
if (!fileInfo.Exists || fileInfo.Length != stream.Length)
{
Directory.CreateDirectory(systemProfilesPath);
_fileSystem.CreateDirectory(systemProfilesPath);
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
{
await stream.CopyToAsync(fileStream);
stream.CopyTo(fileStream);
}
}
}
}
// Not necessary, but just to make it easy to find
Directory.CreateDirectory(UserProfilesPath);
_fileSystem.CreateDirectory(UserProfilesPath);
}
public void DeleteProfile(string id)
@@ -493,7 +490,7 @@ namespace Emby.Dlna
internal string Path { get; set; }
}
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
public string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId, string serverAddress)
{
var profile = GetProfile(headers) ??
GetDefaultProfile();
@@ -509,12 +506,12 @@ namespace Emby.Dlna
? ImageFormat.Png
: ImageFormat.Jpg;
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant();
var resource = GetType().Namespace + ".Images." + filename.ToLower();
return new ImageStream
{
Format = format,
Stream = _assembly.GetManifestResourceStream(resource)
Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource)
};
}
}

View File

@@ -58,9 +58,4 @@
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
</ItemGroup>
</Project>

View File

@@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace Emby.Dlna
{
public interface IUpnpService
@@ -5,8 +7,9 @@ namespace Emby.Dlna
/// <summary>
/// Gets the content directory XML.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>System.String.</returns>
string GetServiceXml();
string GetServiceXml(IDictionary<string, string> headers);
/// <summary>
/// Processes the control request.

View File

@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna.PlayTo;
@@ -19,10 +20,11 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Threading;
using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
using Rssdp;
using Rssdp.Infrastructure;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Dlna.Main
{
@@ -48,7 +50,9 @@ namespace Emby.Dlna.Main
private SsdpDevicePublisher _Publisher;
private readonly ITimerFactory _timerFactory;
private readonly ISocketFactory _socketFactory;
private readonly IEnvironmentInfo _environmentInfo;
private readonly INetworkManager _networkManager;
private ISsdpCommunicationsServer _communicationsServer;
@@ -74,8 +78,11 @@ namespace Emby.Dlna.Main
IDeviceDiscovery deviceDiscovery,
IMediaEncoder mediaEncoder,
ISocketFactory socketFactory,
ITimerFactory timerFactory,
IEnvironmentInfo environmentInfo,
INetworkManager networkManager,
IUserViewManager userViewManager,
IXmlReaderSettingsFactory xmlReaderSettingsFactory,
ITVSeriesManager tvSeriesManager)
{
_config = config;
@@ -92,11 +99,12 @@ namespace Emby.Dlna.Main
_deviceDiscovery = deviceDiscovery;
_mediaEncoder = mediaEncoder;
_socketFactory = socketFactory;
_timerFactory = timerFactory;
_environmentInfo = environmentInfo;
_networkManager = networkManager;
_logger = loggerFactory.CreateLogger("Dlna");
ContentDirectory = new ContentDirectory.ContentDirectory(
dlnaManager,
ContentDirectory = new ContentDirectory.ContentDirectory(dlnaManager,
userDataManager,
imageProcessor,
libraryManager,
@@ -108,17 +116,18 @@ namespace Emby.Dlna.Main
mediaSourceManager,
userViewManager,
mediaEncoder,
xmlReaderSettingsFactory,
tvSeriesManager);
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient);
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient, xmlReaderSettingsFactory);
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config);
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config, xmlReaderSettingsFactory);
Current = this;
}
public async Task RunAsync()
public void Run()
{
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
((DlnaManager)_dlnaManager).InitProfiles();
ReloadComponents();
@@ -164,10 +173,9 @@ namespace Emby.Dlna.Main
{
if (_communicationsServer == null)
{
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
OperatingSystem.Id == OperatingSystemId.Linux;
var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
_communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
{
IsShared = true
};
@@ -225,7 +233,7 @@ namespace Emby.Dlna.Main
try
{
_Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
_Publisher = new SsdpDevicePublisher(_communicationsServer, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
_Publisher.LogFunction = LogMessage;
_Publisher.SupportPnpRootDevice = false;
@@ -241,21 +249,21 @@ namespace Emby.Dlna.Main
private async Task RegisterServerEndpoints()
{
var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false);
var addresses = (await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false)).ToList();
var udn = CreateUuid(_appHost.SystemId);
foreach (var address in addresses)
{
if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
{
// Not support IPv6 right now
continue;
}
// TODO: Remove this condition on platforms that support it
//if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
//{
// continue;
//}
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address.ToString());
var descriptorUri = "/dlna/" + udn + "/description.xml";
var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri);
@@ -264,8 +272,6 @@ namespace Emby.Dlna.Main
{
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
Location = uri, // Must point to the URL that serves your devices UPnP description document.
Address = address,
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
FriendlyName = "Jellyfin",
Manufacturer = "Jellyfin",
ModelName = "Jellyfin Server",
@@ -347,7 +353,8 @@ namespace Emby.Dlna.Main
_userDataManager,
_localization,
_mediaSourceManager,
_mediaEncoder);
_mediaEncoder,
_timerFactory);
_manager.Start();
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Emby.Dlna.Service;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.MediaReceiverRegistrar
@@ -35,8 +36,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
};
}
public ControlHandler(IServerConfigurationManager config, ILogger logger)
: base(config, logger)
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
{
}
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.MediaReceiverRegistrar
@@ -8,14 +10,16 @@ namespace Emby.Dlna.MediaReceiverRegistrar
public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar
{
private readonly IServerConfigurationManager _config;
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
: base(logger, httpClient)
{
_config = config;
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
}
public string GetServiceXml()
public string GetServiceXml(IDictionary<string, string> headers)
{
return new MediaReceiverRegistrarXmlBuilder().GetXml();
}
@@ -24,7 +28,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
{
return new ControlHandler(
_config,
Logger)
Logger, XmlReaderSettingsFactory)
.ProcessControlRequest(request);
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Emby.Dlna.PlayTo
{
public class CurrentIdEventArgs : EventArgs
{
public string Id { get; set; }
}
}

View File

@@ -4,13 +4,13 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using Emby.Dlna.Common;
using Emby.Dlna.Server;
using Emby.Dlna.Ssdp;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo
@@ -19,7 +19,7 @@ namespace Emby.Dlna.PlayTo
{
#region Fields & Properties
private Timer _timer;
private ITimer _timer;
public DeviceInfo Properties { get; set; }
@@ -40,7 +40,12 @@ namespace Emby.Dlna.PlayTo
public TimeSpan? Duration { get; set; }
public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
private TimeSpan _position = TimeSpan.FromSeconds(0);
public TimeSpan Position
{
get => _position;
set => _position = value;
}
public TRANSPORTSTATE TransportState { get; private set; }
@@ -56,20 +61,24 @@ namespace Emby.Dlna.PlayTo
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
public DateTime DateLastActivity { get; private set; }
public Action OnDeviceUnavailable { get; set; }
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
private readonly ITimerFactory _timerFactory;
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config, ITimerFactory timerFactory)
{
Properties = deviceProperties;
_httpClient = httpClient;
_logger = logger;
_config = config;
_timerFactory = timerFactory;
}
public void Start()
{
_logger.LogDebug("Dlna Device.Start");
_timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
_timer = _timerFactory.Create(TimerCallback, null, 1000, Timeout.Infinite);
}
private DateTime _lastVolumeRefresh;
@@ -110,9 +119,7 @@ namespace Emby.Dlna.PlayTo
lock (_timerLock)
{
if (_disposed)
{
return;
}
_volumeRefreshActive = true;
@@ -129,9 +136,7 @@ namespace Emby.Dlna.PlayTo
lock (_timerLock)
{
if (_disposed)
{
return;
}
_volumeRefreshActive = false;
@@ -139,6 +144,11 @@ namespace Emby.Dlna.PlayTo
}
}
public void OnPlaybackStartedExternally()
{
RestartTimer(true);
}
#region Commanding
public Task VolumeDown(CancellationToken cancellationToken)
@@ -323,9 +333,7 @@ namespace Emby.Dlna.PlayTo
private string CreateDidlMeta(string value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
return DescriptionXmlBuilder.Escape(value);
}
@@ -334,11 +342,10 @@ namespace Emby.Dlna.PlayTo
{
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
if (command == null)
{
return Task.CompletedTask;
}
var service = GetAvTransportService();
if (service == null)
{
throw new InvalidOperationException("Unable to find service");
@@ -362,9 +369,7 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
if (command == null)
{
return;
}
var service = GetAvTransportService();
@@ -380,9 +385,7 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
if (command == null)
{
return;
}
var service = GetAvTransportService();
@@ -402,9 +405,7 @@ namespace Emby.Dlna.PlayTo
private async void TimerCallback(object sender)
{
if (_disposed)
{
return;
}
try
{
@@ -424,6 +425,8 @@ namespace Emby.Dlna.PlayTo
return;
}
DateLastActivity = DateTime.UtcNow;
if (transportState.HasValue)
{
// If we're not playing anything no need to get additional data
@@ -502,9 +505,7 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
if (command == null)
{
return;
}
var service = GetServiceRenderingControl();
@@ -517,17 +518,13 @@ namespace Emby.Dlna.PlayTo
.ConfigureAwait(false);
if (result == null || result.Document == null)
{
return;
}
var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
var volumeValue = volume?.Value;
var volumeValue = volume == null ? null : volume.Value;
if (string.IsNullOrWhiteSpace(volumeValue))
{
return;
}
Volume = int.Parse(volumeValue, UsCulture);
@@ -548,9 +545,7 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
if (command == null)
{
return;
}
var service = GetServiceRenderingControl();
@@ -565,44 +560,39 @@ namespace Emby.Dlna.PlayTo
if (result == null || result.Document == null)
return;
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse")
.Select(i => i.Element("CurrentMute"))
.FirstOrDefault(i => i != null);
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse").Select(i => i.Element("CurrentMute")).FirstOrDefault(i => i != null);
var value = valueNode == null ? null : valueNode.Value;
IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase);
IsMuted = string.Equals(value, "1", StringComparison.OrdinalIgnoreCase);
}
private async Task<TRANSPORTSTATE?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
{
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
if (command == null)
{
return null;
}
var service = GetAvTransportService();
if (service == null)
{
return null;
}
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
.ConfigureAwait(false);
if (result == null || result.Document == null)
{
return null;
}
var transportState =
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
var transportStateValue = transportState == null ? null : transportState.Value;
if (transportStateValue != null
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
if (transportStateValue != null)
{
return state;
if (Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
{
return state;
}
}
return null;
@@ -612,11 +602,10 @@ namespace Emby.Dlna.PlayTo
{
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
if (command == null)
{
return null;
}
var service = GetAvTransportService();
if (service == null)
{
throw new InvalidOperationException("Unable to find service");
@@ -628,9 +617,7 @@ namespace Emby.Dlna.PlayTo
.ConfigureAwait(false);
if (result == null || result.Document == null)
{
return null;
}
var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
@@ -670,13 +657,11 @@ namespace Emby.Dlna.PlayTo
return null;
}
private async Task<(bool, uBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
private async Task<Tuple<bool, uBaseObject>> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
{
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
if (command == null)
{
return (false, null);
}
return new Tuple<bool, uBaseObject>(false, null);
var service = GetAvTransportService();
@@ -691,9 +676,7 @@ namespace Emby.Dlna.PlayTo
.ConfigureAwait(false);
if (result == null || result.Document == null)
{
return (false, null);
}
return new Tuple<bool, uBaseObject>(false, null);
var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
var trackUri = trackUriElem == null ? null : trackUriElem.Value;
@@ -701,8 +684,8 @@ namespace Emby.Dlna.PlayTo
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
var duration = durationElem == null ? null : durationElem.Value;
if (!string.IsNullOrWhiteSpace(duration)
&& !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(duration) &&
!string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
{
Duration = TimeSpan.Parse(duration, UsCulture);
}
@@ -724,75 +707,43 @@ namespace Emby.Dlna.PlayTo
if (track == null)
{
//If track is null, some vendors do this, use GetMediaInfo instead
return (true, null);
return new Tuple<bool, uBaseObject>(true, null);
}
var trackString = (string)track;
if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
{
return (true, null);
return new Tuple<bool, uBaseObject>(true, null);
}
XElement uPnpResponse = null;
XElement uPnpResponse;
// Handle different variations sent back by devices
try
{
uPnpResponse = ParseResponse(trackString);
uPnpResponse = XElement.Parse(trackString);
}
catch (Exception ex)
catch (Exception)
{
_logger.LogError(ex, "Uncaught exception while parsing xml");
}
if (uPnpResponse == null)
{
_logger.LogError("Failed to parse xml: \n {Xml}", trackString);
return (true, null);
// first try to add a root node with a dlna namesapce
try
{
uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>");
uPnpResponse = uPnpResponse.Descendants().First();
}
catch (Exception ex)
{
_logger.LogError(ex, "Unable to parse xml {0}", trackString);
return new Tuple<bool, uBaseObject>(true, null);
}
}
var e = uPnpResponse.Element(uPnpNamespaces.items);
var uTrack = CreateUBaseObject(e, trackUri);
return (true, uTrack);
}
private XElement ParseResponse(string xml)
{
// Handle different variations sent back by devices
try
{
return XElement.Parse(xml);
}
catch (XmlException)
{
}
// first try to add a root node with a dlna namesapce
try
{
return XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + xml + "</data>")
.Descendants()
.First();
}
catch (XmlException)
{
}
// some devices send back invalid xml
try
{
return XElement.Parse(xml.Replace("&", "&amp;"));
}
catch (XmlException)
{
}
return null;
return new Tuple<bool, uBaseObject>(true, uTrack);
}
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
@@ -850,9 +801,11 @@ namespace Emby.Dlna.PlayTo
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
{
if (AvCommands != null)
var avCommands = AvCommands;
if (avCommands != null)
{
return AvCommands;
return avCommands;
}
if (_disposed)
@@ -872,15 +825,18 @@ namespace Emby.Dlna.PlayTo
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
AvCommands = TransportCommands.Create(document);
return AvCommands;
avCommands = TransportCommands.Create(document);
AvCommands = avCommands;
return avCommands;
}
private async Task<TransportCommands> GetRenderingProtocolAsync(CancellationToken cancellationToken)
{
if (RendererCommands != null)
var rendererCommands = RendererCommands;
if (rendererCommands != null)
{
return RendererCommands;
return rendererCommands;
}
if (_disposed)
@@ -889,6 +845,7 @@ namespace Emby.Dlna.PlayTo
}
var avService = GetServiceRenderingControl();
if (avService == null)
{
throw new ArgumentException("Device AvService is null");
@@ -900,8 +857,9 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
RendererCommands = TransportCommands.Create(document);
return RendererCommands;
rendererCommands = TransportCommands.Create(document);
RendererCommands = rendererCommands;
return rendererCommands;
}
private string NormalizeUrl(string baseUrl, string url)
@@ -913,103 +871,85 @@ namespace Emby.Dlna.PlayTo
}
if (!url.Contains("/"))
{
url = "/dmr/" + url;
}
if (!url.StartsWith("/"))
{
url = "/" + url;
}
return baseUrl + url;
}
private TransportCommands AvCommands { get; set; }
private TransportCommands AvCommands
{
get;
set;
}
private TransportCommands RendererCommands { get; set; }
private TransportCommands RendererCommands
{
get;
set;
}
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken)
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, ITimerFactory timerFactory, CancellationToken cancellationToken)
{
var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
var deviceProperties = new DeviceInfo();
var friendlyNames = new List<string>();
var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
if (name != null && !string.IsNullOrWhiteSpace(name.Value))
{
friendlyNames.Add(name.Value);
}
var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
if (room != null && !string.IsNullOrWhiteSpace(room.Value))
{
friendlyNames.Add(room.Value);
}
var deviceProperties = new DeviceInfo()
{
Name = string.Join(" ", friendlyNames),
BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port)
};
deviceProperties.Name = string.Join(" ", friendlyNames.ToArray());
var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
if (model != null)
{
deviceProperties.ModelName = model.Value;
}
var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
if (modelNumber != null)
{
deviceProperties.ModelNumber = modelNumber.Value;
}
var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
if (uuid != null)
{
deviceProperties.UUID = uuid.Value;
}
var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
if (manufacturer != null)
{
deviceProperties.Manufacturer = manufacturer.Value;
}
var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
if (manufacturerUrl != null)
{
deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
}
var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
if (presentationUrl != null)
{
deviceProperties.PresentationUrl = presentationUrl.Value;
}
var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault();
if (modelUrl != null)
{
deviceProperties.ModelUrl = modelUrl.Value;
}
var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault();
if (serialNumber != null)
{
deviceProperties.SerialNumber = serialNumber.Value;
}
var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault();
if (modelDescription != null)
{
deviceProperties.ModelDescription = modelDescription.Value;
}
deviceProperties.BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port);
var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
if (icon != null)
{
deviceProperties.Icon = CreateIcon(icon);
@@ -1018,15 +958,12 @@ namespace Emby.Dlna.PlayTo
foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
{
if (services == null)
{
continue;
}
var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
if (servicesList == null)
{
continue;
}
foreach (var element in servicesList)
{
@@ -1039,7 +976,9 @@ namespace Emby.Dlna.PlayTo
}
}
return new Device(deviceProperties, httpClient, logger, config);
var device = new Device(deviceProperties, httpClient, logger, config, timerFactory);
return device;
}
#endregion
@@ -1126,73 +1065,75 @@ namespace Emby.Dlna.PlayTo
private void OnPlaybackStart(uBaseObject mediaInfo)
{
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
if (PlaybackStart != null)
{
return;
PlaybackStart.Invoke(this, new PlaybackStartEventArgs
{
MediaInfo = mediaInfo
});
}
PlaybackStart?.Invoke(this, new PlaybackStartEventArgs
{
MediaInfo = mediaInfo
});
}
private void OnPlaybackProgress(uBaseObject mediaInfo)
{
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
var mediaUrl = mediaInfo.Url;
if (string.IsNullOrWhiteSpace(mediaUrl))
{
return;
}
PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs
if (PlaybackProgress != null)
{
MediaInfo = mediaInfo
});
PlaybackProgress.Invoke(this, new PlaybackProgressEventArgs
{
MediaInfo = mediaInfo
});
}
}
private void OnPlaybackStop(uBaseObject mediaInfo)
{
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
if (PlaybackStopped != null)
{
MediaInfo = mediaInfo
});
PlaybackStopped.Invoke(this, new PlaybackStoppedEventArgs
{
MediaInfo = mediaInfo
});
}
}
private void OnMediaChanged(uBaseObject old, uBaseObject newMedia)
{
MediaChanged?.Invoke(this, new MediaChangedEventArgs
if (MediaChanged != null)
{
OldMediaInfo = old,
NewMediaInfo = newMedia
});
MediaChanged.Invoke(this, new MediaChangedEventArgs
{
OldMediaInfo = old,
NewMediaInfo = newMedia
});
}
}
#region IDisposable
bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
if (!_disposed)
{
_disposed = true;
DisposeTimer();
}
}
protected virtual void Dispose(bool disposing)
private void DisposeTimer()
{
if (_disposed)
if (_timer != null)
{
return;
_timer.Dispose();
_timer = null;
}
if (disposing)
{
_timer?.Dispose();
}
_timer = null;
Properties = null;
_disposed = true;
}
#endregion

View File

@@ -6,7 +6,6 @@ using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna.Didl;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
@@ -18,8 +17,8 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo
@@ -43,43 +42,30 @@ namespace Emby.Dlna.PlayTo
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly string _serverAddress;
private readonly string _accessToken;
private readonly DateTime _creationTime;
public bool IsSessionActive => !_disposed && _device != null;
public bool SupportsMediaControl => IsSessionActive;
public PlayToController(
SessionInfo session,
ISessionManager sessionManager,
ILibraryManager libraryManager,
ILogger logger,
IDlnaManager dlnaManager,
IUserManager userManager,
IImageProcessor imageProcessor,
string serverAddress,
string accessToken,
IDeviceDiscovery deviceDiscovery,
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
IConfigurationManager config,
IMediaEncoder mediaEncoder)
public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder)
{
_session = session;
_sessionManager = sessionManager;
_libraryManager = libraryManager;
_logger = logger;
_dlnaManager = dlnaManager;
_userManager = userManager;
_imageProcessor = imageProcessor;
_serverAddress = serverAddress;
_accessToken = accessToken;
_deviceDiscovery = deviceDiscovery;
_userDataManager = userDataManager;
_localization = localization;
_mediaSourceManager = mediaSourceManager;
_config = config;
_mediaEncoder = mediaEncoder;
_accessToken = accessToken;
_logger = logger;
_creationTime = DateTime.UtcNow;
}
public void Init(Device device)
@@ -102,10 +88,9 @@ namespace Emby.Dlna.PlayTo
{
_sessionManager.ReportSessionEnded(_session.Id);
}
catch (Exception ex)
catch
{
// Could throw if the session is already gone
_logger.LogError(ex, "Error reporting the end of session {Id}", _session.Id);
}
}
@@ -113,14 +98,20 @@ namespace Emby.Dlna.PlayTo
{
var info = e.Argument;
if (!_disposed
&& info.Headers.TryGetValue("USN", out string usn)
&& usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1
&& (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1
|| (info.Headers.TryGetValue("NT", out string nt)
&& nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)))
info.Headers.TryGetValue("NTS", out string nts);
if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
!_disposed)
{
OnDeviceUnavailable();
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)
{
OnDeviceUnavailable();
}
}
}
@@ -383,7 +374,9 @@ namespace Emby.Dlna.PlayTo
return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None);
case PlaystateCommand.Seek:
return Seek(command.SeekPositionTicks ?? 0);
{
return Seek(command.SeekPositionTicks ?? 0);
}
case PlaystateCommand.NextTrack:
return SetPlaylistIndex(_currentPlaylistIndex + 1);
@@ -449,7 +442,8 @@ namespace Emby.Dlna.PlayTo
var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
_dlnaManager.GetDefaultProfile();
var mediaSources = item is IHasMediaSources
var hasMediaSources = item as IHasMediaSources;
var mediaSources = hasMediaSources != null
? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
: new List<MediaSourceInfo>();
@@ -458,7 +452,7 @@ namespace Emby.Dlna.PlayTo
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder)
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager, _mediaEncoder)
.GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
playlistItem.Didl = itemXml;
@@ -607,34 +601,22 @@ namespace Emby.Dlna.PlayTo
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
if (!_disposed)
{
return;
}
_disposed = true;
_device.PlaybackStart -= _device_PlaybackStart;
_device.PlaybackProgress -= _device_PlaybackProgress;
_device.PlaybackStopped -= _device_PlaybackStopped;
_device.MediaChanged -= _device_MediaChanged;
//_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
_device.OnDeviceUnavailable = null;
if (disposing)
{
_device.Dispose();
}
_device.PlaybackStart -= _device_PlaybackStart;
_device.PlaybackProgress -= _device_PlaybackProgress;
_device.PlaybackStopped -= _device_PlaybackStopped;
_device.MediaChanged -= _device_MediaChanged;
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
_device.OnDeviceUnavailable = null;
_device = null;
_disposed = true;
}
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{
@@ -855,13 +837,13 @@ namespace Emby.Dlna.PlayTo
if (index == -1) return request;
var query = url.Substring(index + 1);
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
QueryParamCollection values = MyHttpUtility.ParseQueryString(query);
request.DeviceProfileId = values.GetValueOrDefault("DeviceProfileId");
request.DeviceId = values.GetValueOrDefault("DeviceId");
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
request.DeviceProfileId = values.Get("DeviceProfileId");
request.DeviceId = values.Get("DeviceId");
request.MediaSourceId = values.Get("MediaSourceId");
request.LiveStreamId = values.Get("LiveStreamId");
request.IsDirectStream = string.Equals("true", values.Get("Static"), StringComparison.OrdinalIgnoreCase);
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
@@ -875,9 +857,9 @@ namespace Emby.Dlna.PlayTo
}
}
private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
private static int? GetIntValue(QueryParamCollection values, string name)
{
var value = values.GetValueOrDefault(name);
var value = values.Get(name);
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
@@ -887,9 +869,9 @@ namespace Emby.Dlna.PlayTo
return null;
}
private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
private static long GetLongValue(QueryParamCollection values, string name)
{
var value = values.GetValueOrDefault(name);
var value = values.Get(name);
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{

View File

@@ -16,6 +16,7 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo
@@ -38,12 +39,13 @@ namespace Emby.Dlna.PlayTo
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly ITimerFactory _timerFactory;
private bool _disposed;
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory)
{
_logger = logger;
_sessionManager = sessionManager;
@@ -59,6 +61,7 @@ namespace Emby.Dlna.PlayTo
_localization = localization;
_mediaSourceManager = mediaSourceManager;
_mediaEncoder = mediaEncoder;
_timerFactory = timerFactory;
}
public void Start()
@@ -89,6 +92,11 @@ namespace Emby.Dlna.PlayTo
return;
}
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
{
return;
}
var cancellationToken = _disposeCancellationTokenSource.Token;
await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -100,11 +108,6 @@ namespace Emby.Dlna.PlayTo
return;
}
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
{
return;
}
await AddDevice(info, location, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
@@ -159,15 +162,17 @@ namespace Emby.Dlna.PlayTo
uuid = location.GetMD5().ToString("N");
}
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null);
string deviceName = null;
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, deviceName, uri.OriginalString, null);
var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
if (controller == null)
{
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, cancellationToken).ConfigureAwait(false);
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, _timerFactory, cancellationToken).ConfigureAwait(false);
string deviceName = device.Properties.Name;
deviceName = device.Properties.Name;
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
@@ -181,6 +186,8 @@ namespace Emby.Dlna.PlayTo
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
}
string accessToken = null;
controller = new PlayToController(sessionInfo,
_sessionManager,
_libraryManager,
@@ -189,7 +196,7 @@ namespace Emby.Dlna.PlayTo
_userManager,
_imageProcessor,
serverAddress,
null,
accessToken,
_deviceDiscovery,
_userDataManager,
_localization,

View File

@@ -9,6 +9,8 @@ namespace Emby.Dlna.PlayTo
{
public class PlaylistItemFactory
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public PlaylistItem Create(Photo item, DeviceProfile profile)
{
var playlistItem = new PlaylistItem

View File

@@ -107,18 +107,12 @@ namespace Emby.Dlna.PlayTo
foreach (var arg in action.ArgumentList)
{
if (arg.Direction == "out")
{
continue;
}
if (arg.Name == "InstanceID")
{
stateString += BuildArgumentXml(arg, "0");
}
else
{
stateString += BuildArgumentXml(arg, null);
}
}
return string.Format(CommandBase, action.Name, xmlNamespace, stateString);
@@ -131,18 +125,11 @@ namespace Emby.Dlna.PlayTo
foreach (var arg in action.ArgumentList)
{
if (arg.Direction == "out")
{
continue;
}
if (arg.Name == "InstanceID")
{
stateString += BuildArgumentXml(arg, "0");
}
else
{
stateString += BuildArgumentXml(arg, value.ToString(), commandParameter);
}
}
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
@@ -155,17 +142,11 @@ namespace Emby.Dlna.PlayTo
foreach (var arg in action.ArgumentList)
{
if (arg.Name == "InstanceID")
{
stateString += BuildArgumentXml(arg, "0");
}
else if (dictionary.ContainsKey(arg.Name))
{
stateString += BuildArgumentXml(arg, dictionary[arg.Name]);
}
else
{
stateString += BuildArgumentXml(arg, value.ToString());
}
}
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);

View File

@@ -0,0 +1,9 @@
using System;
namespace Emby.Dlna.PlayTo
{
public class TransportStateEventArgs : EventArgs
{
public TRANSPORTSTATE State { get; set; }
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Emby.Dlna.PlayTo
{
public class uParser
{
public static IList<uBaseObject> ParseBrowseXml(XDocument doc)
{
if (doc == null)
{
throw new ArgumentException("doc");
}
var list = new List<uBaseObject>();
var document = doc.Document;
if (document == null)
return list;
var item = (from result in document.Descendants("Result") select result).FirstOrDefault();
if (item == null)
return list;
var uPnpResponse = XElement.Parse((string)item);
var uObjects = from container in uPnpResponse.Elements(uPnpNamespaces.containers)
select new uParserObject { Element = container };
var uObjects2 = from container in uPnpResponse.Elements(uPnpNamespaces.items)
select new uParserObject { Element = container };
list.AddRange(uObjects.Concat(uObjects2).Select(CreateObjectFromXML).Where(uObject => uObject != null));
return list;
}
public static uBaseObject CreateObjectFromXML(uParserObject uItem)
{
return UpnpContainer.Create(uItem.Element);
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Xml.Linq;
namespace Emby.Dlna.PlayTo
{
public class uParserObject
{
public XElement Element { get; set; }
}
}

View File

@@ -8,8 +8,8 @@ using System.Resources;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]

View File

@@ -107,19 +107,19 @@ namespace Emby.Dlna.Server
'&'
};
private static readonly string[] s_escapeStringPairs = new[]
{
"<",
"&lt;",
">",
"&gt;",
"\"",
"&quot;",
"'",
"&apos;",
"&",
"&amp;"
};
private static readonly string[] s_escapeStringPairs = new string[]
{
"<",
"&lt;",
">",
"&gt;",
"\"",
"&quot;",
"'",
"&apos;",
"&",
"&amp;"
};
private static string GetEscapeSequence(char c)
{
@@ -133,7 +133,7 @@ namespace Emby.Dlna.Server
return result;
}
}
return c.ToString(CultureInfo.InvariantCulture);
return c.ToString();
}
/// <summary>Replaces invalid XML characters in a string with their valid XML equivalent.</summary>
@@ -145,7 +145,6 @@ namespace Emby.Dlna.Server
{
return null;
}
StringBuilder stringBuilder = null;
int length = str.Length;
int num = 0;
@@ -231,9 +230,9 @@ namespace Emby.Dlna.Server
var serverName = new string(characters);
var name = _profile.FriendlyName?.Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase);
var name = (_profile.FriendlyName ?? string.Empty).Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase);
return name ?? string.Empty;
return name;
}
private void AppendIconList(StringBuilder builder)
@@ -296,62 +295,65 @@ namespace Emby.Dlna.Server
}
private IEnumerable<DeviceIcon> GetIcons()
=> new[]
{
var list = new List<DeviceIcon>();
list.Add(new DeviceIcon
{
new DeviceIcon
{
MimeType = "image/png",
Depth = "24",
Width = 240,
Height = 240,
Url = "icons/logo240.png"
},
MimeType = "image/png",
Depth = "24",
Width = 240,
Height = 240,
Url = "icons/logo240.png"
});
new DeviceIcon
{
MimeType = "image/jpeg",
Depth = "24",
Width = 240,
Height = 240,
Url = "icons/logo240.jpg"
},
list.Add(new DeviceIcon
{
MimeType = "image/jpeg",
Depth = "24",
Width = 240,
Height = 240,
Url = "icons/logo240.jpg"
});
new DeviceIcon
{
MimeType = "image/png",
Depth = "24",
Width = 120,
Height = 120,
Url = "icons/logo120.png"
},
list.Add(new DeviceIcon
{
MimeType = "image/png",
Depth = "24",
Width = 120,
Height = 120,
Url = "icons/logo120.png"
});
new DeviceIcon
{
MimeType = "image/jpeg",
Depth = "24",
Width = 120,
Height = 120,
Url = "icons/logo120.jpg"
},
list.Add(new DeviceIcon
{
MimeType = "image/jpeg",
Depth = "24",
Width = 120,
Height = 120,
Url = "icons/logo120.jpg"
});
new DeviceIcon
{
MimeType = "image/png",
Depth = "24",
Width = 48,
Height = 48,
Url = "icons/logo48.png"
},
list.Add(new DeviceIcon
{
MimeType = "image/png",
Depth = "24",
Width = 48,
Height = 48,
Url = "icons/logo48.png"
});
new DeviceIcon
{
MimeType = "image/jpeg",
Depth = "24",
Width = 48,
Height = 48,
Url = "icons/logo48.jpg"
}
};
list.Add(new DeviceIcon
{
MimeType = "image/jpeg",
Depth = "24",
Width = 48,
Height = 48,
Url = "icons/logo48.jpg"
});
return list;
}
private IEnumerable<DeviceService> GetServices()
{

View File

@@ -7,6 +7,7 @@ using System.Xml;
using Emby.Dlna.Didl;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.Service
@@ -17,11 +18,13 @@ namespace Emby.Dlna.Service
protected readonly IServerConfigurationManager Config;
protected readonly ILogger _logger;
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
{
Config = config;
_logger = logger;
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
}
public ControlResponse ProcessControlRequest(ControlRequest request)
@@ -58,13 +61,11 @@ namespace Emby.Dlna.Service
using (var streamReader = new StreamReader(request.InputXml))
{
var readerSettings = new XmlReaderSettings()
{
ValidationType = ValidationType.None,
CheckCharacters = false,
IgnoreProcessingInstructions = true,
IgnoreComments = true
};
var readerSettings = XmlReaderSettingsFactory.Create(false);
readerSettings.CheckCharacters = false;
readerSettings.IgnoreProcessingInstructions = true;
readerSettings.IgnoreComments = true;
using (var reader = XmlReader.Create(streamReader, readerSettings))
{

View File

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging;
using Rssdp;
using Rssdp.Infrastructure;
@@ -47,17 +48,20 @@ namespace Emby.Dlna.Ssdp
private SsdpDeviceLocator _deviceLocator;
private readonly ITimerFactory _timerFactory;
private readonly ISocketFactory _socketFactory;
private ISsdpCommunicationsServer _commsServer;
public DeviceDiscovery(
ILoggerFactory loggerFactory,
IServerConfigurationManager config,
ISocketFactory socketFactory)
ISocketFactory socketFactory,
ITimerFactory timerFactory)
{
_logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery));
_config = config;
_socketFactory = socketFactory;
_timerFactory = timerFactory;
}
// Call this method from somewhere in your code to start the search.
@@ -74,7 +78,7 @@ namespace Emby.Dlna.Ssdp
{
if (_listenerCount > 0 && _deviceLocator == null)
{
_deviceLocator = new SsdpDeviceLocator(_commsServer);
_deviceLocator = new SsdpDeviceLocator(_commsServer, _timerFactory);
// (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

View File

@@ -5,6 +5,12 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" />
<PackageReference Include="Jellyfin.SkiaSharp.NativeAssets.LinuxArm" Version="1.68.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />

View File

@@ -18,6 +18,7 @@ using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
using SkiaSharp;
namespace Emby.Drawing
{
@@ -65,7 +66,7 @@ namespace Emby.Drawing
_appPaths = appPaths;
ImageEnhancers = Array.Empty<IImageEnhancer>();
ImageHelper.ImageProcessor = this;
}
@@ -83,8 +84,8 @@ namespace Emby.Drawing
}
}
public IReadOnlyCollection<string> SupportedInputFormats =>
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
public string[] SupportedInputFormats =>
new string[]
{
"tiff",
"tif",
@@ -137,14 +138,14 @@ namespace Emby.Drawing
}
}
public IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats()
=> _imageEncoder.SupportedOutputFormats;
private static readonly HashSet<string> TransparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
public ImageFormat[] GetSupportedImageOutputFormats()
{
return _imageEncoder.SupportedOutputFormats;
}
private static readonly string[] TransparentImageTypes = new string[] { ".png", ".webp", ".gif" };
public bool SupportsTransparency(string path)
=> TransparentImageTypes.Contains(Path.GetExtension(path));
=> TransparentImageTypes.Contains(Path.GetExtension(path).ToLower());
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
{
@@ -167,10 +168,10 @@ namespace Emby.Drawing
string originalImagePath = originalImage.Path;
DateTime dateModified = originalImage.DateModified;
ImageDimensions? originalImageSize = null;
ImageSize? originalImageSize = null;
if (originalImage.Width > 0 && originalImage.Height > 0)
{
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
originalImageSize = new ImageSize(originalImage.Width, originalImage.Height);
}
if (!_imageEncoder.SupportsImageEncoding)
@@ -180,12 +181,6 @@ namespace Emby.Drawing
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
originalImagePath = supportedImageInfo.path;
if (!File.Exists(originalImagePath))
{
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
dateModified = supportedImageInfo.dateModified;
bool requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath));
@@ -236,7 +231,7 @@ namespace Emby.Drawing
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
ImageDimensions newSize = ImageHelper.GetNewImageSize(options, null);
ImageSize newSize = ImageHelper.GetNewImageSize(options, null);
int quality = options.Quality;
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
@@ -250,7 +245,7 @@ namespace Emby.Drawing
try
{
if (!File.Exists(cacheFilePath))
if (!_fileSystem.FileExists(cacheFilePath))
{
if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath))
{
@@ -267,10 +262,21 @@ namespace Emby.Drawing
return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
}
catch (ArgumentOutOfRangeException ex)
{
// Decoder failed to decode it
#if DEBUG
_logger.LogError(ex, "Error encoding image");
#endif
// Just spit out the original file if all the options are default
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
catch (Exception ex)
{
// If it fails for whatever reason, return the original image
_logger.LogError(ex, "Error encoding image");
// Just spit out the original file if all the options are default
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
finally
@@ -328,7 +334,7 @@ namespace Emby.Drawing
/// <summary>
/// Gets the cache file path based on a set of parameters
/// </summary>
private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
{
var filename = originalPath
+ "width=" + outputSize.Width
@@ -369,28 +375,29 @@ namespace Emby.Drawing
filename += "v=" + Version;
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant());
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower());
}
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
=> GetImageDimensions(item, info, true);
public ImageSize GetImageSize(BaseItem item, ItemImageInfo info)
=> GetImageSize(item, info, true);
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
public ImageSize GetImageSize(BaseItem item, ItemImageInfo info, bool updateItem)
{
int width = info.Width;
int height = info.Height;
if (height > 0 && width > 0)
{
return new ImageDimensions(width, height);
return new ImageSize(width, height);
}
string path = info.Path;
_logger.LogInformation("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
ImageDimensions size = GetImageDimensions(path);
info.Width = size.Width;
info.Height = size.Height;
var size = GetImageSize(path);
info.Height = Convert.ToInt32(size.Height);
info.Width = Convert.ToInt32(size.Width);
if (updateItem)
{
@@ -403,8 +410,20 @@ namespace Emby.Drawing
/// <summary>
/// Gets the size of the image.
/// </summary>
public ImageDimensions GetImageDimensions(string path)
=> _imageEncoder.GetImageSize(path);
public ImageSize GetImageSize(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
}
using (var s = new SKFileStream(path))
using (var codec = SKCodec.Create(s))
{
var info = codec.Info;
return new ImageSize(info.Width, info.Height);
}
}
/// <summary>
/// Gets the image cache tag.
@@ -476,7 +495,7 @@ namespace Emby.Drawing
return (originalImagePath, dateModified);
}
if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat, StringComparer.OrdinalIgnoreCase))
{
try
{
@@ -621,12 +640,12 @@ namespace Emby.Drawing
try
{
// Check again in case of contention
if (File.Exists(enhancedImagePath))
if (_fileSystem.FileExists(enhancedImagePath))
{
return (enhancedImagePath, treatmentRequiresTransparency);
}
Directory.CreateDirectory(Path.GetDirectoryName(enhancedImagePath));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(enhancedImagePath));
await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false);

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Drawing;
@@ -7,11 +6,15 @@ namespace Emby.Drawing
{
public class NullImageEncoder : IImageEncoder
{
public IReadOnlyCollection<string> SupportedInputFormats
=> new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" };
public string[] SupportedInputFormats =>
new[]
{
"png",
"jpeg",
"jpg"
};
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
=> new HashSet<ImageFormat>() { ImageFormat.Jpg, ImageFormat.Png };
public ImageFormat[] SupportedOutputFormats => new[] { ImageFormat.Jpg, ImageFormat.Png };
public void CropWhiteSpace(string inputPath, string outputPath)
{
@@ -34,7 +37,7 @@ namespace Emby.Drawing
public bool SupportsImageEncoding => false;
public ImageDimensions GetImageSize(string path)
public ImageSize GetImageSize(string path)
{
throw new NotImplementedException();
}

View File

@@ -2,13 +2,13 @@ using System;
using MediaBrowser.Model.Drawing;
using SkiaSharp;
namespace Jellyfin.Drawing.Skia
namespace Emby.Drawing
{
public static class PercentPlayedDrawer
{
private const int IndicatorHeight = 8;
public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent)
public static void Process(SKCanvas canvas, ImageSize imageSize, double percent)
{
using (var paint = new SKPaint())
{
@@ -23,8 +23,8 @@ namespace Jellyfin.Drawing.Skia
foregroundWidth *= percent;
foregroundWidth /= 100;
paint.Color = SKColor.Parse("#FF00A4DC");
canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(foregroundWidth), (float)endY), paint);
paint.Color = SKColor.Parse("#FF52B54B");
canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(Math.Round(foregroundWidth)), (float)endY), paint);
}
}
}

View File

@@ -1,19 +1,19 @@
using MediaBrowser.Model.Drawing;
using SkiaSharp;
namespace Jellyfin.Drawing.Skia
namespace Emby.Drawing
{
public static class PlayedIndicatorDrawer
{
private const int OffsetFromTopRightCorner = 38;
public static void DrawPlayedIndicator(SKCanvas canvas, ImageDimensions imageSize)
public static void DrawPlayedIndicator(SKCanvas canvas, ImageSize imageSize)
{
var x = imageSize.Width - OffsetFromTopRightCorner;
using (var paint = new SKPaint())
{
paint.Color = SKColor.Parse("#CC00A4DC");
paint.Color = SKColor.Parse("#CC52B54B");
paint.Style = SKPaintStyle.Fill;
canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint);
}

View File

@@ -8,8 +8,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -13,7 +12,7 @@ using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
using SkiaSharp;
namespace Jellyfin.Drawing.Skia
namespace Emby.Drawing
{
public class SkiaEncoder : IImageEncoder
{
@@ -36,8 +35,8 @@ namespace Jellyfin.Drawing.Skia
LogVersion();
}
public IReadOnlyCollection<string> SupportedInputFormats =>
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
public string[] SupportedInputFormats =>
new[]
{
"jpeg",
"jpg",
@@ -63,8 +62,7 @@ namespace Jellyfin.Drawing.Skia
"arw"
};
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
=> new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
public ImageFormat[] SupportedOutputFormats => new[] { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
private void LogVersion()
{
@@ -74,11 +72,16 @@ namespace Jellyfin.Drawing.Skia
_logger.LogInformation("SkiaSharp version: " + GetVersion());
}
public static Version GetVersion()
=> typeof(SKBitmap).GetTypeInfo().Assembly.GetName().Version;
public static string GetVersion()
{
return typeof(SKBitmap).GetTypeInfo().Assembly.GetName().Version.ToString();
}
private static bool IsTransparent(SKColor color)
=> (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0;
{
return (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0;
}
public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat)
{
@@ -127,51 +130,33 @@ namespace Jellyfin.Drawing.Skia
for (int row = 0; row < bitmap.Height; ++row)
{
if (IsTransparentRow(bitmap, row))
{
topmost = row + 1;
}
else
{
break;
}
else break;
}
int bottommost = bitmap.Height;
for (int row = bitmap.Height - 1; row >= 0; --row)
{
if (IsTransparentRow(bitmap, row))
{
bottommost = row;
}
else
{
break;
}
else break;
}
int leftmost = 0, rightmost = bitmap.Width;
for (int col = 0; col < bitmap.Width; ++col)
{
if (IsTransparentColumn(bitmap, col))
{
leftmost = col + 1;
}
else
{
break;
}
}
for (int col = bitmap.Width - 1; col >= 0; --col)
{
if (IsTransparentColumn(bitmap, col))
{
rightmost = col;
}
else
{
break;
}
}
var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost);
@@ -183,19 +168,25 @@ namespace Jellyfin.Drawing.Skia
}
}
public ImageDimensions GetImageSize(string path)
public ImageSize GetImageSize(string path)
{
using (var s = new SKFileStream(path))
using (var codec = SKCodec.Create(s))
{
var info = codec.Info;
return new ImageDimensions(info.Width, info.Height);
return new ImageSize
{
Width = info.Width,
Height = info.Height
};
}
}
private static bool HasDiacritics(string text)
=> !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
{
return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
}
private static bool RequiresSpecialCharacterHack(string path)
{
@@ -221,8 +212,8 @@ namespace Jellyfin.Drawing.Skia
var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + Path.GetExtension(path) ?? string.Empty);
Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
File.Copy(path, tempPath, true);
fileSystem.CreateDirectory(fileSystem.GetDirectoryName(tempPath));
fileSystem.CopyFile(path, tempPath, true);
return tempPath;
}
@@ -255,17 +246,15 @@ namespace Jellyfin.Drawing.Skia
}
}
private static readonly HashSet<string> TransparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private static string[] TransparentImageTypes = new string[] { ".png", ".gif", ".webp" };
internal static SKBitmap Decode(string path, bool forceCleanBitmap, IFileSystem fileSystem, ImageOrientation? orientation, out SKEncodedOrigin origin)
{
if (!File.Exists(path))
if (!fileSystem.FileExists(path))
{
throw new FileNotFoundException("File not found", path);
}
var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path));
var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty);
if (requiresTransparencyHack || forceCleanBitmap)
{
@@ -281,10 +270,17 @@ namespace Jellyfin.Drawing.Skia
// create the bitmap
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
// decode
_ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
if (bitmap != null)
{
// decode
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
origin = codec.EncodedOrigin;
origin = codec.EncodedOrigin;
}
else
{
origin = GetSKEncodedOrigin(orientation);
}
return bitmap;
}
@@ -331,11 +327,14 @@ namespace Jellyfin.Drawing.Skia
{
var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out origin);
if (bitmap != null && origin != SKEncodedOrigin.TopLeft)
if (bitmap != null)
{
using (bitmap)
if (origin != SKEncodedOrigin.TopLeft)
{
return OrientImage(bitmap, origin);
using (bitmap)
{
return OrientImage(bitmap, origin);
}
}
}
@@ -359,6 +358,7 @@ namespace Jellyfin.Drawing.Skia
switch (origin)
{
case SKEncodedOrigin.TopRight:
{
var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
@@ -377,8 +377,11 @@ namespace Jellyfin.Drawing.Skia
var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
using (var surface = new SKCanvas(rotated))
{
float px = (float)bitmap.Width / 2;
float py = (float)bitmap.Height / 2;
float px = bitmap.Width;
px /= 2;
float py = bitmap.Height;
py /= 2;
surface.RotateDegrees(180, px, py);
surface.DrawBitmap(bitmap, 0, 0);
@@ -392,9 +395,11 @@ namespace Jellyfin.Drawing.Skia
var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
using (var surface = new SKCanvas(rotated))
{
float px = (float)bitmap.Width / 2;
float px = bitmap.Width;
px /= 2;
float py = (float)bitmap.Height / 2;
float py = bitmap.Height;
py /= 2;
surface.Translate(rotated.Width, 0);
surface.Scale(-1, 1);
@@ -418,6 +423,7 @@ namespace Jellyfin.Drawing.Skia
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height);
@@ -482,7 +488,8 @@ namespace Jellyfin.Drawing.Skia
return rotated;
}
default: return bitmap;
default:
return bitmap;
}
}
@@ -492,7 +499,6 @@ namespace Jellyfin.Drawing.Skia
{
throw new ArgumentNullException(nameof(inputPath));
}
if (string.IsNullOrWhiteSpace(inputPath))
{
throw new ArgumentNullException(nameof(outputPath));
@@ -512,11 +518,11 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentOutOfRangeException(string.Format("Skia unable to read image {0}", inputPath));
}
var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height);
//_logger.LogInformation("Color type {0}", bitmap.Info.ColorType);
if (!options.CropWhiteSpace
&& options.HasDefaultOptions(inputPath, originalImageSize)
&& !autoOrient)
var originalImageSize = new ImageSize(bitmap.Width, bitmap.Height);
if (!options.CropWhiteSpace && options.HasDefaultOptions(inputPath, originalImageSize) && !autoOrient)
{
// Just spit out the original file if all the options are default
return inputPath;
@@ -524,10 +530,10 @@ namespace Jellyfin.Drawing.Skia
var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize);
var width = newImageSize.Width;
var height = newImageSize.Height;
var width = Convert.ToInt32(Math.Round(newImageSize.Width));
var height = Convert.ToInt32(Math.Round(newImageSize.Height));
using (var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType))
using (var resizedBitmap = new SKBitmap(width, height))//, bitmap.ColorType, bitmap.AlphaType))
{
// scale image
bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High);
@@ -535,12 +541,14 @@ namespace Jellyfin.Drawing.Skia
// If all we're doing is resizing then we can stop now
if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator)
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath));
using (var outputStream = new SKFileWStream(outputPath))
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()))
{
pixmap.Encode(outputStream, skiaOutputFormat, quality);
return outputPath;
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()))
{
pixmap.Encode(outputStream, skiaOutputFormat, quality);
return outputPath;
}
}
}
@@ -587,7 +595,7 @@ namespace Jellyfin.Drawing.Skia
DrawIndicator(canvas, width, height, options);
}
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath));
using (var outputStream = new SKFileWStream(outputPath))
{
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels()))
@@ -603,7 +611,8 @@ namespace Jellyfin.Drawing.Skia
public void CreateImageCollage(ImageCollageOptions options)
{
double ratio = (double)options.Width / options.Height;
double ratio = options.Width;
ratio /= options.Height;
if (ratio >= 1.4)
{
@@ -615,7 +624,7 @@ namespace Jellyfin.Drawing.Skia
}
else
{
// TODO: Create Poster collage capability
// @todo create Poster collage capability
new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height);
}
}
@@ -624,7 +633,7 @@ namespace Jellyfin.Drawing.Skia
{
try
{
var currentImageSize = new ImageDimensions(imageWidth, imageHeight);
var currentImageSize = new ImageSize(imageWidth, imageHeight);
if (options.AddPlayedIndicator)
{

View File

@@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using SkiaSharp;
namespace Jellyfin.Drawing.Skia
namespace Emby.Drawing
{
public class StripCollageBuilder
{
@@ -25,7 +25,7 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentNullException(nameof(outputPath));
}
var ext = Path.GetExtension(outputPath).ToLowerInvariant();
var ext = Path.GetExtension(outputPath).ToLower();
if (ext == ".jpg" || ext == ".jpeg")
return SKEncodedImageFormat.Jpeg;
@@ -43,14 +43,21 @@ namespace Jellyfin.Drawing.Skia
return SKEncodedImageFormat.Png;
}
public void BuildPosterCollage(string[] paths, string outputPath, int width, int height)
{
// @todo
}
public void BuildSquareCollage(string[] paths, string outputPath, int width, int height)
{
using (var bitmap = BuildSquareCollageBitmap(paths, width, height))
using (var outputStream = new SKFileWStream(outputPath))
{
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels()))
using (var outputStream = new SKFileWStream(outputPath))
{
pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90);
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels()))
{
pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90);
}
}
}
}
@@ -77,18 +84,21 @@ namespace Jellyfin.Drawing.Skia
{
canvas.Clear(SKColors.Black);
// number of images used in the thumbnail
var iCount = 3;
// determine sizes for each image that will composited into the final image
var iSlice = Convert.ToInt32(width / iCount);
int iHeight = Convert.ToInt32(height * 1.00);
var iSlice = Convert.ToInt32(width * 0.23475);
int iTrans = Convert.ToInt32(height * .25);
int iHeight = Convert.ToInt32(height * .70);
var horizontalImagePadding = Convert.ToInt32(width * 0.0125);
var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
int imageIndex = 0;
for (int i = 0; i < iCount; i++)
for (int i = 0; i < 4; i++)
{
using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex))
{
imageIndex = newIndex;
if (currentBitmap == null)
{
continue;
@@ -105,7 +115,44 @@ namespace Jellyfin.Drawing.Skia
using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)))
{
// draw image onto canvas
canvas.DrawImage(subset ?? image, iSlice * i, 0);
canvas.DrawImage(subset ?? image, (horizontalImagePadding * (i + 1)) + (iSlice * i), verticalSpacing);
if (subset == null)
{
continue;
}
// create reflection of image below the drawn image
using (var croppedBitmap = SKBitmap.FromImage(subset))
using (var reflectionBitmap = new SKBitmap(croppedBitmap.Width, croppedBitmap.Height / 2, croppedBitmap.ColorType, croppedBitmap.AlphaType))
{
// resize to half height
currentBitmap.ScalePixels(reflectionBitmap, SKFilterQuality.High);
using (var flippedBitmap = new SKBitmap(reflectionBitmap.Width, reflectionBitmap.Height, reflectionBitmap.ColorType, reflectionBitmap.AlphaType))
using (var flippedCanvas = new SKCanvas(flippedBitmap))
{
// flip image vertically
var matrix = SKMatrix.MakeScale(1, -1);
matrix.SetScaleTranslate(1, -1, 0, flippedBitmap.Height);
flippedCanvas.SetMatrix(matrix);
flippedCanvas.DrawBitmap(reflectionBitmap, 0, 0);
flippedCanvas.ResetMatrix();
// create gradient to make image appear as a reflection
var remainingHeight = height - (iHeight + (2 * verticalSpacing));
flippedCanvas.ClipRect(SKRect.Create(reflectionBitmap.Width, remainingHeight));
using (var gradient = new SKPaint())
{
gradient.IsAntialias = true;
gradient.BlendMode = SKBlendMode.SrcOver;
gradient.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(0, remainingHeight), new[] { new SKColor(0, 0, 0, 128), new SKColor(0, 0, 0, 208), new SKColor(0, 0, 0, 240), new SKColor(0, 0, 0, 255) }, null, SKShaderTileMode.Clamp);
flippedCanvas.DrawPaint(gradient);
}
// finally draw reflection onto canvas
canvas.DrawBitmap(flippedBitmap, (horizontalImagePadding * (i + 1)) + (iSlice * i), iHeight + (2 * verticalSpacing));
}
}
}
}
}

View File

@@ -2,20 +2,20 @@ using System.Globalization;
using MediaBrowser.Model.Drawing;
using SkiaSharp;
namespace Jellyfin.Drawing.Skia
namespace Emby.Drawing
{
public static class UnplayedCountIndicator
{
private const int OffsetFromTopRightCorner = 38;
public static void DrawUnplayedCountIndicator(SKCanvas canvas, ImageDimensions imageSize, int count)
public static void DrawUnplayedCountIndicator(SKCanvas canvas, ImageSize imageSize, int count)
{
var x = imageSize.Width - OffsetFromTopRightCorner;
var text = count.ToString(CultureInfo.InvariantCulture);
using (var paint = new SKPaint())
{
paint.Color = SKColor.Parse("#CC00A4DC");
paint.Color = SKColor.Parse("#CC52B54B");
paint.Style = SKPaintStyle.Fill;
canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint);
}

View File

@@ -7,7 +7,6 @@ using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace IsoMounter
{
@@ -18,7 +17,9 @@ namespace IsoMounter
#region Private Fields
private readonly IEnvironmentInfo EnvironmentInfo;
private readonly bool ExecutablesAvailable;
private readonly IFileSystem FileSystem;
private readonly ILogger _logger;
private readonly string MountCommand;
private readonly string MountPointRoot;
@@ -30,12 +31,15 @@ namespace IsoMounter
#region Constructor(s)
public LinuxIsoManager(ILogger logger, IProcessFactory processFactory)
public LinuxIsoManager(ILogger logger, IFileSystem fileSystem, IEnvironmentInfo environment, IProcessFactory processFactory)
{
EnvironmentInfo = environment;
FileSystem = fileSystem;
_logger = logger;
ProcessFactory = processFactory;
MountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby";
MountPointRoot = FileSystem.DirectorySeparatorChar + "tmp" + FileSystem.DirectorySeparatorChar + "Emby";
_logger.LogDebug(
"[{0}] System PATH is currently set to [{1}].",
@@ -107,7 +111,7 @@ namespace IsoMounter
public bool CanMount(string path)
{
if (OperatingSystem.Id != OperatingSystemId.Linux)
if (EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Linux)
{
return false;
}
@@ -116,8 +120,8 @@ namespace IsoMounter
Name,
path,
Path.GetExtension(path),
OperatingSystem.Name,
ExecutablesAvailable
EnvironmentInfo.OperatingSystem,
ExecutablesAvailable.ToString()
);
if (ExecutablesAvailable)
@@ -179,7 +183,7 @@ namespace IsoMounter
_logger.LogInformation(
"[{0}] Disposing [{1}].",
Name,
disposing
disposing.ToString()
);
if (disposing)
@@ -210,9 +214,9 @@ namespace IsoMounter
{
string path = test.Trim();
if (!string.IsNullOrEmpty(path) && File.Exists(path = Path.Combine(path, name)))
if (!string.IsNullOrEmpty(path) && FileSystem.FileExists(path = Path.Combine(path, name)))
{
return Path.GetFullPath(path);
return FileSystem.GetFullPath(path);
}
}
@@ -225,8 +229,9 @@ namespace IsoMounter
var uid = getuid();
_logger.LogDebug(
"[{0}] GetUserId() returned [{2}].",
"[{0}] Our current UID is [{1}], GetUserId() returned [{2}].",
Name,
uid.ToString(),
uid
);
@@ -322,7 +327,7 @@ namespace IsoMounter
try
{
Directory.CreateDirectory(mountPoint);
FileSystem.CreateDirectory(mountPoint);
}
catch (UnauthorizedAccessException)
{
@@ -372,7 +377,7 @@ namespace IsoMounter
try
{
Directory.Delete(mountPoint, false);
FileSystem.DeleteDirectory(mountPoint, false);
}
catch (Exception ex)
{
@@ -450,7 +455,7 @@ namespace IsoMounter
try
{
Directory.Delete(mount.MountedPath, false);
FileSystem.DeleteDirectory(mount.MountedPath, false);
}
catch (Exception ex)
{

View File

@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]

View File

@@ -30,13 +30,10 @@ namespace Emby.Naming.AudioBook
{
throw new ArgumentNullException(nameof(path));
}
if (IsDirectory) // TODO
{
if (IsDirectory)
return null;
}
var extension = Path.GetExtension(path);
var extension = Path.GetExtension(path) ?? string.Empty;
// Check supported extensions
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{

View File

@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
namespace Emby.Naming.TV
@@ -21,9 +22,7 @@ namespace Emby.Naming.TV
// There were no failed tests without this block, but to be safe, we can keep it until
// the regex which require file extensions are modified so that they don't need them.
if (IsDirectory)
{
path += ".mp4";
}
EpisodePathParserResult result = null;
@@ -36,7 +35,6 @@ namespace Emby.Naming.TV
continue;
}
}
if (isNamed.HasValue)
{
if (expression.IsNamed != isNamed.Value)
@@ -44,7 +42,6 @@ namespace Emby.Naming.TV
continue;
}
}
if (isOptimistic.HasValue)
{
if (expression.IsOptimistic != isOptimistic.Value)
@@ -194,20 +191,13 @@ namespace Emby.Naming.TV
private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable<EpisodeExpression> expressions)
{
foreach (var i in expressions)
var results = expressions
.Where(i => i.IsNamed)
.Select(i => Parse(path, i))
.Where(i => i.Success);
foreach (var result in results)
{
if (!i.IsNamed)
{
continue;
}
var result = Parse(path, i);
if (!result.Success)
{
continue;
}
if (string.IsNullOrEmpty(info.SeriesName))
{
info.SeriesName = result.SeriesName;
@@ -218,10 +208,12 @@ namespace Emby.Naming.TV
info.EndingEpsiodeNumber = result.EndingEpsiodeNumber;
}
if (!string.IsNullOrEmpty(info.SeriesName)
&& (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue))
if (!string.IsNullOrEmpty(info.SeriesName))
{
break;
if (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue)
{
break;
}
}
}
}

View File

@@ -183,7 +183,8 @@ namespace Emby.Naming.Video
{
if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path)))
{
if (HaveSameYear(videos))
// Enforce the multi-version limit
if (videos.Count <= 8 && HaveSameYear(videos))
{
var ordered = videos.OrderBy(i => i.Name).ToList();
@@ -199,6 +200,23 @@ namespace Emby.Naming.Video
}
return videos;
//foreach (var video in videos.OrderBy(i => i.Name))
//{
// var match = list
// .FirstOrDefault(i => string.Equals(i.Name, video.Name, StringComparison.OrdinalIgnoreCase));
// if (match != null && video.Files.Count == 1 && match.Files.Count == 1)
// {
// match.AlternateVersions.Add(video.Files[0]);
// match.Extras.AddRange(video.Extras);
// }
// else
// {
// list.Add(video);
// }
//}
//return list;
}
private bool HaveSameYear(List<VideoInfo> videos)
@@ -208,14 +226,17 @@ namespace Emby.Naming.Video
private bool IsEligibleForMultiVersion(string folderName, string testFilename)
{
testFilename = Path.GetFileNameWithoutExtension(testFilename) ?? string.Empty;
testFilename = Path.GetFileNameWithoutExtension(testFilename);
if (string.Equals(folderName, testFilename, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{
testFilename = testFilename.Substring(folderName.Length).Trim();
return string.IsNullOrEmpty(testFilename) ||
testFilename.StartsWith("-") ||
string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)) ;
return testFilename.StartsWith("-", StringComparison.OrdinalIgnoreCase) || Regex.Replace(testFilename, @"\[([^]]*)\]", "").Trim() == string.Empty;
}
return false;

View File

@@ -11,82 +11,112 @@ namespace Emby.Notifications
public class CoreNotificationTypes : INotificationTypeFactory
{
private readonly ILocalizationManager _localization;
private readonly IServerApplicationHost _appHost;
public CoreNotificationTypes(ILocalizationManager localization)
public CoreNotificationTypes(ILocalizationManager localization, IServerApplicationHost appHost)
{
_localization = localization;
_appHost = appHost;
}
public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
{
var knownTypes = new NotificationTypeInfo[]
var knownTypes = new List<NotificationTypeInfo>
{
new NotificationTypeInfo
{
Type = NotificationType.ApplicationUpdateInstalled.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.InstallationFailed.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.PluginInstalled.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.PluginError.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.PluginUninstalled.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.PluginUpdateInstalled.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.ServerRestartRequired.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.TaskFailed.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.NewLibraryContent.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.AudioPlayback.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.GamePlayback.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.VideoPlayback.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.AudioPlaybackStopped.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.GamePlaybackStopped.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.VideoPlaybackStopped.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.CameraImageUploaded.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.UserLockedOut.ToString()
},
new NotificationTypeInfo
{
Type = NotificationType.ApplicationUpdateAvailable.ToString()
}
};
if (!_appHost.CanSelfUpdate)
{
knownTypes.Add(new NotificationTypeInfo
{
Type = NotificationType.ApplicationUpdateAvailable.ToString()
});
}
foreach (var type in knownTypes)
{
Update(type);

View File

@@ -5,17 +5,22 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Notifications;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging;
namespace Emby.Notifications
@@ -25,50 +30,53 @@ namespace Emby.Notifications
/// </summary>
public class Notifications : IServerEntryPoint
{
private readonly IInstallationManager _installationManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ITaskManager _taskManager;
private readonly INotificationManager _notificationManager;
private readonly ILibraryManager _libraryManager;
private readonly ISessionManager _sessionManager;
private readonly IServerApplicationHost _appHost;
private readonly ITimerFactory _timerFactory;
private Timer LibraryUpdateTimer { get; set; }
private ITimer LibraryUpdateTimer { get; set; }
private readonly object _libraryChangedSyncLock = new object();
private readonly IConfigurationManager _config;
private readonly IDeviceManager _deviceManager;
private readonly ILocalizationManager _localization;
private readonly IActivityManager _activityManager;
private string[] _coreNotificationTypes;
public Notifications(
IActivityManager activityManager,
ILocalizationManager localization,
ILogger logger,
INotificationManager notificationManager,
ILibraryManager libraryManager,
IServerApplicationHost appHost,
IConfigurationManager config)
public Notifications(IInstallationManager installationManager, IActivityManager activityManager, ILocalizationManager localization, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager, ITimerFactory timerFactory)
{
_installationManager = installationManager;
_userManager = userManager;
_logger = logger;
_taskManager = taskManager;
_notificationManager = notificationManager;
_libraryManager = libraryManager;
_sessionManager = sessionManager;
_appHost = appHost;
_config = config;
_deviceManager = deviceManager;
_timerFactory = timerFactory;
_localization = localization;
_activityManager = activityManager;
_coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
_coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray();
}
public Task RunAsync()
public void Run()
{
_libraryManager.ItemAdded += _libraryManager_ItemAdded;
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
_activityManager.EntryCreated += _activityManager_EntryCreated;
return Task.CompletedTask;
}
private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
@@ -117,9 +125,10 @@ namespace Emby.Notifications
return _config.GetConfiguration<NotificationOptions>("notifications");
}
private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
{
if (!_appHost.HasUpdateAvailable)
// This notification is for users who can't auto-update (aka running as service)
if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate)
{
return;
}
@@ -137,7 +146,7 @@ namespace Emby.Notifications
}
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
{
if (!FilterItem(e.Item))
{
@@ -148,7 +157,7 @@ namespace Emby.Notifications
{
if (LibraryUpdateTimer == null)
{
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, 5000,
LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, 5000,
Timeout.Infinite);
}
else

View File

@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]

View File

@@ -3,16 +3,13 @@
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\ThirdParty\taglib-sharp\src\taglib-sharp.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="TagLibSharp" Version="2.2.0-beta" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>

View File

@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
using TagLib;
using TagLib.IFD;
@@ -20,11 +21,13 @@ namespace Emby.Photos
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
{
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private IImageProcessor _imageProcessor;
public PhotoProvider(ILogger logger, IImageProcessor imageProcessor)
public PhotoProvider(ILogger logger, IFileSystem fileSystem, IImageProcessor imageProcessor)
{
_logger = logger;
_fileSystem = fileSystem;
_imageProcessor = imageProcessor;
}
@@ -178,12 +181,12 @@ namespace Emby.Photos
try
{
var size = _imageProcessor.GetImageDimensions(item, img, false);
var size = _imageProcessor.GetImageSize(item, img, false);
if (size.Width > 0 && size.Height > 0)
{
item.Width = size.Width;
item.Height = size.Height;
item.Width = Convert.ToInt32(size.Width);
item.Height = Convert.ToInt32(size.Height);
}
}
catch (ArgumentException)

View File

@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@@ -30,10 +29,13 @@ namespace Emby.Server.Implementations.Activity
public class ActivityLogEntryPoint : IServerEntryPoint
{
private readonly IInstallationManager _installationManager;
//private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
private readonly ITaskManager _taskManager;
private readonly IActivityManager _activityManager;
private readonly ILocalizationManager _localization;
private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager;
@@ -56,39 +58,41 @@ namespace Emby.Server.Implementations.Activity
_deviceManager = deviceManager;
}
public Task RunAsync()
public void Run()
{
_taskManager.TaskCompleted += OnTaskCompleted;
_taskManager.TaskCompleted += _taskManager_TaskCompleted;
_installationManager.PluginInstalled += OnPluginInstalled;
_installationManager.PluginUninstalled += OnPluginUninstalled;
_installationManager.PluginUpdated += OnPluginUpdated;
_installationManager.PackageInstallationFailed += OnPackageInstallationFailed;
_installationManager.PluginInstalled += _installationManager_PluginInstalled;
_installationManager.PluginUninstalled += _installationManager_PluginUninstalled;
_installationManager.PluginUpdated += _installationManager_PluginUpdated;
_installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed;
_sessionManager.SessionStarted += OnSessionStarted;
_sessionManager.AuthenticationFailed += OnAuthenticationFailed;
_sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
_sessionManager.SessionEnded += OnSessionEnded;
_sessionManager.SessionStarted += _sessionManager_SessionStarted;
_sessionManager.AuthenticationFailed += _sessionManager_AuthenticationFailed;
_sessionManager.AuthenticationSucceeded += _sessionManager_AuthenticationSucceeded;
_sessionManager.SessionEnded += _sessionManager_SessionEnded;
_sessionManager.PlaybackStart += OnPlaybackStart;
_sessionManager.PlaybackStopped += OnPlaybackStopped;
_sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
_sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped;
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
//_subManager.SubtitlesDownloaded += _subManager_SubtitlesDownloaded;
_subManager.SubtitleDownloadFailure += _subManager_SubtitleDownloadFailure;
_userManager.UserCreated += OnUserCreated;
_userManager.UserPasswordChanged += OnUserPasswordChanged;
_userManager.UserDeleted += OnUserDeleted;
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
_userManager.UserLockedOut += OnUserLockedOut;
_userManager.UserCreated += _userManager_UserCreated;
_userManager.UserPasswordChanged += _userManager_UserPasswordChanged;
_userManager.UserDeleted += _userManager_UserDeleted;
_userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated;
_userManager.UserLockedOut += _userManager_UserLockedOut;
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
//_config.ConfigurationUpdated += _config_ConfigurationUpdated;
//_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
_appHost.ApplicationUpdated += OnApplicationUpdated;
_deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded;
return Task.CompletedTask;
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
}
private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -97,7 +101,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnUserLockedOut(object sender, GenericEventArgs<User> e)
void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -107,7 +111,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -118,7 +122,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
{
var item = e.MediaInfo;
@@ -139,7 +143,7 @@ namespace Emby.Server.Implementations.Activity
return;
}
var user = e.Users[0];
var user = e.Users.First();
CreateLogEntry(new ActivityLogEntry
{
@@ -149,7 +153,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
{
var item = e.MediaInfo;
@@ -203,6 +207,10 @@ namespace Emby.Server.Implementations.Activity
{
return NotificationType.AudioPlayback.ToString();
}
if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.GamePlayback.ToString();
}
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.VideoPlayback.ToString();
@@ -217,6 +225,10 @@ namespace Emby.Server.Implementations.Activity
{
return NotificationType.AudioPlaybackStopped.ToString();
}
if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.GamePlaybackStopped.ToString();
}
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.VideoPlaybackStopped.ToString();
@@ -225,7 +237,7 @@ namespace Emby.Server.Implementations.Activity
return null;
}
private void OnSessionEnded(object sender, SessionEventArgs e)
void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
{
string name;
var session = e.SessionInfo;
@@ -251,7 +263,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
{
var user = e.Argument.User;
@@ -264,7 +276,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -275,7 +287,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -285,7 +297,25 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key),
Type = "NamedConfigurationUpdated"
});
}
void _config_ConfigurationUpdated(object sender, EventArgs e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"),
Type = "ServerConfigurationUpdated"
});
}
void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -295,7 +325,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -304,7 +334,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -314,7 +344,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnUserCreated(object sender, GenericEventArgs<User> e)
void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -324,7 +354,18 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnSessionStarted(object sender, SessionEventArgs e)
void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)),
Type = "SubtitlesDownloaded",
ItemId = e.Item.Id.ToString("N"),
ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider)
});
}
void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
{
string name;
var session = e.SessionInfo;
@@ -350,7 +391,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -361,7 +402,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -370,7 +411,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -380,7 +421,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
{
var installationInfo = e.InstallationInfo;
@@ -393,7 +434,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
{
var result = e.Result;
var task = e.Task;
@@ -432,36 +473,49 @@ namespace Emby.Server.Implementations.Activity
}
private void CreateLogEntry(ActivityLogEntry entry)
=> _activityManager.Create(entry);
{
try
{
_activityManager.Create(entry);
}
catch
{
// Logged at lower levels
}
}
public void Dispose()
{
_taskManager.TaskCompleted -= OnTaskCompleted;
_taskManager.TaskCompleted -= _taskManager_TaskCompleted;
_installationManager.PluginInstalled -= OnPluginInstalled;
_installationManager.PluginUninstalled -= OnPluginUninstalled;
_installationManager.PluginUpdated -= OnPluginUpdated;
_installationManager.PackageInstallationFailed -= OnPackageInstallationFailed;
_installationManager.PluginInstalled -= _installationManager_PluginInstalled;
_installationManager.PluginUninstalled -= _installationManager_PluginUninstalled;
_installationManager.PluginUpdated -= _installationManager_PluginUpdated;
_installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed;
_sessionManager.SessionStarted -= OnSessionStarted;
_sessionManager.AuthenticationFailed -= OnAuthenticationFailed;
_sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded;
_sessionManager.SessionEnded -= OnSessionEnded;
_sessionManager.SessionStarted -= _sessionManager_SessionStarted;
_sessionManager.AuthenticationFailed -= _sessionManager_AuthenticationFailed;
_sessionManager.AuthenticationSucceeded -= _sessionManager_AuthenticationSucceeded;
_sessionManager.SessionEnded -= _sessionManager_SessionEnded;
_sessionManager.PlaybackStart -= OnPlaybackStart;
_sessionManager.PlaybackStopped -= OnPlaybackStopped;
_sessionManager.PlaybackStart -= _sessionManager_PlaybackStart;
_sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped;
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
_subManager.SubtitlesDownloaded -= _subManager_SubtitlesDownloaded;
_subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure;
_userManager.UserCreated -= OnUserCreated;
_userManager.UserPasswordChanged -= OnUserPasswordChanged;
_userManager.UserDeleted -= OnUserDeleted;
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
_userManager.UserLockedOut -= OnUserLockedOut;
_userManager.UserCreated -= _userManager_UserCreated;
_userManager.UserPasswordChanged -= _userManager_UserPasswordChanged;
_userManager.UserDeleted -= _userManager_UserDeleted;
_userManager.UserPolicyUpdated -= _userManager_UserPolicyUpdated;
_userManager.UserLockedOut -= _userManager_UserLockedOut;
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
_config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated;
_appHost.ApplicationUpdated -= OnApplicationUpdated;
_deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
_appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
}
/// <summary>
@@ -483,7 +537,6 @@ namespace Emby.Server.Implementations.Activity
values.Add(CreateValueString(years, "year"));
days = days % DaysInYear;
}
// Number of months
if (days >= DaysInMonth)
{
@@ -491,39 +544,25 @@ namespace Emby.Server.Implementations.Activity
values.Add(CreateValueString(months, "month"));
days = days % DaysInMonth;
}
// Number of days
if (days >= 1)
{
values.Add(CreateValueString(days, "day"));
}
// Number of hours
if (span.Hours >= 1)
{
values.Add(CreateValueString(span.Hours, "hour"));
}
// Number of minutes
if (span.Minutes >= 1)
{
values.Add(CreateValueString(span.Minutes, "minute"));
}
// Number of seconds (include when 0 if no other components included)
if (span.Seconds >= 1 || values.Count == 0)
{
values.Add(CreateValueString(span.Seconds, "second"));
}
// Combine values into string
var builder = new StringBuilder();
for (int i = 0; i < values.Count; i++)
{
if (builder.Length > 0)
{
builder.Append(i == values.Count - 1 ? " and " : ", ");
}
builder.Append(values[i]);
}
// Return result

View File

@@ -39,13 +39,8 @@ namespace Emby.Server.Implementations.Activity
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
foreach (var item in result.Items)
foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty)))
{
if (item.UserId == Guid.Empty)
{
continue;
}
var user = _userManager.GetUserById(item.UserId);
if (user != null)

View File

@@ -1,4 +1,3 @@
using System;
using System.IO;
using MediaBrowser.Common.Configuration;
@@ -15,52 +14,48 @@ namespace Emby.Server.Implementations.AppBase
/// </summary>
protected BaseApplicationPaths(
string programDataPath,
string logDirectoryPath,
string configurationDirectoryPath,
string cacheDirectoryPath,
string webDirectoryPath)
string appFolderPath,
string logDirectoryPath = null,
string configurationDirectoryPath = null)
{
ProgramDataPath = programDataPath;
ProgramSystemPath = appFolderPath;
LogDirectoryPath = logDirectoryPath;
ConfigurationDirectoryPath = configurationDirectoryPath;
CachePath = cacheDirectoryPath;
WebPath = webDirectoryPath;
DataPath = Path.Combine(ProgramDataPath, "data");
}
/// <summary>
/// Gets the path to the program data folder
/// </summary>
/// <value>The program data path.</value>
public string ProgramDataPath { get; private set; }
/// <summary>
/// Gets the path to the web UI resources folder
/// </summary>
/// <value>The web UI resources path.</value>
public string WebPath { get; set; }
/// <summary>
/// Gets the path to the system folder
/// </summary>
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
public string ProgramSystemPath { get; private set; }
/// <summary>
/// The _data directory
/// </summary>
private string _dataDirectory;
/// <summary>
/// Gets the folder path to the data directory
/// </summary>
/// <value>The data directory.</value>
private string _dataPath;
public string DataPath
{
get => _dataPath;
private set => _dataPath = Directory.CreateDirectory(value).FullName;
get
{
if (_dataDirectory == null)
{
_dataDirectory = Path.Combine(ProgramDataPath, "data");
Directory.CreateDirectory(_dataDirectory);
}
return _dataDirectory;
}
}
/// <summary>
/// Gets the magic strings used for virtual path manipulation.
/// </summary>
public string VirtualDataPath { get; } = "%AppDataPath%";
private const string _virtualDataPath = "%AppDataPath%";
public string VirtualDataPath => _virtualDataPath;
/// <summary>
/// Gets the image cache path.
@@ -80,17 +75,61 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The plugin configurations path.</value>
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
/// <summary>
/// Gets the path to where temporary update files will be stored
/// </summary>
/// <value>The plugin configurations path.</value>
public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates");
/// <summary>
/// The _log directory
/// </summary>
private string _logDirectoryPath;
/// <summary>
/// Gets the path to the log directory
/// </summary>
/// <value>The log directory path.</value>
public string LogDirectoryPath { get; private set; }
public string LogDirectoryPath
{
get
{
if (string.IsNullOrEmpty(_logDirectoryPath))
{
_logDirectoryPath = Path.Combine(ProgramDataPath, "logs");
Directory.CreateDirectory(_logDirectoryPath);
}
return _logDirectoryPath;
}
set => _logDirectoryPath = value;
}
/// <summary>
/// The _config directory
/// </summary>
private string _configurationDirectoryPath;
/// <summary>
/// Gets the path to the application configuration root directory
/// </summary>
/// <value>The configuration directory path.</value>
public string ConfigurationDirectoryPath { get; private set; }
public string ConfigurationDirectoryPath
{
get
{
if (string.IsNullOrEmpty(_configurationDirectoryPath))
{
_configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
Directory.CreateDirectory(_configurationDirectoryPath);
}
return _configurationDirectoryPath;
}
set => _configurationDirectoryPath = value;
}
/// <summary>
/// Gets the path to the system configuration file
@@ -98,11 +137,29 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The system configuration file path.</value>
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
/// <summary>
/// The _cache directory
/// </summary>
private string _cachePath;
/// <summary>
/// Gets the folder path to the cache directory
/// </summary>
/// <value>The cache directory.</value>
public string CachePath { get; set; }
public string CachePath
{
get
{
if (string.IsNullOrEmpty(_cachePath))
{
_cachePath = Path.Combine(ProgramDataPath, "cache");
Directory.CreateDirectory(_cachePath);
}
return _cachePath;
}
set => _cachePath = value;
}
/// <summary>
/// Gets the folder path to the temp directory within the cache folder

View File

@@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.AppBase
get
{
// Lazy load
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem));
return _configuration;
}
protected set
@@ -127,7 +127,7 @@ namespace Emby.Server.Implementations.AppBase
Logger.LogInformation("Saving system configuration");
var path = CommonApplicationPaths.SystemConfigurationFilePath;
Directory.CreateDirectory(Path.GetDirectoryName(path));
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path));
lock (_configurationSyncLock)
{
@@ -171,29 +171,16 @@ namespace Emby.Server.Implementations.AppBase
private void UpdateCachePath()
{
string cachePath;
// If the configuration file has no entry (i.e. not set in UI)
if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
{
// If the current live configuration has no entry (i.e. not set on CLI/envvars, during startup)
if (string.IsNullOrWhiteSpace(((BaseApplicationPaths)CommonApplicationPaths).CachePath))
{
// Set cachePath to a default value under ProgramDataPath
cachePath = Path.Combine(((BaseApplicationPaths)CommonApplicationPaths).ProgramDataPath, "cache");
}
else
{
// Set cachePath to the existing live value; will require restart if UI value is removed (but not replaced)
// TODO: Figure out how to re-grab this from the CLI/envvars while running
cachePath = ((BaseApplicationPaths)CommonApplicationPaths).CachePath;
}
cachePath = null;
}
else
{
// Set cachePath to the new UI-set value
cachePath = CommonConfiguration.CachePath;
cachePath = Path.Combine(CommonConfiguration.CachePath, "cache");
}
Logger.LogInformation("Setting cache path to " + cachePath);
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
}
@@ -210,7 +197,7 @@ namespace Emby.Server.Implementations.AppBase
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
{
// Validate
if (!Directory.Exists(newPath))
if (!FileSystem.DirectoryExists(newPath))
{
throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
}
@@ -222,7 +209,8 @@ namespace Emby.Server.Implementations.AppBase
protected void EnsureWriteAccess(string path)
{
var file = Path.Combine(path, Guid.NewGuid().ToString());
File.WriteAllText(file, string.Empty);
FileSystem.WriteAllText(file, string.Empty);
FileSystem.DeleteFile(file);
}
@@ -230,7 +218,7 @@ namespace Emby.Server.Implementations.AppBase
private string GetConfigurationFile(string key)
{
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml");
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLower() + ".xml");
}
public object GetConfiguration(string key)
@@ -258,15 +246,14 @@ namespace Emby.Server.Implementations.AppBase
private object LoadConfiguration(string path, Type configurationType)
{
if (!File.Exists(path))
{
return Activator.CreateInstance(configurationType);
}
try
{
return XmlSerializer.DeserializeFromFile(configurationType, path);
}
catch (FileNotFoundException)
{
return Activator.CreateInstance(configurationType);
}
catch (IOException)
{
return Activator.CreateInstance(configurationType);
@@ -306,7 +293,7 @@ namespace Emby.Server.Implementations.AppBase
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
var path = GetConfigurationFile(key);
Directory.CreateDirectory(Path.GetDirectoryName(path));
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path));
lock (_configurationSyncLock)
{

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase
@@ -17,8 +18,9 @@ namespace Emby.Server.Implementations.AppBase
/// <param name="type">The type.</param>
/// <param name="path">The path.</param>
/// <param name="xmlSerializer">The XML serializer.</param>
/// <param name="fileSystem">The file system</param>
/// <returns>System.Object.</returns>
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer)
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
{
object configuration;
@@ -27,7 +29,7 @@ namespace Emby.Server.Implementations.AppBase
// Use try/catch to avoid the extra file system lookup using File.Exists
try
{
buffer = File.ReadAllBytes(path);
buffer = fileSystem.ReadAllBytes(path);
configuration = xmlSerializer.DeserializeFromBytes(type, buffer);
}
@@ -46,10 +48,10 @@ namespace Emby.Server.Implementations.AppBase
// If the file didn't exist before, or if something has changed, re-save
if (buffer == null || !buffer.SequenceEqual(newBytes))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
fileSystem.CreateDirectory(fileSystem.GetDirectoryName(path));
// Save it after load in case we got new items
File.WriteAllBytes(path, newBytes);
fileSystem.WriteAllBytes(path, newBytes);
}
return configuration;

File diff suppressed because it is too large Load Diff

View File

@@ -14,9 +14,11 @@ namespace Emby.Server.Implementations.Archiving
/// </summary>
public class ZipClient : IZipClient
{
public ZipClient()
{
private readonly IFileSystem _fileSystem;
public ZipClient(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
/// <summary>
@@ -27,7 +29,7 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
using (var fileStream = _fileSystem.OpenRead(sourceFile))
{
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
}
@@ -113,7 +115,7 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
using (var fileStream = _fileSystem.OpenRead(sourceFile))
{
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
}
@@ -153,7 +155,7 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
using (var fileStream = _fileSystem.OpenRead(sourceFile))
{
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
}

View File

@@ -243,7 +243,8 @@ namespace Emby.Server.Implementations.Channels
{
foreach (var item in returnItems)
{
RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None).GetAwaiter().GetResult();
var task = RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None);
Task.WaitAll(task);
}
}
@@ -302,7 +303,9 @@ namespace Emby.Server.Implementations.Channels
}
numComplete++;
double percent = (double)numComplete / allChannelsList.Count;
double percent = numComplete;
percent /= allChannelsList.Count;
progress.Report(100 * percent);
}
@@ -352,7 +355,7 @@ namespace Emby.Server.Implementations.Channels
return;
}
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
_jsonSerializer.SerializeToFile(mediaSources, path);
}
@@ -655,7 +658,9 @@ namespace Emby.Server.Implementations.Channels
foreach (var item in result.Items)
{
if (item is Folder folder)
var folder = item as Folder;
if (folder != null)
{
await GetChannelItemsInternal(new InternalItemsQuery
{
@@ -676,17 +681,22 @@ namespace Emby.Server.Implementations.Channels
// Find the corresponding channel provider plugin
var channelProvider = GetChannelProvider(channel);
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
var user = query.User;
ChannelItemSortField? sortField = null;
var sortDescending = false;
var parentItem = !query.ParentId.Equals(Guid.Empty) ? _libraryManager.GetItemById(query.ParentId) : channel;
var itemsResult = await GetChannelItems(channelProvider,
query.User,
user,
parentItem is Channel ? null : parentItem.ExternalId,
null,
false,
sortField,
sortDescending,
cancellationToken)
.ConfigureAwait(false);
if (query.ParentId == Guid.Empty)
if (query.ParentId.Equals(Guid.Empty))
{
query.Parent = channel;
}
@@ -829,7 +839,7 @@ namespace Emby.Server.Implementations.Channels
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
_jsonSerializer.SerializeToFile(result, path);
}

View File

@@ -35,52 +35,64 @@ namespace Emby.Server.Implementations.Channels
public static string GetUserDistinctValue(User user)
{
var channels = user.Policy.EnabledChannels
.OrderBy(i => i);
.OrderBy(i => i)
.ToList();
return string.Join("|", channels);
return string.Join("|", channels.ToArray());
}
private void CleanDatabase(CancellationToken cancellationToken)
{
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
var databaseIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Channel).Name },
ExcludeItemIds = installedChannelIds.ToArray()
IncludeItemTypes = new[] { typeof(Channel).Name }
});
foreach (var channel in uninstalledChannels)
var invalidIds = databaseIds
.Except(installedChannelIds)
.ToList();
foreach (var id in invalidIds)
{
cancellationToken.ThrowIfCancellationRequested();
CleanChannel((Channel)channel, cancellationToken);
CleanChannel(id, cancellationToken);
}
}
private void CleanChannel(Channel channel, CancellationToken cancellationToken)
private void CleanChannel(Guid id, CancellationToken cancellationToken)
{
_logger.LogInformation("Cleaning channel {0} from database", channel.Id);
_logger.LogInformation("Cleaning channel {0} from database", id);
// Delete all channel items
var items = _libraryManager.GetItemList(new InternalItemsQuery
var allIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
ChannelIds = new[] { channel.Id }
ChannelIds = new[] { id }
});
foreach (var item in items)
foreach (var deleteId in allIds)
{
cancellationToken.ThrowIfCancellationRequested();
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
}, false);
DeleteItem(deleteId);
}
// Finally, delete the channel itself
_libraryManager.DeleteItem(channel, new DeleteOptions
DeleteItem(id);
}
private void DeleteItem(Guid id)
{
var item = _libraryManager.GetItemById(id);
if (item == null)
{
return;
}
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false

View File

@@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
{
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly IChannelManager _channelManager;
private readonly IUserManager _userManager;

View File

@@ -10,18 +10,14 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Collections
{
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
{
public CollectionImageProvider(
IFileSystem fileSystem,
IProviderManager providerManager,
IApplicationPaths applicationPaths,
IImageProcessor imageProcessor)
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
public CollectionImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
}
@@ -36,7 +32,7 @@ namespace Emby.Server.Implementations.Collections
return base.Supports(item);
}
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
protected override List<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (BoxSet)item;
@@ -74,13 +70,12 @@ namespace Emby.Server.Implementations.Collections
return null;
})
.Where(i => i != null)
.GroupBy(x => x.Id)
.Select(x => x.First())
.DistinctBy(i => i.Id)
.OrderBy(i => Guid.NewGuid())
.ToList();
}
protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
protected override string CreateImage(BaseItem item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
}

View File

@@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Collections
return null;
}
Directory.CreateDirectory(path);
_fileSystem.CreateDirectory(path);
var libraryOptions = new LibraryOptions
{
@@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Collections
try
{
Directory.CreateDirectory(path);
_fileSystem.CreateDirectory(path);
var collection = new BoxSet
{
@@ -342,22 +342,24 @@ namespace Emby.Server.Implementations.Collections
{
private readonly CollectionManager _collectionManager;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private ILogger _logger;
public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger)
public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger)
{
_collectionManager = (CollectionManager)collectionManager;
_config = config;
_fileSystem = fileSystem;
_logger = logger;
}
public async Task RunAsync()
public async void Run()
{
if (!_config.Configuration.CollectionsUpgraded && _config.Configuration.IsStartupWizardCompleted)
{
var path = _collectionManager.GetCollectionsFolderPath();
if (Directory.Exists(path))
if (_fileSystem.DirectoryExists(path))
{
try
{

View File

@@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.Configuration
&& !string.Equals(Configuration.CertificatePath ?? string.Empty, newPath))
{
// Validate
if (!File.Exists(newPath))
if (!FileSystem.FileExists(newPath))
{
throw new FileNotFoundException(string.Format("Certificate file '{0}' does not exist.", newPath));
}
@@ -168,7 +168,7 @@ namespace Emby.Server.Implementations.Configuration
&& !string.Equals(Configuration.MetadataPath ?? string.Empty, newPath))
{
// Validate
if (!Directory.Exists(newPath))
if (!FileSystem.DirectoryExists(newPath))
{
throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
}

View File

@@ -1,13 +0,0 @@
using System.Collections.Generic;
namespace Emby.Server.Implementations
{
public static class ConfigurationOptions
{
public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
{
{"HttpListenerHost:DefaultRedirectPath", "web/index.html"},
{"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"}
};
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
@@ -10,39 +8,6 @@ namespace Emby.Server.Implementations.Cryptography
{
public class CryptographyProvider : ICryptoProvider
{
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
{
"MD5",
"System.Security.Cryptography.MD5",
"SHA",
"SHA1",
"System.Security.Cryptography.SHA1",
"SHA256",
"SHA-256",
"System.Security.Cryptography.SHA256",
"SHA384",
"SHA-384",
"System.Security.Cryptography.SHA384",
"SHA512",
"SHA-512",
"System.Security.Cryptography.SHA512"
};
public string DefaultHashMethod => "PBKDF2";
private RandomNumberGenerator _randomNumberGenerator;
private const int _defaultIterations = 1000;
public CryptographyProvider()
{
//FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
//Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
//there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
//Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
_randomNumberGenerator = RandomNumberGenerator.Create();
}
public Guid GetMD5(string str)
{
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
@@ -71,98 +36,5 @@ namespace Emby.Server.Implementations.Cryptography
return provider.ComputeHash(bytes);
}
}
public IEnumerable<string> GetSupportedHashMethods()
{
return _supportedHashMethods;
}
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
{
//downgrading for now as we need this library to be dotnetstandard compliant
//with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
if (method == DefaultHashMethod)
{
using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
{
return r.GetBytes(32);
}
}
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
}
public byte[] ComputeHash(string hashMethod, byte[] bytes)
{
return ComputeHash(hashMethod, bytes, Array.Empty<byte>());
}
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
{
return ComputeHash(DefaultHashMethod, bytes);
}
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
{
if (hashMethod == DefaultHashMethod)
{
return PBKDF2(hashMethod, bytes, salt, _defaultIterations);
}
else if (_supportedHashMethods.Contains(hashMethod))
{
using (var h = HashAlgorithm.Create(hashMethod))
{
if (salt.Length == 0)
{
return h.ComputeHash(bytes);
}
else
{
byte[] salted = new byte[bytes.Length + salt.Length];
Array.Copy(bytes, salted, bytes.Length);
Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
return h.ComputeHash(salted);
}
}
}
else
{
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
}
}
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
{
return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
}
public byte[] ComputeHash(PasswordHash hash)
{
int iterations = _defaultIterations;
if (!hash.Parameters.ContainsKey("iterations"))
{
hash.Parameters.Add("iterations", _defaultIterations.ToString(CultureInfo.InvariantCulture));
}
else
{
try
{
iterations = int.Parse(hash.Parameters["iterations"]);
}
catch (Exception e)
{
throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e);
}
}
return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations);
}
public byte[] GenerateSalt()
{
byte[] salt = new byte[64];
_randomNumberGenerator.GetBytes(salt);
return salt;
}
}
}

View File

@@ -224,7 +224,7 @@ namespace Emby.Server.Implementations.Data
});
}
db.ExecuteAll(string.Join(";", queries));
db.ExecuteAll(string.Join(";", queries.ToArray()));
Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
}
@@ -232,6 +232,23 @@ namespace Emby.Server.Implementations.Data
protected virtual int? CacheSize => null;
internal static void CheckOk(int rc)
{
string msg = "";
if (raw.SQLITE_OK != rc)
{
throw CreateException((ErrorCode)rc, msg);
}
}
internal static Exception CreateException(ErrorCode rc, string msg)
{
var exp = new Exception(msg);
return exp;
}
private bool _disposed;
protected void CheckDisposed()
{
@@ -358,6 +375,13 @@ namespace Emby.Server.Implementations.Data
}
}
public class DummyToken : IDisposable
{
public void Dispose()
{
}
}
public static IDisposable Read(this ReaderWriterLockSlim obj)
{
//if (BaseSqliteRepository.ThreadSafeMode > 0)
@@ -366,7 +390,6 @@ namespace Emby.Server.Implementations.Data
//}
return new WriteLockToken(obj);
}
public static IDisposable Write(this ReaderWriterLockSlim obj)
{
//if (BaseSqliteRepository.ThreadSafeMode > 0)

View File

@@ -1,8 +1,11 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Data
@@ -10,12 +13,18 @@ namespace Emby.Server.Implementations.Data
public class CleanDatabaseScheduledTask : ILibraryPostScanTask
{
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepo;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IApplicationPaths _appPaths;
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger)
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
{
_libraryManager = libraryManager;
_itemRepo = itemRepo;
_logger = logger;
_fileSystem = fileSystem;
_appPaths = appPaths;
}
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)

View File

@@ -90,10 +90,9 @@ namespace Emby.Server.Implementations.Data
{
throw new ArgumentNullException(nameof(displayPreferences));
}
if (string.IsNullOrEmpty(displayPreferences.Id))
{
throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences));
throw new ArgumentNullException(nameof(displayPreferences.Id));
}
cancellationToken.ThrowIfCancellationRequested();

File diff suppressed because it is too large Load Diff

View File

@@ -119,9 +119,9 @@ namespace Emby.Server.Implementations.Data
{
list.Add(row[0].ReadGuidFromBlob());
}
catch (Exception ex)
catch
{
Logger.LogError(ex, "Error while getting user");
}
}
}

View File

@@ -55,8 +55,6 @@ namespace Emby.Server.Implementations.Data
{
TryMigrateToLocalUsersTable(connection);
}
RemoveEmptyPasswordHashes();
}
}
@@ -75,38 +73,6 @@ namespace Emby.Server.Implementations.Data
}
}
private void RemoveEmptyPasswordHashes()
{
foreach (var user in RetrieveAllUsers())
{
// If the user password is the sha1 hash of the empty string, remove it
if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
|| !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
{
continue;
}
user.Password = null;
var serialized = _jsonSerializer.SerializeToBytes(user);
using (WriteLock.Write())
using (var connection = CreateConnection())
{
connection.RunInTransaction(db =>
{
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
{
statement.TryBind("@InternalId", user.InternalId);
statement.TryBind("@data", serialized);
statement.MoveNext();
}
}, TransactionMode);
}
}
}
/// <summary>
/// Save a user in the repo
/// </summary>

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using MediaBrowser.Model.Reflection;
namespace Emby.Server.Implementations.Data
{
@@ -9,13 +10,16 @@ namespace Emby.Server.Implementations.Data
/// </summary>
public class TypeMapper
{
private readonly IAssemblyInfo _assemblyInfo;
/// <summary>
/// This holds all the types in the running assemblies so that we can de-serialize properly when we don't have strong types
/// </summary>
private readonly ConcurrentDictionary<string, Type> _typeMap = new ConcurrentDictionary<string, Type>();
public TypeMapper()
public TypeMapper(IAssemblyInfo assemblyInfo)
{
_assemblyInfo = assemblyInfo;
}
/// <summary>
@@ -41,7 +45,8 @@ namespace Emby.Server.Implementations.Data
/// <returns>Type.</returns>
private Type LookupType(string typeName)
{
return AppDomain.CurrentDomain.GetAssemblies()
return _assemblyInfo
.GetCurrentAssemblies()
.Select(a => a.GetType(typeName))
.FirstOrDefault(t => t != null);
}

View File

@@ -11,6 +11,7 @@ namespace Emby.Server.Implementations.Devices
{
private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly object _syncLock = new object();
@@ -52,11 +53,11 @@ namespace Emby.Server.Implementations.Devices
{
var path = CachePath;
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
lock (_syncLock)
{
File.WriteAllText(path, id, Encoding.UTF8);
_fileSystem.WriteAllText(path, id, Encoding.UTF8);
}
}
catch (Exception ex)
@@ -85,10 +86,19 @@ namespace Emby.Server.Implementations.Devices
private string _id;
public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
public DeviceId(
IApplicationPaths appPaths,
ILoggerFactory loggerFactory,
IFileSystem fileSystem)
{
if (fileSystem == null)
{
throw new ArgumentNullException(nameof(fileSystem));
}
_appPaths = appPaths;
_logger = loggerFactory.CreateLogger("SystemId");
_fileSystem = fileSystem;
}
public string Value => _id ?? (_id = GetDeviceId());

View File

@@ -34,6 +34,8 @@ namespace Emby.Server.Implementations.Devices
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly INetworkManager _network;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
@@ -53,13 +55,17 @@ namespace Emby.Server.Implementations.Devices
IUserManager userManager,
IFileSystem fileSystem,
ILibraryMonitor libraryMonitor,
IServerConfigurationManager config)
IServerConfigurationManager config,
ILoggerFactory loggerFactory,
INetworkManager network)
{
_json = json;
_userManager = userManager;
_fileSystem = fileSystem;
_libraryMonitor = libraryMonitor;
_config = config;
_logger = loggerFactory.CreateLogger(nameof(DeviceManager));
_network = network;
_libraryManager = libraryManager;
_localizationManager = localizationManager;
_authRepo = authRepo;
@@ -70,7 +76,7 @@ namespace Emby.Server.Implementations.Devices
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
{
var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
lock (_capabilitiesSyncLock)
{
@@ -233,7 +239,7 @@ namespace Emby.Server.Implementations.Devices
path = Path.Combine(path, file.Name);
path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg");
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false);
@@ -269,7 +275,7 @@ namespace Emby.Server.Implementations.Devices
private void AddCameraUpload(string deviceId, LocalFileInfo file)
{
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
lock (_cameraUploadSyncLock)
{
@@ -311,7 +317,7 @@ namespace Emby.Server.Implementations.Devices
return Task.CompletedTask;
}
Directory.CreateDirectory(path);
_fileSystem.CreateDirectory(path);
var libraryOptions = new LibraryOptions
{
@@ -408,22 +414,24 @@ namespace Emby.Server.Implementations.Devices
{
private readonly DeviceManager _deviceManager;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private ILogger _logger;
public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger)
public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger)
{
_deviceManager = (DeviceManager)deviceManager;
_config = config;
_fileSystem = fileSystem;
_logger = logger;
}
public async Task RunAsync()
public async void Run()
{
if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted)
{
var path = _deviceManager.GetUploadsPath();
if (Directory.Exists(path))
if (_fileSystem.DirectoryExists(path))
{
try
{

View File

@@ -9,14 +9,14 @@ namespace Emby.Server.Implementations.Diagnostics
{
public class CommonProcess : IProcess
{
private readonly Process _process;
public event EventHandler Exited;
private bool _disposed = false;
private bool _hasExited;
private readonly ProcessOptions _options;
private readonly Process _process;
public CommonProcess(ProcessOptions options)
{
StartInfo = options;
_options = options;
var startInfo = new ProcessStartInfo
{
@@ -27,10 +27,10 @@ namespace Emby.Server.Implementations.Diagnostics
CreateNoWindow = options.CreateNoWindow,
RedirectStandardError = options.RedirectStandardError,
RedirectStandardInput = options.RedirectStandardInput,
RedirectStandardOutput = options.RedirectStandardOutput,
ErrorDialog = options.ErrorDialog
RedirectStandardOutput = options.RedirectStandardOutput
};
startInfo.ErrorDialog = options.ErrorDialog;
if (options.IsHidden)
{
@@ -45,22 +45,11 @@ namespace Emby.Server.Implementations.Diagnostics
if (options.EnableRaisingEvents)
{
_process.EnableRaisingEvents = true;
_process.Exited += OnProcessExited;
_process.Exited += _process_Exited;
}
}
public event EventHandler Exited;
public ProcessOptions StartInfo { get; }
public StreamWriter StandardInput => _process.StandardInput;
public StreamReader StandardError => _process.StandardError;
public StreamReader StandardOutput => _process.StandardOutput;
public int ExitCode => _process.ExitCode;
private bool _hasExited;
private bool HasExited
{
get
@@ -83,6 +72,25 @@ namespace Emby.Server.Implementations.Diagnostics
}
}
private void _process_Exited(object sender, EventArgs e)
{
_hasExited = true;
if (Exited != null)
{
Exited(this, e);
}
}
public ProcessOptions StartInfo => _options;
public StreamWriter StandardInput => _process.StandardInput;
public StreamReader StandardError => _process.StandardError;
public StreamReader StandardOutput => _process.StandardOutput;
public int ExitCode => _process.ExitCode;
public void Start()
{
_process.Start();
@@ -100,19 +108,23 @@ namespace Emby.Server.Implementations.Diagnostics
public Task<bool> WaitForExitAsync(int timeMs)
{
// Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
if (HasExited)
{
return Task.FromResult(true);
}
//if (_process.WaitForExit(100))
//{
// return Task.FromResult(true);
//}
//timeMs -= 100;
timeMs = Math.Max(0, timeMs);
var tcs = new TaskCompletionSource<bool>();
var cancellationToken = new CancellationTokenSource(timeMs).Token;
if (HasExited)
{
return Task.FromResult(true);
}
_process.Exited += (sender, args) => tcs.TrySetResult(true);
cancellationToken.Register(() => tcs.TrySetResult(HasExited));
@@ -122,29 +134,7 @@ namespace Emby.Server.Implementations.Diagnostics
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_process?.Dispose();
}
_disposed = true;
}
private void OnProcessExited(object sender, EventArgs e)
{
_hasExited = true;
Exited?.Invoke(this, e);
_process.Dispose();
}
}
}

View File

@@ -5,6 +5,8 @@ using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -19,6 +21,8 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
@@ -32,9 +36,13 @@ namespace Emby.Server.Implementations.Dto
private readonly IItemRepository _itemRepo;
private readonly IImageProcessor _imageProcessor;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IProviderManager _providerManager;
private readonly Func<IChannelManager> _channelManagerFactory;
private readonly IApplicationHost _appHost;
private readonly Func<IDeviceManager> _deviceManager;
private readonly Func<IMediaSourceManager> _mediaSourceManager;
private readonly Func<ILiveTvManager> _livetvManager;
@@ -44,8 +52,12 @@ namespace Emby.Server.Implementations.Dto
IUserDataManager userDataRepository,
IItemRepository itemRepo,
IImageProcessor imageProcessor,
IServerConfigurationManager config,
IFileSystem fileSystem,
IProviderManager providerManager,
Func<IChannelManager> channelManagerFactory,
IApplicationHost appHost,
Func<IDeviceManager> deviceManager,
Func<IMediaSourceManager> mediaSourceManager,
Func<ILiveTvManager> livetvManager)
{
@@ -54,8 +66,12 @@ namespace Emby.Server.Implementations.Dto
_userDataRepository = userDataRepository;
_itemRepo = itemRepo;
_imageProcessor = imageProcessor;
_config = config;
_fileSystem = fileSystem;
_providerManager = providerManager;
_channelManagerFactory = channelManagerFactory;
_appHost = appHost;
_deviceManager = deviceManager;
_mediaSourceManager = mediaSourceManager;
_livetvManager = livetvManager;
}
@@ -79,8 +95,15 @@ namespace Emby.Server.Implementations.Dto
return GetBaseItemDto(item, options, user, owner);
}
public BaseItemDto[] GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
=> GetBaseItemDtos(items, items.Count, options, user, owner);
public BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
{
return GetBaseItemDtos(items, items.Count, options, user, owner);
}
public BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null)
{
return GetBaseItemDtos(items, items.Length, options, user, owner);
}
public BaseItemDto[] GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null)
{
@@ -351,6 +374,10 @@ namespace Emby.Server.Implementations.Dto
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
dto.SongCount = taggedItems.Count(i => i is Audio);
}
else if (item is GameGenre)
{
dto.GameCount = taggedItems.Count(i => i is Game);
}
else
{
// This populates them all and covers Genre, Person, Studio, Year
@@ -358,6 +385,7 @@ namespace Emby.Server.Implementations.Dto
dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
dto.EpisodeCount = taggedItems.Count(i => i is Episode);
dto.GameCount = taggedItems.Count(i => i is Game);
dto.MovieCount = taggedItems.Count(i => i is Movie);
dto.TrailerCount = taggedItems.Count(i => i is Trailer);
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
@@ -504,6 +532,17 @@ namespace Emby.Server.Implementations.Dto
dto.Album = item.Album;
}
private static void SetGameProperties(BaseItemDto dto, Game item)
{
dto.GameSystem = item.GameSystem;
dto.MultiPartGameFiles = item.MultiPartGameFiles;
}
private static void SetGameSystemProperties(BaseItemDto dto, GameSystem item)
{
dto.GameSystem = item.GameSystemName;
}
private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
{
return images
@@ -597,8 +636,7 @@ namespace Emby.Server.Implementations.Dto
}
}).Where(i => i != null)
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.Select(x => x.First())
.DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < people.Count; i++)
@@ -660,6 +698,11 @@ namespace Emby.Server.Implementations.Dto
return _libraryManager.GetMusicGenreId(name);
}
if (owner is Game || owner is GameSystem)
{
return _libraryManager.GetGameGenreId(name);
}
return _libraryManager.GetGenreId(name);
}
@@ -1163,6 +1206,20 @@ namespace Emby.Server.Implementations.Dto
}
}
var game = item as Game;
if (game != null)
{
SetGameProperties(dto, game);
}
var gameSystem = item as GameSystem;
if (gameSystem != null)
{
SetGameSystemProperties(dto, gameSystem);
}
var musicVideo = item as MusicVideo;
if (musicVideo != null)
{
@@ -1371,7 +1428,7 @@ namespace Emby.Server.Implementations.Dto
var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary);
ImageDimensions size;
ImageSize size;
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
@@ -1382,9 +1439,9 @@ namespace Emby.Server.Implementations.Dto
return defaultAspectRatio;
}
int dummyWidth = 200;
int dummyHeight = Convert.ToInt32(dummyWidth / defaultAspectRatio);
size = new ImageDimensions(dummyWidth, dummyHeight);
double dummyWidth = 200;
double dummyHeight = dummyWidth / defaultAspectRatio;
size = new ImageSize(dummyWidth, dummyHeight);
}
else
{
@@ -1395,7 +1452,7 @@ namespace Emby.Server.Implementations.Dto
try
{
size = _imageProcessor.GetImageDimensions(item, imageInfo);
size = _imageProcessor.GetImageSize(item, imageInfo);
if (size.Width <= 0 || size.Height <= 0)
{
@@ -1424,7 +1481,7 @@ namespace Emby.Server.Implementations.Dto
var width = size.Width;
var height = size.Height;
if (width <= 0 || height <= 0)
if (width.Equals(0) || height.Equals(0))
{
return null;
}

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