mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-04-23 21:47:14 -04:00
merging with master to clear merge conflict
This commit is contained in:
commit
a0d31a49a0
143 changed files with 3162 additions and 3215 deletions
8
.copr/Makefile
Normal file
8
.copr/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
|||
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)"
|
|
@ -8,3 +8,4 @@ README.md
|
|||
deployment/*/dist
|
||||
deployment/*/pkg-dist
|
||||
deployment/collect-dist/
|
||||
ci/
|
||||
|
|
103
.drone.yml
103
.drone.yml
|
@ -1,12 +1,111 @@
|
|||
---
|
||||
kind: pipeline
|
||||
name: build
|
||||
name: build-debug
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init --recursive
|
||||
|
||||
- name: build
|
||||
image: microsoft/dotnet:2-sdk
|
||||
commands:
|
||||
- dotnet publish --configuration release --output /release Jellyfin.Server
|
||||
- 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"
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
name: check-abi
|
||||
|
||||
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"
|
||||
|
||||
- name: clone-dotnet-compat
|
||||
image: docker:git
|
||||
commands:
|
||||
- git clone --depth 1 https://github.com/EraYaN/dotnet-compatibility ci/dotnet-compatibility
|
||||
|
||||
- name: build-dotnet-compat
|
||||
image: microsoft/dotnet:2-sdk
|
||||
commands:
|
||||
- dotnet publish "ci/dotnet-compatibility/CompatibilityCheckerCoreCLI" --configuration Release --output "../../ci-tools"
|
||||
|
||||
- name: download-last-nuget-release-common
|
||||
image: plugins/download
|
||||
settings:
|
||||
source: https://www.nuget.org/api/v2/package/Jellyfin.Common
|
||||
destination: ci/Jellyfin.Common.nupkg
|
||||
|
||||
- name: download-last-nuget-release-model
|
||||
image: plugins/download
|
||||
settings:
|
||||
source: https://www.nuget.org/api/v2/package/Jellyfin.Model
|
||||
destination: ci/Jellyfin.Model.nupkg
|
||||
|
||||
- name: download-last-nuget-release-controller
|
||||
image: plugins/download
|
||||
settings:
|
||||
source: https://www.nuget.org/api/v2/package/Jellyfin.Controller
|
||||
destination: ci/Jellyfin.Controller.nupkg
|
||||
|
||||
- name: download-last-nuget-release-naming
|
||||
image: plugins/download
|
||||
settings:
|
||||
source: https://www.nuget.org/api/v2/package/Jellyfin.Naming
|
||||
destination: ci/Jellyfin.Naming.nupkg
|
||||
|
||||
- name: extract-downloaded-nuget-packages
|
||||
image: garthk/unzip
|
||||
commands:
|
||||
- unzip -j ci/Jellyfin.Common.nupkg "*.dll" -d ci/nuget-packages
|
||||
- unzip -j ci/Jellyfin.Model.nupkg "*.dll" -d ci/nuget-packages
|
||||
- unzip -j ci/Jellyfin.Controller.nupkg "*.dll" -d ci/nuget-packages
|
||||
- unzip -j ci/Jellyfin.Naming.nupkg "*.dll" -d ci/nuget-packages
|
||||
|
||||
- name: run-dotnet-compat-common
|
||||
image: microsoft/dotnet:2-runtime
|
||||
err_ignore: true
|
||||
commands:
|
||||
- dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Common.dll ci/ci-release/MediaBrowser.Common.dll
|
||||
|
||||
- name: run-dotnet-compat-model
|
||||
image: microsoft/dotnet:2-runtime
|
||||
err_ignore: true
|
||||
commands:
|
||||
- dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Model.dll ci/ci-release/MediaBrowser.Model.dll
|
||||
|
||||
- name: run-dotnet-compat-controller
|
||||
image: microsoft/dotnet:2-runtime
|
||||
err_ignore: true
|
||||
commands:
|
||||
- dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Controller.dll ci/ci-release/MediaBrowser.Controller.dll
|
||||
|
||||
- name: run-dotnet-compat-naming
|
||||
image: microsoft/dotnet:2-runtime
|
||||
err_ignore: true
|
||||
commands:
|
||||
- dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Emby.Naming.dll ci/ci-release/Emby.Naming.dll
|
||||
|
|
|
@ -15,6 +15,10 @@ 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
|
||||
|
@ -55,15 +59,77 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
|
|||
###############################
|
||||
# Naming Conventions #
|
||||
###############################
|
||||
# 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
|
||||
# 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
|
||||
|
||||
###############################
|
||||
# C# Coding Conventions #
|
||||
###############################
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -263,4 +263,6 @@ deployment/**/pkg-dist/
|
|||
deployment/**/pkg-dist-tmp/
|
||||
deployment/collect-dist/
|
||||
|
||||
jellyfin_version.ini
|
||||
jellyfin_version.ini
|
||||
|
||||
ci/
|
||||
|
|
|
@ -165,7 +165,7 @@ namespace BDInfo
|
|||
foreach (var file in files)
|
||||
{
|
||||
PlaylistFiles.Add(
|
||||
file.Name.ToUpper(), new TSPlaylistFile(this, file, _fileSystem));
|
||||
file.Name.ToUpper(), new TSPlaylistFile(this, file));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ namespace BDInfo
|
|||
foreach (var file in files)
|
||||
{
|
||||
StreamClipFiles.Add(
|
||||
file.Name.ToUpper(), new TSStreamClipFile(file, _fileSystem));
|
||||
file.Name.ToUpper(), new TSStreamClipFile(file));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ namespace BDInfo
|
|||
{
|
||||
public class TSPlaylistFile
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private FileSystemMetadata FileInfo = null;
|
||||
public string FileType = null;
|
||||
public bool IsInitialized = false;
|
||||
|
@ -64,21 +63,19 @@ namespace BDInfo
|
|||
new List<TSGraphicsStream>();
|
||||
|
||||
public TSPlaylistFile(BDROM bdrom,
|
||||
FileSystemMetadata fileInfo, IFileSystem fileSystem)
|
||||
FileSystemMetadata fileInfo)
|
||||
{
|
||||
BDROM = bdrom;
|
||||
FileInfo = fileInfo;
|
||||
_fileSystem = fileSystem;
|
||||
Name = fileInfo.Name.ToUpper();
|
||||
}
|
||||
|
||||
public TSPlaylistFile(BDROM bdrom,
|
||||
string name,
|
||||
List<TSStreamClip> clips, IFileSystem fileSystem)
|
||||
List<TSStreamClip> clips)
|
||||
{
|
||||
BDROM = bdrom;
|
||||
Name = name;
|
||||
_fileSystem = fileSystem;
|
||||
IsCustom = true;
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
|
|
|
@ -28,7 +28,6 @@ namespace BDInfo
|
|||
{
|
||||
public class TSStreamClipFile
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
public FileSystemMetadata FileInfo = null;
|
||||
public string FileType = null;
|
||||
public bool IsValid = false;
|
||||
|
@ -37,10 +36,9 @@ namespace BDInfo
|
|||
public Dictionary<ushort, TSStream> Streams =
|
||||
new Dictionary<ushort, TSStream>();
|
||||
|
||||
public TSStreamClipFile(FileSystemMetadata fileInfo, IFileSystem fileSystem)
|
||||
public TSStreamClipFile(FileSystemMetadata fileInfo)
|
||||
{
|
||||
FileInfo = fileInfo;
|
||||
_fileSystem = fileSystem;
|
||||
Name = fileInfo.Name.ToUpper();
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
- [dkanada](https://github.com/dkanada)
|
||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
|
||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||
- [WillWill56](https://github.com/WillWill56)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
|
13
Dockerfile
13
Dockerfile
|
@ -3,9 +3,8 @@ ARG DOTNET_VERSION=2
|
|||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
||||
&& dotnet clean \
|
||||
&& dotnet publish \
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
RUN dotnet publish \
|
||||
--configuration release \
|
||||
--output /jellyfin \
|
||||
Jellyfin.Server
|
||||
|
@ -18,9 +17,11 @@ RUN apt-get update \
|
|||
libfontconfig1 \
|
||||
&& apt-get clean autoclean \
|
||||
&& apt-get autoremove \
|
||||
&& rm -rf /var/lib/{apt,dpkg,cache,log}
|
||||
&& rm -rf /var/lib/{apt,dpkg,cache,log} \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
COPY --from=ffmpeg / /
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
EXPOSE 8096
|
||||
VOLUME /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache
|
||||
|
|
|
@ -1,24 +1,36 @@
|
|||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.0
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch-arm32v7 as builder
|
||||
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
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
#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 \
|
||||
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 dotnet publish \
|
||||
-r linux-arm \
|
||||
--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 -y ffmpeg
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
EXPOSE 8096
|
||||
VOLUME /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache
|
||||
|
|
|
@ -1,33 +1,37 @@
|
|||
# Requires binfm_misc registration for aarch64
|
||||
# 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-aarch64 as qemu
|
||||
FROM alpine as qemu_extract
|
||||
COPY --from=qemu /usr/bin qemu_user_static.tgz
|
||||
RUN tar -xzvf qemu_user_static.tgz
|
||||
COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz
|
||||
RUN tar -xzvf qemu-aarch64-static.tar.gz
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch-arm64v8 as builder
|
||||
COPY --from=qemu_extract qemu-* /usr/bin
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
#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 \
|
||||
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 dotnet publish \
|
||||
-r linux-arm64 \
|
||||
--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 -y ffmpeg
|
||||
COPY --from=qemu_extract qemu-* /usr/bin
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
EXPOSE 8096
|
||||
VOLUME /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache
|
||||
|
|
|
@ -38,7 +38,9 @@ namespace Emby.Dlna
|
|||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
|
||||
IJsonSerializer jsonSerializer,
|
||||
IServerApplicationHost appHost,
|
||||
IAssemblyInfo assemblyInfo)
|
||||
{
|
||||
_xmlSerializer = xmlSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
|
|
|
@ -36,7 +36,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||
};
|
||||
}
|
||||
|
||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
|
||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
||||
: base(config, logger, xmlReaderSettingsFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ 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;
|
||||
|
@ -733,26 +734,21 @@ namespace Emby.Dlna.PlayTo
|
|||
return (true, null);
|
||||
}
|
||||
|
||||
XElement uPnpResponse;
|
||||
XElement uPnpResponse = null;
|
||||
|
||||
// Handle different variations sent back by devices
|
||||
try
|
||||
{
|
||||
uPnpResponse = XElement.Parse(trackString);
|
||||
uPnpResponse = ParseResponse(trackString);
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 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 (true, null);
|
||||
}
|
||||
_logger.LogError(ex, "Uncaught exception while parsing xml");
|
||||
}
|
||||
|
||||
if (uPnpResponse == null)
|
||||
{
|
||||
_logger.LogError("Failed to parse xml: \n {Xml}", trackString);
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
var e = uPnpResponse.Element(uPnpNamespaces.items);
|
||||
|
@ -762,6 +758,43 @@ namespace Emby.Dlna.PlayTo
|
|||
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("&", "&"));
|
||||
}
|
||||
catch (XmlException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
|
||||
{
|
||||
if (container == null)
|
||||
|
|
|
@ -89,11 +89,6 @@ 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);
|
||||
|
@ -105,6 +100,11 @@ 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)
|
||||
|
|
|
@ -19,7 +19,6 @@ namespace IsoMounter
|
|||
|
||||
private readonly IEnvironmentInfo EnvironmentInfo;
|
||||
private readonly bool ExecutablesAvailable;
|
||||
private readonly IFileSystem FileSystem;
|
||||
private readonly ILogger _logger;
|
||||
private readonly string MountCommand;
|
||||
private readonly string MountPointRoot;
|
||||
|
@ -31,11 +30,10 @@ namespace IsoMounter
|
|||
|
||||
#region Constructor(s)
|
||||
|
||||
public LinuxIsoManager(ILogger logger, IFileSystem fileSystem, IEnvironmentInfo environment, IProcessFactory processFactory)
|
||||
public LinuxIsoManager(ILogger logger, IEnvironmentInfo environment, IProcessFactory processFactory)
|
||||
{
|
||||
|
||||
EnvironmentInfo = environment;
|
||||
FileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
ProcessFactory = processFactory;
|
||||
|
||||
|
|
|
@ -11,101 +11,81 @@ namespace Emby.Notifications
|
|||
public class CoreNotificationTypes : INotificationTypeFactory
|
||||
{
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
public CoreNotificationTypes(ILocalizationManager localization, IServerApplicationHost appHost)
|
||||
public CoreNotificationTypes(ILocalizationManager localization)
|
||||
{
|
||||
_localization = localization;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
|
||||
{
|
||||
var knownTypes = new List<NotificationTypeInfo>
|
||||
var knownTypes = new 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.VideoPlayback.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.AudioPlaybackStopped.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.VideoPlaybackStopped.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.CameraImageUploaded.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.UserLockedOut.ToString()
|
||||
}
|
||||
};
|
||||
|
||||
if (!_appHost.CanSelfUpdate)
|
||||
{
|
||||
knownTypes.Add(new NotificationTypeInfo
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.ApplicationUpdateAvailable.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var type in knownTypes)
|
||||
{
|
||||
|
|
|
@ -5,21 +5,17 @@ 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 Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Notifications
|
||||
|
@ -29,43 +25,40 @@ 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 Timer 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(IInstallationManager installationManager, IActivityManager activityManager, ILocalizationManager localization, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager)
|
||||
public Notifications(
|
||||
IActivityManager activityManager,
|
||||
ILocalizationManager localization,
|
||||
ILogger logger,
|
||||
INotificationManager notificationManager,
|
||||
ILibraryManager libraryManager,
|
||||
IServerApplicationHost appHost,
|
||||
IConfigurationManager config)
|
||||
{
|
||||
_installationManager = installationManager;
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
_taskManager = taskManager;
|
||||
_notificationManager = notificationManager;
|
||||
_libraryManager = libraryManager;
|
||||
_sessionManager = sessionManager;
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_deviceManager = deviceManager;
|
||||
_localization = localization;
|
||||
_activityManager = activityManager;
|
||||
|
||||
_coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray();
|
||||
_coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
|
||||
}
|
||||
|
||||
public Task RunAsync()
|
||||
|
@ -124,10 +117,9 @@ namespace Emby.Notifications
|
|||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
|
||||
private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
|
||||
{
|
||||
// This notification is for users who can't auto-update (aka running as service)
|
||||
if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate)
|
||||
if (!_appHost.HasUpdateAvailable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -145,7 +137,7 @@ namespace Emby.Notifications
|
|||
}
|
||||
|
||||
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
|
||||
void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
|
||||
private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
|
||||
{
|
||||
if (!FilterItem(e.Item))
|
||||
{
|
||||
|
|
|
@ -9,7 +9,6 @@ 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;
|
||||
|
@ -21,13 +20,11 @@ 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, IFileSystem fileSystem, IImageProcessor imageProcessor)
|
||||
public PhotoProvider(ILogger logger, IImageProcessor imageProcessor)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_imageProcessor = imageProcessor;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
|
@ -14,50 +15,44 @@ namespace Emby.Server.Implementations.AppBase
|
|||
/// </summary>
|
||||
protected BaseApplicationPaths(
|
||||
string programDataPath,
|
||||
string appFolderPath,
|
||||
string logDirectoryPath = null,
|
||||
string configurationDirectoryPath = null,
|
||||
string cacheDirectoryPath = null)
|
||||
string logDirectoryPath,
|
||||
string configurationDirectoryPath,
|
||||
string cacheDirectoryPath)
|
||||
{
|
||||
ProgramDataPath = programDataPath;
|
||||
ProgramSystemPath = appFolderPath;
|
||||
LogDirectoryPath = logDirectoryPath;
|
||||
ConfigurationDirectoryPath = configurationDirectoryPath;
|
||||
CachePath = cacheDirectoryPath;
|
||||
|
||||
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 system folder
|
||||
/// </summary>
|
||||
public string ProgramSystemPath { get; private set; }
|
||||
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
|
||||
|
||||
/// <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
|
||||
{
|
||||
if (_dataDirectory == null)
|
||||
{
|
||||
_dataDirectory = Path.Combine(ProgramDataPath, "data");
|
||||
|
||||
Directory.CreateDirectory(_dataDirectory);
|
||||
}
|
||||
|
||||
return _dataDirectory;
|
||||
}
|
||||
get => _dataPath;
|
||||
private set => _dataPath = Directory.CreateDirectory(value).FullName;
|
||||
}
|
||||
|
||||
private const string _virtualDataPath = "%AppDataPath%";
|
||||
public string VirtualDataPath => _virtualDataPath;
|
||||
/// <summary>
|
||||
/// Gets the magic strings used for virtual path manipulation.
|
||||
/// </summary>
|
||||
public string VirtualDataPath { get; } = "%AppDataPath%";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image cache path.
|
||||
|
@ -77,61 +72,17 @@ 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
|
||||
{
|
||||
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;
|
||||
public string LogDirectoryPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the application configuration root directory
|
||||
/// </summary>
|
||||
/// <value>The configuration directory path.</value>
|
||||
public string ConfigurationDirectoryPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_configurationDirectoryPath))
|
||||
{
|
||||
_configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
|
||||
|
||||
Directory.CreateDirectory(_configurationDirectoryPath);
|
||||
}
|
||||
|
||||
return _configurationDirectoryPath;
|
||||
}
|
||||
set => _configurationDirectoryPath = value;
|
||||
}
|
||||
public string ConfigurationDirectoryPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system configuration file
|
||||
|
@ -139,29 +90,11 @@ 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
|
||||
{
|
||||
if (string.IsNullOrEmpty(_cachePath))
|
||||
{
|
||||
_cachePath = Path.Combine(ProgramDataPath, "cache");
|
||||
|
||||
Directory.CreateDirectory(_cachePath);
|
||||
}
|
||||
|
||||
return _cachePath;
|
||||
}
|
||||
set => _cachePath = value;
|
||||
}
|
||||
public string CachePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path to the temp directory within the cache folder
|
||||
|
|
|
@ -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, FileSystem));
|
||||
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
|
||||
return _configuration;
|
||||
}
|
||||
protected set
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace Emby.Server.Implementations.AppBase
|
||||
|
@ -18,9 +17,8 @@ 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, IFileSystem fileSystem)
|
||||
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer)
|
||||
{
|
||||
object configuration;
|
||||
|
||||
|
|
|
@ -104,9 +104,10 @@ using MediaBrowser.Providers.Manager;
|
|||
using MediaBrowser.Providers.Subtitles;
|
||||
using MediaBrowser.WebDashboard.Api;
|
||||
using MediaBrowser.XbmcMetadata.Providers;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ServiceStack;
|
||||
using ServiceStack.Text.Jsv;
|
||||
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
|
@ -122,12 +123,6 @@ namespace Emby.Server.Implementations
|
|||
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
|
||||
public abstract bool CanSelfRestart { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance can self update.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
|
||||
public virtual bool CanSelfUpdate => false;
|
||||
|
||||
public virtual bool CanLaunchWebBrowser
|
||||
{
|
||||
get
|
||||
|
@ -202,7 +197,7 @@ namespace Emby.Server.Implementations
|
|||
/// Gets all concrete types.
|
||||
/// </summary>
|
||||
/// <value>All concrete types.</value>
|
||||
public Tuple<Type, string>[] AllConcreteTypes { get; protected set; }
|
||||
public Type[] AllConcreteTypes { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The disposable parts
|
||||
|
@ -219,8 +214,6 @@ namespace Emby.Server.Implementations
|
|||
|
||||
protected IEnvironmentInfo EnvironmentInfo { get; set; }
|
||||
|
||||
private IBlurayExaminer BlurayExaminer { get; set; }
|
||||
|
||||
public PackageVersionClass SystemUpdateLevel
|
||||
{
|
||||
get
|
||||
|
@ -232,12 +225,7 @@ namespace Emby.Server.Implementations
|
|||
}
|
||||
}
|
||||
|
||||
public virtual string OperatingSystemDisplayName => EnvironmentInfo.OperatingSystemName;
|
||||
|
||||
/// <summary>
|
||||
/// The container
|
||||
/// </summary>
|
||||
protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
|
||||
protected IServiceProvider _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server configuration manager.
|
||||
|
@ -309,7 +297,6 @@ namespace Emby.Server.Implementations
|
|||
/// <value>The user data repository.</value>
|
||||
private IUserDataManager UserDataManager { get; set; }
|
||||
private IUserRepository UserRepository { get; set; }
|
||||
internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
|
||||
internal SqliteItemRepository ItemRepository { get; set; }
|
||||
|
||||
private INotificationManager NotificationManager { get; set; }
|
||||
|
@ -325,6 +312,8 @@ namespace Emby.Server.Implementations
|
|||
private IMediaSourceManager MediaSourceManager { get; set; }
|
||||
private IPlaylistManager PlaylistManager { get; set; }
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the installation manager.
|
||||
/// </summary>
|
||||
|
@ -363,8 +352,10 @@ namespace Emby.Server.Implementations
|
|||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environmentInfo,
|
||||
IImageEncoder imageEncoder,
|
||||
INetworkManager networkManager)
|
||||
INetworkManager networkManager,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
|
||||
// hack alert, until common can target .net core
|
||||
BaseExtensions.CryptographyProvider = CryptographyProvider;
|
||||
|
@ -440,7 +431,7 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
if (_deviceId == null)
|
||||
{
|
||||
_deviceId = new DeviceId(ApplicationPaths, LoggerFactory, FileSystemManager);
|
||||
_deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
|
||||
}
|
||||
|
||||
return _deviceId.Value;
|
||||
|
@ -453,138 +444,58 @@ namespace Emby.Server.Implementations
|
|||
/// <value>The name.</value>
|
||||
public string Name => ApplicationProductName;
|
||||
|
||||
private static Tuple<Assembly, string> GetAssembly(Type type)
|
||||
{
|
||||
var assembly = type.GetTypeInfo().Assembly;
|
||||
|
||||
return new Tuple<Assembly, string>(assembly, null);
|
||||
}
|
||||
|
||||
public virtual IStreamHelper CreateStreamHelper()
|
||||
{
|
||||
return new StreamHelper();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of type and resolves all constructor dependancies
|
||||
/// Creates an instance of type and resolves all constructor dependencies
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public object CreateInstance(Type type)
|
||||
{
|
||||
return Container.GetInstance(type);
|
||||
}
|
||||
=> ActivatorUtilities.CreateInstance(_serviceProvider, type);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of type and resolves all constructor dependencies
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public T CreateInstance<T>()
|
||||
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the instance safe.
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">The type information.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
protected object CreateInstanceSafe(Tuple<Type, string> typeInfo)
|
||||
protected object CreateInstanceSafe(Type type)
|
||||
{
|
||||
var type = typeInfo.Item1;
|
||||
|
||||
try
|
||||
{
|
||||
return Container.GetInstance(type);
|
||||
Logger.LogDebug("Creating instance of {Type}", type);
|
||||
return ActivatorUtilities.CreateInstance(_serviceProvider, type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error creating {type}", type.FullName);
|
||||
// Don't blow up in release mode
|
||||
Logger.LogError(ex, "Error creating {Type}", type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the specified obj.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="obj">The obj.</param>
|
||||
/// <param name="manageLifetime">if set to <c>true</c> [manage lifetime].</param>
|
||||
protected void RegisterSingleInstance<T>(T obj, bool manageLifetime = true)
|
||||
where T : class
|
||||
{
|
||||
Container.RegisterInstance<T>(obj);
|
||||
|
||||
if (manageLifetime)
|
||||
{
|
||||
var disposable = obj as IDisposable;
|
||||
|
||||
if (disposable != null)
|
||||
{
|
||||
DisposableParts.Add(disposable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the single instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="func">The func.</param>
|
||||
protected void RegisterSingleInstance<T>(Func<T> func)
|
||||
where T : class
|
||||
{
|
||||
Container.RegisterSingleton(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves this instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
public T Resolve<T>()
|
||||
{
|
||||
return (T)Container.GetRegistration(typeof(T), true).GetInstance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves this instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
public T TryResolve<T>()
|
||||
{
|
||||
var result = Container.GetRegistration(typeof(T), false);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return (T)result.GetInstance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the assembly.
|
||||
/// </summary>
|
||||
/// <param name="file">The file.</param>
|
||||
/// <returns>Assembly.</returns>
|
||||
protected Tuple<Assembly, string> LoadAssembly(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.Load(File.ReadAllBytes(file));
|
||||
|
||||
return new Tuple<Assembly, string>(assembly, file);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading assembly {File}", file);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public T Resolve<T>() => _serviceProvider.GetService<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the export types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>IEnumerable{Type}.</returns>
|
||||
public IEnumerable<Tuple<Type, string>> GetExportTypes<T>()
|
||||
public IEnumerable<Type> GetExportTypes<T>()
|
||||
{
|
||||
var currentType = typeof(T);
|
||||
|
||||
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i.Item1));
|
||||
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -596,9 +507,10 @@ namespace Emby.Server.Implementations
|
|||
public IEnumerable<T> GetExports<T>(bool manageLifetime = true)
|
||||
{
|
||||
var parts = GetExportTypes<T>()
|
||||
.Select(CreateInstanceSafe)
|
||||
.Select(x => CreateInstanceSafe(x))
|
||||
.Where(i => i != null)
|
||||
.Cast<T>();
|
||||
.Cast<T>()
|
||||
.ToList(); // Convert to list so this isn't executed for each iteration
|
||||
|
||||
if (manageLifetime)
|
||||
{
|
||||
|
@ -611,33 +523,6 @@ namespace Emby.Server.Implementations
|
|||
return parts;
|
||||
}
|
||||
|
||||
public List<Tuple<T, string>> GetExportsWithInfo<T>(bool manageLifetime = true)
|
||||
{
|
||||
var parts = GetExportTypes<T>()
|
||||
.Select(i =>
|
||||
{
|
||||
var obj = CreateInstanceSafe(i);
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new Tuple<T, string>((T)obj, i.Item2);
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
|
||||
if (manageLifetime)
|
||||
{
|
||||
lock (DisposableParts)
|
||||
{
|
||||
DisposableParts.AddRange(parts.Select(i => i.Item1).OfType<IDisposable>());
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the startup tasks.
|
||||
/// </summary>
|
||||
|
@ -691,7 +576,7 @@ namespace Emby.Server.Implementations
|
|||
}
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
public async Task Init(IServiceCollection serviceCollection)
|
||||
{
|
||||
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
||||
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
||||
|
@ -721,7 +606,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
SetHttpLimit();
|
||||
|
||||
await RegisterResources();
|
||||
await RegisterResources(serviceCollection);
|
||||
|
||||
FindParts();
|
||||
}
|
||||
|
@ -736,104 +621,103 @@ namespace Emby.Server.Implementations
|
|||
/// <summary>
|
||||
/// Registers resources that classes will depend on
|
||||
/// </summary>
|
||||
protected async Task RegisterResources()
|
||||
protected async Task RegisterResources(IServiceCollection serviceCollection)
|
||||
{
|
||||
RegisterSingleInstance(ConfigurationManager);
|
||||
RegisterSingleInstance<IApplicationHost>(this);
|
||||
serviceCollection.AddSingleton(ConfigurationManager);
|
||||
serviceCollection.AddSingleton<IApplicationHost>(this);
|
||||
|
||||
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
|
||||
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||
|
||||
RegisterSingleInstance(JsonSerializer);
|
||||
|
||||
RegisterSingleInstance(LoggerFactory, false);
|
||||
RegisterSingleInstance(Logger);
|
||||
serviceCollection.AddSingleton(JsonSerializer);
|
||||
|
||||
RegisterSingleInstance(EnvironmentInfo);
|
||||
serviceCollection.AddSingleton(LoggerFactory);
|
||||
serviceCollection.AddLogging();
|
||||
serviceCollection.AddSingleton(Logger);
|
||||
|
||||
RegisterSingleInstance(FileSystemManager);
|
||||
serviceCollection.AddSingleton(EnvironmentInfo);
|
||||
|
||||
serviceCollection.AddSingleton(FileSystemManager);
|
||||
|
||||
HttpClient = CreateHttpClient();
|
||||
RegisterSingleInstance(HttpClient);
|
||||
serviceCollection.AddSingleton(HttpClient);
|
||||
|
||||
RegisterSingleInstance(NetworkManager);
|
||||
serviceCollection.AddSingleton(NetworkManager);
|
||||
|
||||
IsoManager = new IsoManager();
|
||||
RegisterSingleInstance(IsoManager);
|
||||
serviceCollection.AddSingleton(IsoManager);
|
||||
|
||||
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
|
||||
RegisterSingleInstance(TaskManager);
|
||||
serviceCollection.AddSingleton(TaskManager);
|
||||
|
||||
RegisterSingleInstance(XmlSerializer);
|
||||
serviceCollection.AddSingleton(XmlSerializer);
|
||||
|
||||
ProcessFactory = new ProcessFactory();
|
||||
RegisterSingleInstance(ProcessFactory);
|
||||
serviceCollection.AddSingleton(ProcessFactory);
|
||||
|
||||
var streamHelper = CreateStreamHelper();
|
||||
ApplicationHost.StreamHelper = streamHelper;
|
||||
RegisterSingleInstance(streamHelper);
|
||||
ApplicationHost.StreamHelper = new StreamHelper();
|
||||
serviceCollection.AddSingleton(StreamHelper);
|
||||
|
||||
RegisterSingleInstance(CryptographyProvider);
|
||||
serviceCollection.AddSingleton(CryptographyProvider);
|
||||
|
||||
SocketFactory = new SocketFactory();
|
||||
RegisterSingleInstance(SocketFactory);
|
||||
serviceCollection.AddSingleton(SocketFactory);
|
||||
|
||||
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime);
|
||||
RegisterSingleInstance(InstallationManager);
|
||||
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, ZipClient, PackageRuntime);
|
||||
serviceCollection.AddSingleton(InstallationManager);
|
||||
|
||||
ZipClient = new ZipClient(FileSystemManager);
|
||||
RegisterSingleInstance(ZipClient);
|
||||
ZipClient = new ZipClient();
|
||||
serviceCollection.AddSingleton(ZipClient);
|
||||
|
||||
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
|
||||
RegisterSingleInstance(HttpResultFactory);
|
||||
serviceCollection.AddSingleton(HttpResultFactory);
|
||||
|
||||
RegisterSingleInstance<IServerApplicationHost>(this);
|
||||
RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);
|
||||
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
||||
|
||||
RegisterSingleInstance(ServerConfigurationManager);
|
||||
serviceCollection.AddSingleton(ServerConfigurationManager);
|
||||
|
||||
IAssemblyInfo assemblyInfo = new AssemblyInfo();
|
||||
RegisterSingleInstance(assemblyInfo);
|
||||
var assemblyInfo = new AssemblyInfo();
|
||||
serviceCollection.AddSingleton<IAssemblyInfo>(assemblyInfo);
|
||||
|
||||
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory);
|
||||
await LocalizationManager.LoadAll();
|
||||
RegisterSingleInstance<ILocalizationManager>(LocalizationManager);
|
||||
serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
|
||||
|
||||
BlurayExaminer = new BdInfoExaminer(FileSystemManager);
|
||||
RegisterSingleInstance(BlurayExaminer);
|
||||
serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
|
||||
|
||||
RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
|
||||
serviceCollection.AddSingleton<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
|
||||
|
||||
UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
|
||||
RegisterSingleInstance(UserDataManager);
|
||||
serviceCollection.AddSingleton(UserDataManager);
|
||||
|
||||
UserRepository = GetUserRepository();
|
||||
// This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it
|
||||
RegisterSingleInstance(UserRepository);
|
||||
serviceCollection.AddSingleton(UserRepository);
|
||||
|
||||
var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager);
|
||||
DisplayPreferencesRepository = displayPreferencesRepo;
|
||||
RegisterSingleInstance(DisplayPreferencesRepository);
|
||||
serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo);
|
||||
|
||||
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo);
|
||||
RegisterSingleInstance<IItemRepository>(ItemRepository);
|
||||
serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
|
||||
|
||||
AuthenticationRepository = GetAuthenticationRepository();
|
||||
RegisterSingleInstance(AuthenticationRepository);
|
||||
serviceCollection.AddSingleton(AuthenticationRepository);
|
||||
|
||||
UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager, CryptographyProvider);
|
||||
RegisterSingleInstance(UserManager);
|
||||
UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
|
||||
serviceCollection.AddSingleton(UserManager);
|
||||
|
||||
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
|
||||
RegisterSingleInstance(LibraryManager);
|
||||
serviceCollection.AddSingleton(LibraryManager);
|
||||
|
||||
// TODO wtaylor: investigate use of second music manager
|
||||
var musicManager = new MusicManager(LibraryManager);
|
||||
RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
|
||||
serviceCollection.AddSingleton<IMusicManager>(new MusicManager(LibraryManager));
|
||||
|
||||
LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
|
||||
RegisterSingleInstance(LibraryMonitor);
|
||||
LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
|
||||
serviceCollection.AddSingleton(LibraryMonitor);
|
||||
|
||||
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager));
|
||||
serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
|
||||
|
||||
CertificateInfo = GetCertificateInfo(true);
|
||||
Certificate = GetCertificate(CertificateInfo);
|
||||
|
@ -841,88 +725,88 @@ namespace Emby.Server.Implementations
|
|||
HttpServer = new HttpListenerHost(this,
|
||||
LoggerFactory,
|
||||
ServerConfigurationManager,
|
||||
"web/index.html",
|
||||
_configuration,
|
||||
NetworkManager,
|
||||
JsonSerializer,
|
||||
XmlSerializer,
|
||||
GetParseFn);
|
||||
XmlSerializer);
|
||||
|
||||
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
|
||||
RegisterSingleInstance(HttpServer);
|
||||
serviceCollection.AddSingleton(HttpServer);
|
||||
|
||||
ImageProcessor = GetImageProcessor();
|
||||
RegisterSingleInstance(ImageProcessor);
|
||||
serviceCollection.AddSingleton(ImageProcessor);
|
||||
|
||||
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
|
||||
RegisterSingleInstance(TVSeriesManager);
|
||||
serviceCollection.AddSingleton(TVSeriesManager);
|
||||
|
||||
var encryptionManager = new EncryptionManager();
|
||||
RegisterSingleInstance<IEncryptionManager>(encryptionManager);
|
||||
serviceCollection.AddSingleton<IEncryptionManager>(encryptionManager);
|
||||
|
||||
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager);
|
||||
RegisterSingleInstance(DeviceManager);
|
||||
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager);
|
||||
serviceCollection.AddSingleton(DeviceManager);
|
||||
|
||||
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder);
|
||||
RegisterSingleInstance(MediaSourceManager);
|
||||
serviceCollection.AddSingleton(MediaSourceManager);
|
||||
|
||||
SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager);
|
||||
RegisterSingleInstance(SubtitleManager);
|
||||
serviceCollection.AddSingleton(SubtitleManager);
|
||||
|
||||
ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
|
||||
RegisterSingleInstance(ProviderManager);
|
||||
serviceCollection.AddSingleton(ProviderManager);
|
||||
|
||||
DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager);
|
||||
RegisterSingleInstance(DtoService);
|
||||
DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
|
||||
serviceCollection.AddSingleton(DtoService);
|
||||
|
||||
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
|
||||
RegisterSingleInstance(ChannelManager);
|
||||
serviceCollection.AddSingleton(ChannelManager);
|
||||
|
||||
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
|
||||
RegisterSingleInstance(SessionManager);
|
||||
serviceCollection.AddSingleton(SessionManager);
|
||||
|
||||
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo);
|
||||
RegisterSingleInstance<IDlnaManager>(dlnaManager);
|
||||
serviceCollection.AddSingleton<IDlnaManager>(
|
||||
new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo));
|
||||
|
||||
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
|
||||
RegisterSingleInstance(CollectionManager);
|
||||
serviceCollection.AddSingleton(CollectionManager);
|
||||
|
||||
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
|
||||
RegisterSingleInstance(PlaylistManager);
|
||||
serviceCollection.AddSingleton(PlaylistManager);
|
||||
|
||||
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
|
||||
RegisterSingleInstance(LiveTvManager);
|
||||
serviceCollection.AddSingleton(LiveTvManager);
|
||||
|
||||
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
|
||||
RegisterSingleInstance(UserViewManager);
|
||||
serviceCollection.AddSingleton(UserViewManager);
|
||||
|
||||
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
||||
RegisterSingleInstance(NotificationManager);
|
||||
serviceCollection.AddSingleton(NotificationManager);
|
||||
|
||||
RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
|
||||
serviceCollection.AddSingleton<IDeviceDiscovery>(
|
||||
new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
|
||||
|
||||
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
|
||||
RegisterSingleInstance(ChapterManager);
|
||||
serviceCollection.AddSingleton(ChapterManager);
|
||||
|
||||
RegisterMediaEncoder(assemblyInfo);
|
||||
RegisterMediaEncoder(serviceCollection);
|
||||
|
||||
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
|
||||
RegisterSingleInstance(EncodingManager);
|
||||
serviceCollection.AddSingleton(EncodingManager);
|
||||
|
||||
var activityLogRepo = GetActivityLogRepository();
|
||||
RegisterSingleInstance(activityLogRepo);
|
||||
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
|
||||
serviceCollection.AddSingleton(activityLogRepo);
|
||||
serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
|
||||
|
||||
var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
|
||||
RegisterSingleInstance<IAuthorizationContext>(authContext);
|
||||
RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
|
||||
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
|
||||
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
|
||||
|
||||
AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager);
|
||||
RegisterSingleInstance(AuthService);
|
||||
serviceCollection.AddSingleton(AuthService);
|
||||
|
||||
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
|
||||
RegisterSingleInstance(SubtitleEncoder);
|
||||
serviceCollection.AddSingleton(SubtitleEncoder);
|
||||
|
||||
RegisterSingleInstance(CreateResourceFileManager());
|
||||
serviceCollection.AddSingleton(CreateResourceFileManager());
|
||||
|
||||
displayPreferencesRepo.Initialize();
|
||||
|
||||
|
@ -935,6 +819,8 @@ namespace Emby.Server.Implementations
|
|||
((UserDataManager)UserDataManager).Repository = userDataRepo;
|
||||
ItemRepository.Initialize(userDataRepo, UserManager);
|
||||
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
|
||||
|
||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
}
|
||||
|
||||
protected virtual IBrotliCompressor CreateBrotliCompressor()
|
||||
|
@ -942,11 +828,6 @@ namespace Emby.Server.Implementations
|
|||
return null;
|
||||
}
|
||||
|
||||
private static Func<string, object> GetParseFn(Type propertyType)
|
||||
{
|
||||
return s => JsvReader.GetParseFn(propertyType)(s);
|
||||
}
|
||||
|
||||
public virtual string PackageRuntime => "netcore";
|
||||
|
||||
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo)
|
||||
|
@ -1058,7 +939,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
protected virtual FFMpegInfo GetFFMpegInfo()
|
||||
{
|
||||
return new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, GetFfmpegInstallInfo())
|
||||
return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo())
|
||||
.GetFFMpegInfo(StartupOptions);
|
||||
}
|
||||
|
||||
|
@ -1066,7 +947,7 @@ namespace Emby.Server.Implementations
|
|||
/// Registers the media encoder.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo)
|
||||
private void RegisterMediaEncoder(IServiceCollection serviceCollection)
|
||||
{
|
||||
string encoderPath = null;
|
||||
string probePath = null;
|
||||
|
@ -1098,7 +979,7 @@ namespace Emby.Server.Implementations
|
|||
5000);
|
||||
|
||||
MediaEncoder = mediaEncoder;
|
||||
RegisterSingleInstance(MediaEncoder);
|
||||
serviceCollection.AddSingleton(MediaEncoder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1174,7 +1055,10 @@ namespace Emby.Server.Implementations
|
|||
}
|
||||
|
||||
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||
Plugins = GetExportsWithInfo<IPlugin>().Select(LoadPlugin).Where(i => i != null).ToArray();
|
||||
Plugins = GetExports<IPlugin>()
|
||||
.Select(LoadPlugin)
|
||||
.Where(i => i != null)
|
||||
.ToArray();
|
||||
|
||||
HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>());
|
||||
|
||||
|
@ -1208,19 +1092,15 @@ namespace Emby.Server.Implementations
|
|||
IsoManager.AddParts(GetExports<IIsoMounter>());
|
||||
}
|
||||
|
||||
private IPlugin LoadPlugin(Tuple<IPlugin, string> info)
|
||||
private IPlugin LoadPlugin(IPlugin plugin)
|
||||
{
|
||||
var plugin = info.Item1;
|
||||
var assemblyFilePath = info.Item2;
|
||||
|
||||
try
|
||||
{
|
||||
var assemblyPlugin = plugin as IPluginAssembly;
|
||||
|
||||
if (assemblyPlugin != null)
|
||||
if (plugin is IPluginAssembly assemblyPlugin)
|
||||
{
|
||||
var assembly = plugin.GetType().Assembly;
|
||||
var assemblyName = assembly.GetName();
|
||||
var assemblyFilePath = assembly.Location;
|
||||
|
||||
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
|
||||
|
||||
|
@ -1264,78 +1144,15 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
Logger.LogInformation("Loading assemblies");
|
||||
|
||||
var assemblyInfos = GetComposablePartAssemblies();
|
||||
|
||||
foreach (var assemblyInfo in assemblyInfos)
|
||||
{
|
||||
var assembly = assemblyInfo.Item1;
|
||||
var path = assemblyInfo.Item2;
|
||||
|
||||
if (path == null)
|
||||
AllConcreteTypes = GetComposablePartAssemblies()
|
||||
.SelectMany(x => x.ExportedTypes)
|
||||
.Where(type =>
|
||||
{
|
||||
Logger.LogInformation("Loading {assemblyName}", assembly.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation("Loading {assemblyName} from {path}", assembly.FullName, path);
|
||||
}
|
||||
}
|
||||
|
||||
AllConcreteTypes = assemblyInfos
|
||||
.SelectMany(GetTypes)
|
||||
.Where(info =>
|
||||
{
|
||||
var t = info.Item1;
|
||||
return t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType;
|
||||
return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType;
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of types within an assembly
|
||||
/// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference
|
||||
/// </summary>
|
||||
protected List<Tuple<Type, string>> GetTypes(Tuple<Assembly, string> assemblyInfo)
|
||||
{
|
||||
if (assemblyInfo == null)
|
||||
{
|
||||
return new List<Tuple<Type, string>>();
|
||||
}
|
||||
|
||||
var assembly = assemblyInfo.Item1;
|
||||
|
||||
try
|
||||
{
|
||||
// This null checking really shouldn't be needed but adding it due to some
|
||||
// unhandled exceptions in mono 5.0 that are a little hard to hunt down
|
||||
var types = assembly.GetTypes() ?? new Type[] { };
|
||||
return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
if (ex.LoaderExceptions != null)
|
||||
{
|
||||
foreach (var loaderException in ex.LoaderExceptions)
|
||||
{
|
||||
if (loaderException != null)
|
||||
{
|
||||
Logger.LogError("LoaderException: " + loaderException.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it fails we can still get a list of the Types it was able to resolve
|
||||
var types = ex.Types ?? new Type[] { };
|
||||
return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading types from assembly");
|
||||
|
||||
return new List<Tuple<Type, string>>();
|
||||
}
|
||||
}
|
||||
|
||||
private CertificateInfo CertificateInfo { get; set; }
|
||||
protected X509Certificate Certificate { get; private set; }
|
||||
|
||||
|
@ -1546,151 +1363,64 @@ namespace Emby.Server.Implementations
|
|||
/// Gets the composable part assemblies.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{Assembly}.</returns>
|
||||
protected List<Tuple<Assembly, string>> GetComposablePartAssemblies()
|
||||
protected IEnumerable<Assembly> GetComposablePartAssemblies()
|
||||
{
|
||||
var list = GetPluginAssemblies(ApplicationPaths.PluginsPath);
|
||||
|
||||
// Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
|
||||
// This will prevent the .dll file from getting locked, and allow us to replace it when needed
|
||||
if (Directory.Exists(ApplicationPaths.PluginsPath))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
Logger.LogInformation("Loading assembly {Path}", file);
|
||||
yield return Assembly.LoadFrom(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Include composable parts in the Api assembly
|
||||
list.Add(GetAssembly(typeof(ApiEntryPoint)));
|
||||
yield return typeof(ApiEntryPoint).Assembly;
|
||||
|
||||
// Include composable parts in the Dashboard assembly
|
||||
list.Add(GetAssembly(typeof(DashboardService)));
|
||||
yield return typeof(DashboardService).Assembly;
|
||||
|
||||
// Include composable parts in the Model assembly
|
||||
list.Add(GetAssembly(typeof(SystemInfo)));
|
||||
yield return typeof(SystemInfo).Assembly;
|
||||
|
||||
// Include composable parts in the Common assembly
|
||||
list.Add(GetAssembly(typeof(IApplicationHost)));
|
||||
yield return typeof(IApplicationHost).Assembly;
|
||||
|
||||
// Include composable parts in the Controller assembly
|
||||
list.Add(GetAssembly(typeof(IServerApplicationHost)));
|
||||
yield return typeof(IServerApplicationHost).Assembly;
|
||||
|
||||
// Include composable parts in the Providers assembly
|
||||
list.Add(GetAssembly(typeof(ProviderUtils)));
|
||||
yield return typeof(ProviderUtils).Assembly;
|
||||
|
||||
// Include composable parts in the Photos assembly
|
||||
list.Add(GetAssembly(typeof(PhotoProvider)));
|
||||
yield return typeof(PhotoProvider).Assembly;
|
||||
|
||||
// Emby.Server implementations
|
||||
list.Add(GetAssembly(typeof(InstallationManager)));
|
||||
yield return typeof(InstallationManager).Assembly;
|
||||
|
||||
// MediaEncoding
|
||||
list.Add(GetAssembly(typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder)));
|
||||
yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
|
||||
|
||||
// Dlna
|
||||
list.Add(GetAssembly(typeof(DlnaEntryPoint)));
|
||||
yield return typeof(DlnaEntryPoint).Assembly;
|
||||
|
||||
// Local metadata
|
||||
list.Add(GetAssembly(typeof(BoxSetXmlSaver)));
|
||||
yield return typeof(BoxSetXmlSaver).Assembly;
|
||||
|
||||
// Notifications
|
||||
list.Add(GetAssembly(typeof(NotificationManager)));
|
||||
yield return typeof(NotificationManager).Assembly;
|
||||
|
||||
// Xbmc
|
||||
list.Add(GetAssembly(typeof(ArtistNfoProvider)));
|
||||
yield return typeof(ArtistNfoProvider).Assembly;
|
||||
|
||||
list.AddRange(GetAssembliesWithPartsInternal().Select(i => new Tuple<Assembly, string>(i, null)));
|
||||
|
||||
return list.ToList();
|
||||
foreach (var i in GetAssembliesWithPartsInternal())
|
||||
{
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
|
||||
|
||||
private List<Tuple<Assembly, string>> GetPluginAssemblies(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly))
|
||||
.Select(LoadAssembly)
|
||||
.Where(a => a != null)
|
||||
.ToList();
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return new List<Tuple<Assembly, string>>();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> FilterAssembliesToLoad(IEnumerable<string> paths)
|
||||
{
|
||||
|
||||
var exclude = new[]
|
||||
{
|
||||
"mbplus.dll",
|
||||
"mbintros.dll",
|
||||
"embytv.dll",
|
||||
"Messenger.dll",
|
||||
"Messages.dll",
|
||||
"MediaBrowser.Plugins.TvMazeProvider.dll",
|
||||
"MBBookshelf.dll",
|
||||
"MediaBrowser.Channels.Adult.YouJizz.dll",
|
||||
"MediaBrowser.Channels.Vine-co.dll",
|
||||
"MediaBrowser.Plugins.Vimeo.dll",
|
||||
"MediaBrowser.Channels.Vevo.dll",
|
||||
"MediaBrowser.Plugins.Twitch.dll",
|
||||
"MediaBrowser.Channels.SvtPlay.dll",
|
||||
"MediaBrowser.Plugins.SoundCloud.dll",
|
||||
"MediaBrowser.Plugins.SnesBox.dll",
|
||||
"MediaBrowser.Plugins.RottenTomatoes.dll",
|
||||
"MediaBrowser.Plugins.Revision3.dll",
|
||||
"MediaBrowser.Plugins.NesBox.dll",
|
||||
"MBChapters.dll",
|
||||
"MediaBrowser.Channels.LeagueOfLegends.dll",
|
||||
"MediaBrowser.Plugins.ADEProvider.dll",
|
||||
"MediaBrowser.Channels.BallStreams.dll",
|
||||
"MediaBrowser.Channels.Adult.Beeg.dll",
|
||||
"ChannelDownloader.dll",
|
||||
"Hamstercat.Emby.EmbyBands.dll",
|
||||
"EmbyTV.dll",
|
||||
"MediaBrowser.Channels.HitboxTV.dll",
|
||||
"MediaBrowser.Channels.HockeyStreams.dll",
|
||||
"MediaBrowser.Plugins.ITV.dll",
|
||||
"MediaBrowser.Plugins.Lastfm.dll",
|
||||
"ServerRestart.dll",
|
||||
"MediaBrowser.Plugins.NotifyMyAndroidNotifications.dll",
|
||||
"MetadataViewer.dll"
|
||||
};
|
||||
|
||||
var minRequiredVersions = new Dictionary<string, Version>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "moviethemesongs.dll", new Version(1, 6) },
|
||||
{ "themesongs.dll", new Version(1, 2) }
|
||||
};
|
||||
|
||||
return paths.Where(path =>
|
||||
{
|
||||
var filename = Path.GetFileName(path);
|
||||
if (exclude.Contains(filename ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (minRequiredVersions.TryGetValue(filename, out Version minRequiredVersion))
|
||||
{
|
||||
try
|
||||
{
|
||||
var version = Version.Parse(FileVersionInfo.GetVersionInfo(path).FileVersion);
|
||||
|
||||
if (version < minRequiredVersion)
|
||||
{
|
||||
Logger.LogInformation("Not loading {filename} {version} because the minimum supported version is {minRequiredVersion}. Please update to the newer version", filename, version, minRequiredVersion);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error getting version number from {path}", path);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system status.
|
||||
/// </summary>
|
||||
|
@ -1718,9 +1448,8 @@ namespace Emby.Server.Implementations
|
|||
SupportsHttps = SupportsHttps,
|
||||
HttpsPortNumber = HttpsPort,
|
||||
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
|
||||
OperatingSystemDisplayName = OperatingSystemDisplayName,
|
||||
OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName,
|
||||
CanSelfRestart = CanSelfRestart,
|
||||
CanSelfUpdate = CanSelfUpdate,
|
||||
CanLaunchWebBrowser = CanLaunchWebBrowser,
|
||||
WanAddress = wanAddress,
|
||||
HasUpdateAvailable = HasUpdateAvailable,
|
||||
|
@ -1788,7 +1517,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
public async Task<string> GetWanApiUrl(CancellationToken cancellationToken)
|
||||
{
|
||||
var url = "http://ipv4.icanhazip.com";
|
||||
const string url = "http://ipv4.icanhazip.com";
|
||||
try
|
||||
{
|
||||
using (var response = await HttpClient.Get(new HttpRequestOptions
|
||||
|
@ -2019,21 +1748,6 @@ namespace Emby.Server.Implementations
|
|||
Plugins = list.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the application.
|
||||
/// </summary>
|
||||
/// <param name="package">The package that contains the update</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
public async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
await InstallationManager.InstallPackage(package, false, progress, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
HasUpdateAvailable = false;
|
||||
|
||||
OnApplicationUpdated(package);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns localhost in the case of no external dns, and the hostname if the
|
||||
/// dns is prefixed with a valid Uri prefix.
|
||||
|
|
|
@ -14,11 +14,9 @@ namespace Emby.Server.Implementations.Archiving
|
|||
/// </summary>
|
||||
public class ZipClient : IZipClient
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public ZipClient(IFileSystem fileSystem)
|
||||
public ZipClient()
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
|
|
@ -10,14 +10,18 @@ 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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -342,14 +342,12 @@ 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, IFileSystem fileSystem, ILogger logger)
|
||||
public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger)
|
||||
{
|
||||
_collectionManager = (CollectionManager)collectionManager;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
12
Emby.Server.Implementations/ConfigurationOptions.cs
Normal file
12
Emby.Server.Implementations/ConfigurationOptions.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
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"}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -224,7 +224,7 @@ namespace Emby.Server.Implementations.Data
|
|||
});
|
||||
}
|
||||
|
||||
db.ExecuteAll(string.Join(";", queries.ToArray()));
|
||||
db.ExecuteAll(string.Join(";", queries));
|
||||
Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
|
||||
}
|
||||
|
||||
|
@ -232,23 +232,6 @@ 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()
|
||||
{
|
||||
|
@ -375,13 +358,6 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
}
|
||||
|
||||
public class DummyToken : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable Read(this ReaderWriterLockSlim obj)
|
||||
{
|
||||
//if (BaseSqliteRepository.ThreadSafeMode > 0)
|
||||
|
@ -390,6 +366,7 @@ namespace Emby.Server.Implementations.Data
|
|||
//}
|
||||
return new WriteLockToken(obj);
|
||||
}
|
||||
|
||||
public static IDisposable Write(this ReaderWriterLockSlim obj)
|
||||
{
|
||||
//if (BaseSqliteRepository.ThreadSafeMode > 0)
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
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
|
||||
|
@ -13,18 +10,12 @@ 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, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
|
||||
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_itemRepo = itemRepo;
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
|
|
|
@ -536,7 +536,7 @@ namespace Emby.Server.Implementations.Data
|
|||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
SaveItems(new List<BaseItem> { item }, cancellationToken);
|
||||
SaveItems(new [] { item }, cancellationToken);
|
||||
}
|
||||
|
||||
public void SaveImages(BaseItem item)
|
||||
|
@ -576,7 +576,7 @@ namespace Emby.Server.Implementations.Data
|
|||
/// or
|
||||
/// cancellationToken
|
||||
/// </exception>
|
||||
public void SaveItems(List<BaseItem> items, CancellationToken cancellationToken)
|
||||
public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
|
@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
CheckDisposed();
|
||||
|
||||
var tuples = new List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>>();
|
||||
var tuples = new List<(BaseItem, List<Guid>, BaseItem, string, List<string>)>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
var ancestorIds = item.SupportsAncestors ?
|
||||
|
@ -599,7 +599,7 @@ namespace Emby.Server.Implementations.Data
|
|||
var userdataKey = item.GetUserDataKeys().FirstOrDefault();
|
||||
var inheritedTags = item.GetInheritedTags();
|
||||
|
||||
tuples.Add(new Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>(item, ancestorIds, topParent, userdataKey, inheritedTags));
|
||||
tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
|
||||
}
|
||||
|
||||
using (WriteLock.Write())
|
||||
|
@ -615,7 +615,7 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
}
|
||||
|
||||
private void SaveItemsInTranscation(IDatabaseConnection db, List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>> tuples)
|
||||
private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List<Guid>, BaseItem, string, List<string>)> tuples)
|
||||
{
|
||||
var statements = PrepareAllSafe(db, new string[]
|
||||
{
|
||||
|
@ -966,7 +966,7 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
if (item.ExtraIds.Length > 0)
|
||||
{
|
||||
saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds.ToArray()));
|
||||
saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1183,9 +1183,9 @@ namespace Emby.Server.Implementations.Data
|
|||
/// <exception cref="ArgumentException"></exception>
|
||||
public BaseItem RetrieveItem(Guid id)
|
||||
{
|
||||
if (id.Equals(Guid.Empty))
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
throw new ArgumentException(nameof(id), "Guid can't be empty");
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
@ -2079,14 +2079,14 @@ namespace Emby.Server.Implementations.Data
|
|||
return false;
|
||||
}
|
||||
|
||||
var sortingFields = query.OrderBy.Select(i => i.Item1);
|
||||
var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.SeriesDatePlayed, StringComparer.OrdinalIgnoreCase)
|
||||
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
|
||||
|| sortingFields.Contains(ItemSortBy.IsPlayed)
|
||||
|| sortingFields.Contains(ItemSortBy.IsUnplayed)
|
||||
|| sortingFields.Contains(ItemSortBy.PlayCount)
|
||||
|| sortingFields.Contains(ItemSortBy.DatePlayed)
|
||||
|| sortingFields.Contains(ItemSortBy.SeriesDatePlayed)
|
||||
|| query.IsFavoriteOrLiked.HasValue
|
||||
|| query.IsFavorite.HasValue
|
||||
|| query.IsResumable.HasValue
|
||||
|
@ -2094,9 +2094,9 @@ namespace Emby.Server.Implementations.Data
|
|||
|| query.IsLiked.HasValue;
|
||||
}
|
||||
|
||||
private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields))
|
||||
private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields))
|
||||
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
|
||||
.ToList();
|
||||
.ToArray();
|
||||
|
||||
private string[] GetColumnNamesFromField(ItemFields field)
|
||||
{
|
||||
|
@ -2151,18 +2151,26 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Program",
|
||||
"TvChannel",
|
||||
"LiveTvProgram",
|
||||
"LiveTvTvChannel"
|
||||
};
|
||||
|
||||
private bool HasProgramAttributes(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (_programExcludeParentTypes.Contains(query.ParentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -2172,29 +2180,18 @@ namespace Emby.Server.Implementations.Data
|
|||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"Program",
|
||||
"TvChannel",
|
||||
"LiveTvProgram",
|
||||
"LiveTvTvChannel"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _programTypes.Contains(x));
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"TvChannel",
|
||||
"LiveTvTvChannel"
|
||||
};
|
||||
|
||||
private bool HasServiceName(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (_programExcludeParentTypes.Contains(query.ParentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -2204,27 +2201,18 @@ namespace Emby.Server.Implementations.Data
|
|||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"TvChannel",
|
||||
"LiveTvTvChannel"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x));
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Program",
|
||||
"LiveTvProgram"
|
||||
};
|
||||
|
||||
private bool HasStartDate(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (_programExcludeParentTypes.Contains(query.ParentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -2234,13 +2222,7 @@ namespace Emby.Server.Implementations.Data
|
|||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"Program",
|
||||
"LiveTvProgram"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x));
|
||||
}
|
||||
|
||||
private bool HasEpisodeAttributes(InternalItemsQuery query)
|
||||
|
@ -2263,16 +2245,26 @@ namespace Emby.Server.Implementations.Data
|
|||
return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
|
||||
private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Audio",
|
||||
"MusicAlbum",
|
||||
"MusicVideo",
|
||||
"AudioBook",
|
||||
"AudioPodcast"
|
||||
};
|
||||
|
||||
private bool HasArtistFields(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (_artistExcludeParentTypes.Contains(query.ParentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -2282,18 +2274,18 @@ namespace Emby.Server.Implementations.Data
|
|||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"Audio",
|
||||
"MusicAlbum",
|
||||
"MusicVideo",
|
||||
"AudioBook",
|
||||
"AudioPodcast"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x));
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Audio",
|
||||
"MusicAlbum",
|
||||
"MusicVideo",
|
||||
"AudioBook",
|
||||
"AudioPodcast"
|
||||
};
|
||||
|
||||
private bool HasSeriesFields(InternalItemsQuery query)
|
||||
{
|
||||
if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -2306,26 +2298,18 @@ namespace Emby.Server.Implementations.Data
|
|||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"Book",
|
||||
"AudioBook",
|
||||
"Episode",
|
||||
"Season"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x));
|
||||
}
|
||||
|
||||
private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns)
|
||||
private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable<string> startColumns)
|
||||
{
|
||||
var list = startColumns.ToList();
|
||||
|
||||
foreach (var field in allFields)
|
||||
foreach (var field in _allFields)
|
||||
{
|
||||
if (!HasField(query, field))
|
||||
{
|
||||
foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList())
|
||||
foreach (var fieldToRemove in GetColumnNamesFromField(field))
|
||||
{
|
||||
list.Remove(fieldToRemove);
|
||||
}
|
||||
|
@ -2419,11 +2403,14 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
list.Add(builder.ToString());
|
||||
|
||||
var excludeIds = query.ExcludeItemIds.ToList();
|
||||
excludeIds.Add(item.Id);
|
||||
excludeIds.AddRange(item.ExtraIds);
|
||||
var oldLen = query.ExcludeItemIds.Length;
|
||||
var newLen = oldLen + item.ExtraIds.Length + 1;
|
||||
var excludeIds = new Guid[newLen];
|
||||
query.ExcludeItemIds.CopyTo(excludeIds, 0);
|
||||
excludeIds[oldLen] = item.Id;
|
||||
item.ExtraIds.CopyTo(excludeIds, oldLen + 1);
|
||||
|
||||
query.ExcludeItemIds = excludeIds.ToArray();
|
||||
query.ExcludeItemIds = excludeIds;
|
||||
query.ExcludeProviderIds = item.ProviderIds;
|
||||
}
|
||||
|
||||
|
@ -2444,7 +2431,7 @@ namespace Emby.Server.Implementations.Data
|
|||
list.Add(builder.ToString());
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
return list;
|
||||
}
|
||||
|
||||
private void BindSearchParams(InternalItemsQuery query, IStatement statement)
|
||||
|
@ -2723,18 +2710,17 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
private void AddItem(List<BaseItem> items, BaseItem newItem)
|
||||
{
|
||||
var providerIds = newItem.ProviderIds.ToList();
|
||||
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
|
||||
foreach (var providerId in providerIds)
|
||||
foreach (var providerId in newItem.ProviderIds)
|
||||
{
|
||||
if (providerId.Key == MetadataProviders.TmdbCollection.ToString())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.GetProviderId(providerId.Key) == providerId.Value)
|
||||
{
|
||||
if (newItem.SourceType == SourceType.Library)
|
||||
|
@ -2753,10 +2739,10 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
|
||||
|
||||
int slowThreshold = 1000;
|
||||
int slowThreshold = 100;
|
||||
|
||||
#if DEBUG
|
||||
slowThreshold = 250;
|
||||
slowThreshold = 10;
|
||||
#endif
|
||||
|
||||
if (elapsed >= slowThreshold)
|
||||
|
@ -2806,7 +2792,7 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
var whereText = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
" where " + string.Join(" AND ", whereClauses);
|
||||
|
||||
commandText += whereText
|
||||
+ GetGroupBy(query)
|
||||
|
@ -2930,25 +2916,31 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
private string GetOrderByText(InternalItemsQuery query)
|
||||
{
|
||||
var orderBy = query.OrderBy.ToList();
|
||||
var enableOrderInversion = false;
|
||||
|
||||
if (query.SimilarTo != null && orderBy.Count == 0)
|
||||
if (string.IsNullOrEmpty(query.SearchTerm))
|
||||
{
|
||||
orderBy.Add(new ValueTuple<string, SortOrder>("SimilarityScore", SortOrder.Descending));
|
||||
orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
|
||||
int oldLen = query.OrderBy.Length;
|
||||
|
||||
if (query.SimilarTo != null && oldLen == 0)
|
||||
{
|
||||
var arr = new (string, SortOrder)[oldLen + 2];
|
||||
query.OrderBy.CopyTo(arr, 0);
|
||||
arr[oldLen] = ("SimilarityScore", SortOrder.Descending);
|
||||
arr[oldLen + 1] = (ItemSortBy.Random, SortOrder.Ascending);
|
||||
query.OrderBy = arr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
query.OrderBy = new []
|
||||
{
|
||||
("SearchScore", SortOrder.Descending),
|
||||
(ItemSortBy.SortName, SortOrder.Ascending)
|
||||
};
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.SearchTerm))
|
||||
{
|
||||
orderBy = new List<(string, SortOrder)>();
|
||||
orderBy.Add(new ValueTuple<string, SortOrder>("SearchScore", SortOrder.Descending));
|
||||
orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
|
||||
}
|
||||
var orderBy = query.OrderBy;
|
||||
|
||||
query.OrderBy = orderBy.ToArray();
|
||||
|
||||
if (orderBy.Count == 0)
|
||||
if (orderBy.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
@ -2957,6 +2949,7 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
var columnMap = MapOrderByField(i.Item1, query);
|
||||
var columnAscending = i.Item2 == SortOrder.Ascending;
|
||||
const bool enableOrderInversion = false;
|
||||
if (columnMap.Item2 && enableOrderInversion)
|
||||
{
|
||||
columnAscending = !columnAscending;
|
||||
|
@ -2968,7 +2961,7 @@ namespace Emby.Server.Implementations.Data
|
|||
}));
|
||||
}
|
||||
|
||||
private ValueTuple<string, bool> MapOrderByField(string name, InternalItemsQuery query)
|
||||
private (string, bool) MapOrderByField(string name, InternalItemsQuery query)
|
||||
{
|
||||
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -3218,7 +3211,7 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
var whereText = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
" where " + string.Join(" AND ", whereClauses);
|
||||
|
||||
commandText += whereText
|
||||
+ GetGroupBy(query)
|
||||
|
@ -4378,7 +4371,7 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
else if (query.Years.Length > 1)
|
||||
{
|
||||
var val = string.Join(",", query.Years.ToArray());
|
||||
var val = string.Join(",", query.Years);
|
||||
|
||||
whereClauses.Add("ProductionYear in (" + val + ")");
|
||||
}
|
||||
|
@ -4952,7 +4945,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
return result;
|
||||
}
|
||||
|
||||
return new[] { value }.Where(IsValidType);
|
||||
if (IsValidType(value))
|
||||
{
|
||||
return new[] { value };
|
||||
}
|
||||
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void DeleteItem(Guid id, CancellationToken cancellationToken)
|
||||
|
@ -5215,32 +5213,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
}
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName);
|
||||
}
|
||||
|
@ -5317,7 +5315,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
}
|
||||
}
|
||||
|
||||
private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
|
||||
private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
|
@ -5335,7 +5333,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
|
||||
var typeClause = itemValueTypes.Length == 1 ?
|
||||
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
|
||||
("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")");
|
||||
("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
|
||||
|
||||
InternalItemsQuery typeSubQuery = null;
|
||||
|
||||
|
@ -5363,11 +5361,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
|
||||
whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
|
||||
|
||||
var typeWhereText = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses);
|
||||
|
||||
itemCountColumnQuery += typeWhereText;
|
||||
itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses);
|
||||
|
||||
itemCountColumns = new Dictionary<string, string>()
|
||||
{
|
||||
|
@ -5400,7 +5394,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
IsSeries = query.IsSeries
|
||||
};
|
||||
|
||||
columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList();
|
||||
columns = GetFinalColumnsToSelect(query, columns);
|
||||
|
||||
var commandText = "select "
|
||||
+ string.Join(",", columns)
|
||||
|
@ -5492,8 +5486,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
{
|
||||
return connection.RunInTransaction(db =>
|
||||
{
|
||||
var list = new List<Tuple<BaseItem, ItemCounts>>();
|
||||
var result = new QueryResult<Tuple<BaseItem, ItemCounts>>();
|
||||
var list = new List<(BaseItem, ItemCounts)>();
|
||||
var result = new QueryResult<(BaseItem, ItemCounts)>();
|
||||
|
||||
var statements = PrepareAllSafe(db, statementTexts);
|
||||
|
||||
|
@ -5531,7 +5525,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
{
|
||||
var countStartColumn = columns.Count - 1;
|
||||
|
||||
list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(row, countStartColumn, typesToCount)));
|
||||
list.Add((item, GetItemCounts(row, countStartColumn, typesToCount)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6198,6 +6192,5 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ namespace Emby.Server.Implementations.Devices
|
|||
{
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
|
@ -86,19 +85,10 @@ namespace Emby.Server.Implementations.Devices
|
|||
|
||||
private string _id;
|
||||
|
||||
public DeviceId(
|
||||
IApplicationPaths appPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IFileSystem fileSystem)
|
||||
public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (fileSystem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
_appPaths = appPaths;
|
||||
_logger = loggerFactory.CreateLogger("SystemId");
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public string Value => _id ?? (_id = GetDeviceId());
|
||||
|
|
|
@ -34,8 +34,6 @@ 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;
|
||||
|
||||
|
@ -55,17 +53,13 @@ namespace Emby.Server.Implementations.Devices
|
|||
IUserManager userManager,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryMonitor libraryMonitor,
|
||||
IServerConfigurationManager config,
|
||||
ILoggerFactory loggerFactory,
|
||||
INetworkManager network)
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_json = json;
|
||||
_userManager = userManager;
|
||||
_fileSystem = fileSystem;
|
||||
_libraryMonitor = libraryMonitor;
|
||||
_config = config;
|
||||
_logger = loggerFactory.CreateLogger(nameof(DeviceManager));
|
||||
_network = network;
|
||||
_libraryManager = libraryManager;
|
||||
_localizationManager = localizationManager;
|
||||
_authRepo = authRepo;
|
||||
|
@ -414,14 +408,12 @@ 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, IFileSystem fileSystem, ILogger logger)
|
||||
public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger)
|
||||
{
|
||||
_deviceManager = (DeviceManager)deviceManager;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,13 +36,9 @@ 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;
|
||||
|
||||
|
@ -52,12 +48,8 @@ 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)
|
||||
{
|
||||
|
@ -66,12 +58,8 @@ 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;
|
||||
}
|
||||
|
|
|
@ -22,9 +22,11 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.22.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="4.4.2" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
|
||||
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
class UserDataChangeNotifier : IServerEntryPoint
|
||||
public class UserDataChangeNotifier : IServerEntryPoint
|
||||
{
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILogger _logger;
|
||||
|
|
|
@ -3,27 +3,19 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.FFMpeg
|
||||
{
|
||||
public class FFMpegLoader
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IZipClient _zipClient;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly FFMpegInstallInfo _ffmpegInstallInfo;
|
||||
|
||||
public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo)
|
||||
public FFMpegLoader(IApplicationPaths appPaths, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo)
|
||||
{
|
||||
_logger = logger;
|
||||
_appPaths = appPaths;
|
||||
_httpClient = httpClient;
|
||||
_zipClient = zipClient;
|
||||
_fileSystem = fileSystem;
|
||||
_ffmpegInstallInfo = ffmpegInstallInfo;
|
||||
}
|
||||
|
@ -115,8 +107,7 @@ namespace Emby.Server.Implementations.FFMpeg
|
|||
var encoderFilename = Path.GetFileName(info.EncoderPath);
|
||||
var probeFilename = Path.GetFileName(info.ProbePath);
|
||||
|
||||
foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath)
|
||||
.ToList())
|
||||
foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath))
|
||||
{
|
||||
var allFiles = _fileSystem.GetFilePaths(directory, true).ToList();
|
||||
|
||||
|
|
|
@ -66,11 +66,6 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||
|
||||
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
|
||||
ServicePointManager.Expect100Continue = false;
|
||||
|
||||
#if NET46
|
||||
// Trakt requests sometimes fail without this
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -106,23 +101,6 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||
return client;
|
||||
}
|
||||
|
||||
private static WebRequest CreateWebRequest(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
return WebRequest.Create(url);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
//Webrequest creation does fail on MONO randomly when using WebRequest.Create
|
||||
//the issue occurs in the GetCreator method here: http://www.oschina.net/code/explore/mono-2.8.1/mcs/class/System/System.Net/WebRequest.cs
|
||||
|
||||
var type = Type.GetType("System.Net.HttpRequestCreator, System, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089");
|
||||
var creator = Activator.CreateInstance(type, nonPublic: true) as IWebRequestCreate;
|
||||
return creator.Create(new Uri(url)) as HttpWebRequest;
|
||||
}
|
||||
}
|
||||
|
||||
private WebRequest GetRequest(HttpRequestOptions options, string method)
|
||||
{
|
||||
string url = options.Url;
|
||||
|
@ -135,7 +113,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||
url = url.Replace(userInfo + "@", string.Empty);
|
||||
}
|
||||
|
||||
var request = CreateWebRequest(url);
|
||||
var request = WebRequest.Create(url);
|
||||
|
||||
if (request is HttpWebRequest httpWebRequest)
|
||||
{
|
||||
|
@ -627,14 +605,16 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||
|
||||
var exception = new HttpException(webException.Message, webException);
|
||||
|
||||
var response = webException.Response as HttpWebResponse;
|
||||
if (response != null)
|
||||
using (var response = webException.Response as HttpWebResponse)
|
||||
{
|
||||
exception.StatusCode = response.StatusCode;
|
||||
|
||||
if ((int)response.StatusCode == 429)
|
||||
if (response != null)
|
||||
{
|
||||
client.LastTimeout = DateTime.UtcNow;
|
||||
exception.StatusCode = response.StatusCode;
|
||||
|
||||
if ((int)response.StatusCode == 429)
|
||||
{
|
||||
client.LastTimeout = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,9 @@ using MediaBrowser.Model.Events;
|
|||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ServiceStack.Text.Jsv;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
|
@ -53,20 +55,20 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
IServerApplicationHost applicationHost,
|
||||
ILoggerFactory loggerFactory,
|
||||
IServerConfigurationManager config,
|
||||
string defaultRedirectPath,
|
||||
IConfiguration configuration,
|
||||
INetworkManager networkManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IXmlSerializer xmlSerializer,
|
||||
Func<Type, Func<string, object>> funcParseFn)
|
||||
IXmlSerializer xmlSerializer)
|
||||
{
|
||||
_appHost = applicationHost;
|
||||
_logger = loggerFactory.CreateLogger("HttpServer");
|
||||
_config = config;
|
||||
DefaultRedirectPath = defaultRedirectPath;
|
||||
DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
|
||||
_networkManager = networkManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_xmlSerializer = xmlSerializer;
|
||||
_funcParseFn = funcParseFn;
|
||||
|
||||
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
|
||||
|
||||
Instance = this;
|
||||
ResponseFilters = Array.Empty<Action<IRequest, IResponse, object>>();
|
||||
|
|
|
@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// </summary>
|
||||
private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
|
||||
{
|
||||
var result = new StreamWriter(content, contentType, _logger);
|
||||
var result = new StreamWriter(content, contentType);
|
||||
|
||||
if (responseHeaders == null)
|
||||
{
|
||||
|
@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
content = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
result = new StreamWriter(content, contentType, contentLength, _logger);
|
||||
result = new StreamWriter(content, contentType, contentLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
responseHeaders = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
|
||||
{
|
||||
responseHeaders["Expires"] = "-1";
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
bytes = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
result = new StreamWriter(bytes, contentType, contentLength, _logger);
|
||||
result = new StreamWriter(bytes, contentType, contentLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
responseHeaders = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
|
||||
{
|
||||
responseHeaders["Expires"] = "-1";
|
||||
}
|
||||
|
@ -277,9 +277,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null)
|
||||
{
|
||||
var contentType = request.ResponseContentType;
|
||||
// TODO: @bond use Span and .Equals
|
||||
var contentType = request.ResponseContentType?.Split(';')[0].Trim().ToLowerInvariant();
|
||||
|
||||
switch (GetRealContentType(contentType))
|
||||
switch (contentType)
|
||||
{
|
||||
case "application/xml":
|
||||
case "text/xml":
|
||||
|
@ -333,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
if (isHeadRequest)
|
||||
{
|
||||
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger);
|
||||
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength);
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = new StreamWriter(content, contentType, contentLength, _logger);
|
||||
var result = new StreamWriter(content, contentType, contentLength);
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
return result;
|
||||
}
|
||||
|
@ -348,13 +349,19 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
private byte[] Compress(byte[] bytes, string compressionType)
|
||||
{
|
||||
if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return CompressBrotli(bytes);
|
||||
}
|
||||
|
||||
if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Deflate(bytes);
|
||||
}
|
||||
|
||||
if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GZip(bytes);
|
||||
}
|
||||
|
||||
throw new NotSupportedException(compressionType);
|
||||
}
|
||||
|
@ -390,13 +397,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
}
|
||||
}
|
||||
|
||||
public static string GetRealContentType(string contentType)
|
||||
{
|
||||
return contentType == null
|
||||
? null
|
||||
: contentType.Split(';')[0].ToLowerInvariant().Trim();
|
||||
}
|
||||
|
||||
private static string SerializeToXmlString(object from)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
|
@ -422,18 +422,20 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// <summary>
|
||||
/// Pres the process optimized result.
|
||||
/// </summary>
|
||||
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
|
||||
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
|
||||
{
|
||||
bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
|
||||
|
||||
if (!noCache)
|
||||
{
|
||||
if (IsNotModified(requestContext, cacheKey))
|
||||
{
|
||||
AddAgeHeader(responseHeaders, lastDateModified);
|
||||
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
|
||||
DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader);
|
||||
|
||||
var result = new HttpResult(Array.Empty<byte>(), contentType ?? "text/html", HttpStatusCode.NotModified);
|
||||
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
|
||||
{
|
||||
AddAgeHeader(responseHeaders, options.DateLastModified);
|
||||
|
||||
var result = new HttpResult(Array.Empty<byte>(), options.ContentType ?? "text/html", HttpStatusCode.NotModified);
|
||||
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
|
||||
|
@ -441,8 +443,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
}
|
||||
}
|
||||
|
||||
AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -487,9 +487,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
|
||||
}
|
||||
|
||||
var cacheKey = path + options.DateLastModified.Value.Ticks;
|
||||
|
||||
options.CacheKey = cacheKey.GetMD5();
|
||||
options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare));
|
||||
|
||||
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
@ -520,7 +517,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
return GetStaticResult(requestContext, new StaticResultOptions
|
||||
{
|
||||
CacheDuration = cacheDuration,
|
||||
CacheKey = cacheKey,
|
||||
ContentFactory = factoryFn,
|
||||
ContentType = contentType,
|
||||
DateLastModified = lastDateModified,
|
||||
|
@ -534,14 +530,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var contentType = options.ContentType;
|
||||
var etag = requestContext.Headers.Get("If-None-Match");
|
||||
var cacheKey = etag != null ? new Guid(etag.Trim('\"')) : Guid.Empty;
|
||||
if (!cacheKey.Equals(Guid.Empty))
|
||||
if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since")))
|
||||
{
|
||||
var key = cacheKey.ToString("N");
|
||||
|
||||
// See if the result is already cached in the browser
|
||||
var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType);
|
||||
var result = GetCachedResult(requestContext, options.ResponseHeaders, options);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
|
@ -553,6 +545,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
var isHeadRequest = options.IsHeadRequest || string.Equals(requestContext.Verb, "HEAD", StringComparison.OrdinalIgnoreCase);
|
||||
var factoryFn = options.ContentFactory;
|
||||
var responseHeaders = options.ResponseHeaders;
|
||||
AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified);
|
||||
AddAgeHeader(responseHeaders, options.DateLastModified);
|
||||
|
||||
var rangeHeader = requestContext.Headers.Get("Range");
|
||||
|
||||
|
@ -566,21 +560,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
};
|
||||
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
// Generate an ETag based on identifying information - TODO read contents from filesystem instead?
|
||||
var responseId = $"{hasHeaders.ContentType}{options.Path}{hasHeaders.TotalContentLength}";
|
||||
var hashedId = MD5.Create().ComputeHash(Encoding.Default.GetBytes(responseId));
|
||||
hasHeaders.Headers["ETag"] = new Guid(hashedId).ToString("N");
|
||||
|
||||
return hasHeaders;
|
||||
}
|
||||
|
||||
var stream = await factoryFn().ConfigureAwait(false);
|
||||
// Generate an etag based on stream content
|
||||
var streamHash = MD5.Create().ComputeHash(stream);
|
||||
var newEtag = new Guid(streamHash).ToString("N");
|
||||
|
||||
// reset position so the response can re-use it -- TODO is this ok?
|
||||
stream.Position = 0;
|
||||
|
||||
var totalContentLength = options.ContentLength;
|
||||
if (!totalContentLength.HasValue)
|
||||
|
@ -603,7 +586,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
};
|
||||
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
hasHeaders.Headers["ETag"] = newEtag;
|
||||
return hasHeaders;
|
||||
}
|
||||
else
|
||||
|
@ -621,14 +603,13 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
}
|
||||
}
|
||||
|
||||
var hasHeaders = new StreamWriter(stream, contentType, _logger)
|
||||
var hasHeaders = new StreamWriter(stream, contentType)
|
||||
{
|
||||
OnComplete = options.OnComplete,
|
||||
OnError = options.OnError
|
||||
};
|
||||
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
hasHeaders.Headers["ETag"] = newEtag;
|
||||
return hasHeaders;
|
||||
}
|
||||
}
|
||||
|
@ -641,37 +622,28 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// <summary>
|
||||
/// Adds the caching responseHeaders.
|
||||
/// </summary>
|
||||
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration)
|
||||
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration,
|
||||
bool noCache, DateTime? lastModifiedDate)
|
||||
{
|
||||
if (cacheDuration.HasValue)
|
||||
{
|
||||
responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(cacheKey))
|
||||
{
|
||||
responseHeaders["Cache-Control"] = "public";
|
||||
}
|
||||
else
|
||||
if (noCache)
|
||||
{
|
||||
responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate";
|
||||
responseHeaders["pragma"] = "no-cache, no-store, must-revalidate";
|
||||
return;
|
||||
}
|
||||
|
||||
AddExpiresHeader(responseHeaders, cacheKey, cacheDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the expires header.
|
||||
/// </summary>
|
||||
private static void AddExpiresHeader(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration)
|
||||
{
|
||||
if (cacheDuration.HasValue)
|
||||
{
|
||||
responseHeaders["Expires"] = DateTime.UtcNow.Add(cacheDuration.Value).ToString("r");
|
||||
responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(cacheKey))
|
||||
else
|
||||
{
|
||||
responseHeaders["Expires"] = "-1";
|
||||
responseHeaders["Cache-Control"] = "public";
|
||||
}
|
||||
|
||||
if (lastModifiedDate.HasValue)
|
||||
{
|
||||
responseHeaders["Last-Modified"] = lastModifiedDate.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -687,32 +659,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Determines whether [is not modified] [the specified cache key].
|
||||
/// </summary>
|
||||
/// <param name="requestContext">The request context.</param>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
|
||||
private bool IsNotModified(IRequest requestContext, Guid cacheKey)
|
||||
{
|
||||
var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match");
|
||||
|
||||
bool hasCacheKey = !cacheKey.Equals(Guid.Empty);
|
||||
|
||||
// Validate If-None-Match
|
||||
if (hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader))
|
||||
{
|
||||
if (Guid.TryParse(ifNoneMatchHeader, out var ifNoneMatch)
|
||||
&& cacheKey.Equals(ifNoneMatch))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is not modified] [the specified if modified since].
|
||||
|
|
|
@ -14,8 +14,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// </summary>
|
||||
public class StreamWriter : IAsyncStreamWriter, IHasHeaders
|
||||
{
|
||||
private ILogger Logger { get; set; }
|
||||
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <summary>
|
||||
|
@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// <param name="source">The source.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public StreamWriter(Stream source, string contentType, ILogger logger)
|
||||
public StreamWriter(Stream source, string contentType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
|
@ -53,7 +51,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
}
|
||||
|
||||
SourceStream = source;
|
||||
Logger = logger;
|
||||
|
||||
Headers["Content-Type"] = contentType;
|
||||
|
||||
|
@ -69,7 +66,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// <param name="source">The source.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public StreamWriter(byte[] source, string contentType, int contentLength, ILogger logger)
|
||||
public StreamWriter(byte[] source, string contentType, int contentLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
|
@ -77,7 +74,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
}
|
||||
|
||||
SourceBytes = source;
|
||||
Logger = logger;
|
||||
|
||||
Headers["Content-Type"] = contentType;
|
||||
|
||||
|
|
|
@ -17,31 +17,23 @@ namespace Emby.Server.Implementations.IO
|
|||
public class FileRefresher : IDisposable
|
||||
{
|
||||
private ILogger Logger { get; set; }
|
||||
private ITaskManager TaskManager { get; set; }
|
||||
private ILibraryManager LibraryManager { get; set; }
|
||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly List<string> _affectedPaths = new List<string>();
|
||||
private Timer _timer;
|
||||
private readonly object _timerLock = new object();
|
||||
public string Path { get; private set; }
|
||||
|
||||
public event EventHandler<EventArgs> Completed;
|
||||
private readonly IEnvironmentInfo _environmentInfo;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1)
|
||||
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
|
||||
{
|
||||
logger.LogDebug("New file refresher created for {0}", path);
|
||||
Path = path;
|
||||
|
||||
_fileSystem = fileSystem;
|
||||
ConfigurationManager = configurationManager;
|
||||
LibraryManager = libraryManager;
|
||||
TaskManager = taskManager;
|
||||
Logger = logger;
|
||||
_environmentInfo = environmentInfo;
|
||||
_libraryManager = libraryManager1;
|
||||
AddPath(path);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.IO
|
|||
/// <summary>
|
||||
/// Any file name ending in any of these will be ignored by the watchers
|
||||
/// </summary>
|
||||
private readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"small.jpg",
|
||||
"albumart.jpg",
|
||||
|
@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.IO
|
|||
"TempSBE"
|
||||
};
|
||||
|
||||
private readonly string[] _alwaysIgnoreSubstrings = new string[]
|
||||
private static readonly string[] _alwaysIgnoreSubstrings = new string[]
|
||||
{
|
||||
// Synology
|
||||
"eaDir",
|
||||
|
@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.IO
|
|||
".actors"
|
||||
};
|
||||
|
||||
private readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// thumbs.db
|
||||
".db",
|
||||
|
@ -123,12 +123,6 @@ namespace Emby.Server.Implementations.IO
|
|||
/// <value>The logger.</value>
|
||||
private ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the task manager.
|
||||
/// </summary>
|
||||
/// <value>The task manager.</value>
|
||||
private ITaskManager TaskManager { get; set; }
|
||||
|
||||
private ILibraryManager LibraryManager { get; set; }
|
||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
||||
|
||||
|
@ -140,19 +134,12 @@ namespace Emby.Server.Implementations.IO
|
|||
/// </summary>
|
||||
public LibraryMonitor(
|
||||
ILoggerFactory loggerFactory,
|
||||
ITaskManager taskManager,
|
||||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environmentInfo)
|
||||
{
|
||||
if (taskManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(taskManager));
|
||||
}
|
||||
|
||||
LibraryManager = libraryManager;
|
||||
TaskManager = taskManager;
|
||||
Logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
ConfigurationManager = configurationManager;
|
||||
_fileSystem = fileSystem;
|
||||
|
@ -541,7 +528,7 @@ namespace Emby.Server.Implementations.IO
|
|||
}
|
||||
}
|
||||
|
||||
var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _environmentInfo, LibraryManager);
|
||||
var newRefresher = new FileRefresher(path, ConfigurationManager, LibraryManager, Logger);
|
||||
newRefresher.Completed += NewRefresher_Completed;
|
||||
_activeRefreshers.Add(newRefresher);
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.IO
|
||||
|
@ -20,61 +22,27 @@ namespace Emby.Server.Implementations.IO
|
|||
private readonly bool _supportsAsyncFileStreams;
|
||||
private char[] _invalidFileNameChars;
|
||||
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
|
||||
private bool EnableSeparateFileAndDirectoryQueries;
|
||||
|
||||
private string _tempPath;
|
||||
private readonly string _tempPath;
|
||||
|
||||
private IEnvironmentInfo _environmentInfo;
|
||||
private bool _isEnvironmentCaseInsensitive;
|
||||
|
||||
private string _defaultDirectory;
|
||||
private readonly IEnvironmentInfo _environmentInfo;
|
||||
private readonly bool _isEnvironmentCaseInsensitive;
|
||||
|
||||
public ManagedFileSystem(
|
||||
ILoggerFactory loggerFactory,
|
||||
IEnvironmentInfo environmentInfo,
|
||||
string defaultDirectory,
|
||||
string tempPath,
|
||||
bool enableSeparateFileAndDirectoryQueries)
|
||||
IApplicationPaths applicationPaths)
|
||||
{
|
||||
Logger = loggerFactory.CreateLogger("FileSystem");
|
||||
_supportsAsyncFileStreams = true;
|
||||
_tempPath = tempPath;
|
||||
_tempPath = applicationPaths.TempDirectory;
|
||||
_environmentInfo = environmentInfo;
|
||||
_defaultDirectory = defaultDirectory;
|
||||
|
||||
// On Linux with mono, this needs to be true or symbolic links are ignored
|
||||
EnableSeparateFileAndDirectoryQueries = enableSeparateFileAndDirectoryQueries;
|
||||
|
||||
SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
|
||||
|
||||
_isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||
}
|
||||
|
||||
public virtual string DefaultDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = _defaultDirectory;
|
||||
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AddShortcutHandler(IShortcutHandler handler)
|
||||
{
|
||||
_shortcutHandlers.Add(handler);
|
||||
|
@ -718,7 +686,7 @@ namespace Emby.Server.Implementations.IO
|
|||
SetAttributes(path, false, false);
|
||||
File.Delete(path);
|
||||
}
|
||||
|
||||
|
||||
public virtual List<FileSystemMetadata> GetDrives()
|
||||
{
|
||||
// Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
|
||||
|
@ -777,20 +745,15 @@ namespace Emby.Server.Implementations.IO
|
|||
var directoryInfo = new DirectoryInfo(path);
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
|
||||
if (EnableSeparateFileAndDirectoryQueries)
|
||||
{
|
||||
return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
|
||||
.Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
|
||||
}
|
||||
|
||||
return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", searchOption));
|
||||
return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
|
||||
.Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
|
||||
}
|
||||
|
||||
private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
|
||||
{
|
||||
return infos.Select(GetFileSystemMetadata);
|
||||
}
|
||||
|
||||
|
||||
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
|
||||
{
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
@ -7,7 +6,6 @@ using MediaBrowser.Controller.Library;
|
|||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
|
@ -16,16 +14,14 @@ namespace Emby.Server.Implementations.Library
|
|||
/// </summary>
|
||||
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private bool _ignoreDotPrefix;
|
||||
|
||||
/// <summary>
|
||||
/// Any folder named in this list will be ignored - can be added to at runtime for extensibility
|
||||
/// </summary>
|
||||
public static readonly Dictionary<string, string> IgnoreFolders = new List<string>
|
||||
public static readonly string[] IgnoreFolders =
|
||||
{
|
||||
"metadata",
|
||||
"ps3_update",
|
||||
|
@ -50,13 +46,11 @@ namespace Emby.Server.Implementations.Library
|
|||
// macos
|
||||
".AppleDouble"
|
||||
|
||||
}.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
||||
};
|
||||
|
||||
public CoreResolutionIgnoreRule(IFileSystem fileSystem, ILibraryManager libraryManager, ILogger logger)
|
||||
public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
|
||||
_ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT;
|
||||
}
|
||||
|
@ -117,7 +111,7 @@ namespace Emby.Server.Implementations.Library
|
|||
if (fileInfo.IsDirectory)
|
||||
{
|
||||
// Ignore any folders in our list
|
||||
if (IgnoreFolders.ContainsKey(filename))
|
||||
if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -986,7 +986,7 @@ namespace Emby.Server.Implementations.Library
|
|||
// Ensure the location is available.
|
||||
Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
|
||||
|
||||
return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress);
|
||||
return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1225,9 +1225,9 @@ namespace Emby.Server.Implementations.Library
|
|||
/// <exception cref="ArgumentNullException">id</exception>
|
||||
public BaseItem GetItemById(Guid id)
|
||||
{
|
||||
if (id.Equals(Guid.Empty))
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
throw new ArgumentException(nameof(id), "Guid can't be empty");
|
||||
}
|
||||
|
||||
if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
|
||||
|
@ -1237,8 +1237,6 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
item = RetrieveItem(id);
|
||||
|
||||
//_logger.LogDebug("GetitemById {0}", id);
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
RegisterItem(item);
|
||||
|
@ -1333,7 +1331,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return ItemRepository.GetItemIdsList(query);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
|
@ -1344,7 +1342,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return ItemRepository.GetStudios(query);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
|
@ -1355,7 +1353,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return ItemRepository.GetGenres(query);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
|
@ -1366,7 +1364,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return ItemRepository.GetMusicGenres(query);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
|
@ -1377,7 +1375,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return ItemRepository.GetAllArtists(query);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
|
@ -1421,7 +1419,7 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
|
@ -1808,18 +1806,16 @@ namespace Emby.Server.Implementations.Library
|
|||
/// <returns>Task.</returns>
|
||||
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = items.ToList();
|
||||
ItemRepository.SaveItems(items, cancellationToken);
|
||||
|
||||
ItemRepository.SaveItems(list, cancellationToken);
|
||||
|
||||
foreach (var item in list)
|
||||
foreach (var item in items)
|
||||
{
|
||||
RegisterItem(item);
|
||||
}
|
||||
|
||||
if (ItemAdded != null)
|
||||
{
|
||||
foreach (var item in list)
|
||||
foreach (var item in items)
|
||||
{
|
||||
// With the live tv guide this just creates too much noise
|
||||
if (item.SourceType != SourceType.Library)
|
||||
|
@ -1853,7 +1849,7 @@ namespace Emby.Server.Implementations.Library
|
|||
/// <summary>
|
||||
/// Updates the item.
|
||||
/// </summary>
|
||||
public void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
|
@ -1908,7 +1904,7 @@ namespace Emby.Server.Implementations.Library
|
|||
/// <returns>Task.</returns>
|
||||
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
{
|
||||
UpdateItems(new List<BaseItem> { item }, parent, updateReason, cancellationToken);
|
||||
UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -2005,9 +2001,7 @@ namespace Emby.Server.Implementations.Library
|
|||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
|
||||
|
||||
return options;
|
||||
return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
|
||||
}
|
||||
|
||||
public string GetContentType(BaseItem item)
|
||||
|
@ -2017,11 +2011,13 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
return configuredContentType;
|
||||
}
|
||||
|
||||
configuredContentType = GetConfiguredContentType(item, true);
|
||||
if (!string.IsNullOrEmpty(configuredContentType))
|
||||
{
|
||||
return configuredContentType;
|
||||
}
|
||||
|
||||
return GetInheritedContentType(item);
|
||||
}
|
||||
|
||||
|
@ -2056,6 +2052,7 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
return collectionFolder.CollectionType;
|
||||
}
|
||||
|
||||
return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath);
|
||||
}
|
||||
|
||||
|
@ -2066,6 +2063,7 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
return nameValuePair.Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -2108,9 +2106,9 @@ namespace Emby.Server.Implementations.Library
|
|||
string viewType,
|
||||
string sortName)
|
||||
{
|
||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views");
|
||||
|
||||
path = Path.Combine(path, _fileSystem.GetValidFilename(viewType));
|
||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath,
|
||||
"views",
|
||||
_fileSystem.GetValidFilename(viewType));
|
||||
|
||||
var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
|
||||
|
||||
|
@ -2543,7 +2541,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
var resolvers = new IItemResolver[]
|
||||
{
|
||||
new GenericVideoResolver<Trailer>(this, _fileSystem)
|
||||
new GenericVideoResolver<Trailer>(this)
|
||||
};
|
||||
|
||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
|
||||
|
|
|
@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities;
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
|
@ -18,11 +17,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||
where T : Video, new()
|
||||
{
|
||||
protected readonly ILibraryManager LibraryManager;
|
||||
protected readonly IFileSystem FileSystem;
|
||||
|
||||
protected BaseVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem)
|
||||
protected BaseVideoResolver(ILibraryManager libraryManager)
|
||||
{
|
||||
FileSystem = fileSystem;
|
||||
LibraryManager = libraryManager;
|
||||
}
|
||||
|
||||
|
|
|
@ -548,7 +548,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
|
||||
private IImageProcessor _imageProcessor;
|
||||
|
||||
public MovieResolver(ILibraryManager libraryManager, IFileSystem fileSystem, IImageProcessor imageProcessor) : base(libraryManager, fileSystem)
|
||||
public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
|
||||
: base(libraryManager)
|
||||
{
|
||||
_imageProcessor = imageProcessor;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities;
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
|
@ -15,13 +14,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||
{
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager, IFileSystem fileSystem)
|
||||
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
|
||||
{
|
||||
_imageProcessor = imageProcessor;
|
||||
_libraryManager = libraryManager;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -113,8 +110,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||
return false;
|
||||
}
|
||||
|
||||
return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'));
|
||||
return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ using MediaBrowser.Model.IO;
|
|||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
class SpecialFolderResolver : FolderResolver<Folder>
|
||||
public class SpecialFolderResolver : FolderResolver<Folder>
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Linq;
|
|||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
{
|
||||
|
@ -74,7 +73,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
return null;
|
||||
}
|
||||
|
||||
public EpisodeResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
|
||||
public EpisodeResolver(ILibraryManager libraryManager)
|
||||
: base(libraryManager)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
public class GenericVideoResolver<T> : BaseVideoResolver<T>
|
||||
where T : Video, new()
|
||||
{
|
||||
public GenericVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
|
||||
public GenericVideoResolver(ILibraryManager libraryManager)
|
||||
: base(libraryManager)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,7 +24,6 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
|
@ -32,11 +31,10 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem)
|
||||
public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,8 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
_mediaSourceManager = mediaSourceManager;
|
||||
_streamHelper = streamHelper;
|
||||
|
||||
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
|
||||
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
|
||||
_seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
|
||||
_timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
|
||||
_timerProvider.TimerFired += _timerProvider_TimerFired;
|
||||
|
||||
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
|
||||
|
@ -1708,7 +1708,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
||||
{
|
||||
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _httpClient, _processFactory, _config, _assemblyInfo);
|
||||
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
||||
}
|
||||
|
||||
return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);
|
||||
|
|
|
@ -7,7 +7,6 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -17,7 +16,6 @@ using MediaBrowser.Model.Diagnostics;
|
|||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -27,7 +25,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private bool _hasExited;
|
||||
|
@ -38,19 +35,23 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
private readonly IJsonSerializer _json;
|
||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IAssemblyInfo _assemblyInfo;
|
||||
|
||||
public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, IHttpClient httpClient, IProcessFactory processFactory, IServerConfigurationManager config, IAssemblyInfo assemblyInfo)
|
||||
public EncodedRecorder(
|
||||
ILogger logger,
|
||||
IFileSystem fileSystem,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IServerApplicationPaths appPaths,
|
||||
IJsonSerializer json,
|
||||
IProcessFactory processFactory,
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_appPaths = appPaths;
|
||||
_json = json;
|
||||
_httpClient = httpClient;
|
||||
_processFactory = processFactory;
|
||||
_config = config;
|
||||
_assemblyInfo = assemblyInfo;
|
||||
}
|
||||
|
||||
private static bool CopySubtitles => false;
|
||||
|
|
|
@ -17,15 +17,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
protected readonly ILogger Logger;
|
||||
private readonly string _dataPath;
|
||||
protected readonly Func<T, T, bool> EqualityComparer;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public ItemDataProvider(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer)
|
||||
public ItemDataProvider(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer)
|
||||
{
|
||||
Logger = logger;
|
||||
_dataPath = dataPath;
|
||||
EqualityComparer = equalityComparer;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public IReadOnlyList<T> GetAll()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -8,8 +7,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo>
|
||||
{
|
||||
public SeriesTimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
|
||||
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
|
||||
public SeriesTimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
|
||||
: base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -19,8 +18,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
|
||||
|
||||
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
|
||||
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
|
||||
public TimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
|
||||
: base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger = logger1;
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId);
|
||||
var user = query.UserId == Guid.Empty ? null : _userManager.GetUserById(query.UserId);
|
||||
|
||||
var topFolder = GetInternalLiveTvFolder(cancellationToken);
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ using MediaBrowser.Common.Configuration;
|
|||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
@ -23,18 +22,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
protected readonly IServerConfigurationManager Config;
|
||||
protected readonly ILogger Logger;
|
||||
protected IJsonSerializer JsonSerializer;
|
||||
protected readonly IMediaEncoder MediaEncoder;
|
||||
protected readonly IFileSystem FileSystem;
|
||||
|
||||
private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
|
||||
new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem)
|
||||
protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
|
||||
{
|
||||
Config = config;
|
||||
Logger = logger;
|
||||
JsonSerializer = jsonSerializer;
|
||||
MediaEncoder = mediaEncoder;
|
||||
FileSystem = fileSystem;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,15 +31,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
|
||||
public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
|
||||
public HdHomerunHost(
|
||||
IServerConfigurationManager config,
|
||||
ILogger logger,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IFileSystem fileSystem,
|
||||
IHttpClient httpClient,
|
||||
IServerApplicationHost appHost,
|
||||
ISocketFactory socketFactory,
|
||||
INetworkManager networkManager)
|
||||
: base(config, logger, jsonSerializer, fileSystem)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_appHost = appHost;
|
||||
_socketFactory = socketFactory;
|
||||
_networkManager = networkManager;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public string Name => "HD Homerun";
|
||||
|
|
|
@ -26,15 +26,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
|
||||
public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
|
||||
: base(config, logger, jsonSerializer, fileSystem)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_appHost = appHost;
|
||||
_environment = environment;
|
||||
_networkManager = networkManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
@ -52,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
{
|
||||
var channelIdPrefix = GetFullChannelIdPrefix(info);
|
||||
|
||||
var result = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
|
||||
var result = await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result.Cast<ChannelInfo>().ToList();
|
||||
}
|
||||
|
@ -115,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
public async Task Validate(TunerHostInfo info)
|
||||
{
|
||||
using (var stream = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
|
||||
using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -19,14 +19,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
public class M3uParser
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
public M3uParser(ILogger logger, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost)
|
||||
public M3uParser(ILogger logger, IHttpClient httpClient, IServerApplicationHost appHost)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_httpClient = httpClient;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
@ -157,56 +155,56 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null;
|
||||
|
||||
string numberString = null;
|
||||
string attributeValue;
|
||||
double doubleValue;
|
||||
|
||||
// Check for channel number with the format from SatIp
|
||||
// #EXTINF:0,84. VOX Schweiz
|
||||
// #EXTINF:0,84.0 - VOX Schweiz
|
||||
if (!string.IsNullOrWhiteSpace(nameInExtInf))
|
||||
if (attributes.TryGetValue("tvg-chno", out attributeValue))
|
||||
{
|
||||
var numberIndex = nameInExtInf.IndexOf(' ');
|
||||
if (numberIndex > 0)
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
|
||||
{
|
||||
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
|
||||
|
||||
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
|
||||
{
|
||||
numberString = numberPart;
|
||||
}
|
||||
numberString = attributeValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = numberString.Trim();
|
||||
}
|
||||
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
{
|
||||
if (attributes.TryGetValue("tvg-id", out string value))
|
||||
if (attributes.TryGetValue("tvg-id", out attributeValue))
|
||||
{
|
||||
if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleValue))
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
|
||||
{
|
||||
numberString = value;
|
||||
numberString = attributeValue;
|
||||
}
|
||||
else if (attributes.TryGetValue("channel-id", out attributeValue))
|
||||
{
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
|
||||
{
|
||||
numberString = attributeValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = numberString.Trim();
|
||||
}
|
||||
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
{
|
||||
if (attributes.TryGetValue("channel-id", out string value))
|
||||
if (String.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = value;
|
||||
}
|
||||
}
|
||||
// Using this as a fallback now as this leads to Problems with channels like "5 USA"
|
||||
// where 5 isnt ment to be the channel number
|
||||
// Check for channel number with the format from SatIp
|
||||
// #EXTINF:0,84. VOX Schweiz
|
||||
// #EXTINF:0,84.0 - VOX Schweiz
|
||||
if (!string.IsNullOrWhiteSpace(nameInExtInf))
|
||||
{
|
||||
var numberIndex = nameInExtInf.IndexOf(' ');
|
||||
if (numberIndex > 0)
|
||||
{
|
||||
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
|
||||
|
||||
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
|
||||
{
|
||||
numberString = numberPart;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = numberString.Trim();
|
||||
}
|
||||
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
|
@ -214,7 +212,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
numberString = null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(numberString))
|
||||
if (!string.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = numberString.Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaUrl))
|
||||
{
|
||||
|
|
|
@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
|
||||
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
//OpenedMediaSource.Path = tempFile;
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
"Albums": "Album",
|
||||
"AppDeviceValues": "App: {0}, Enhed: {1}",
|
||||
"Application": "Applikation",
|
||||
"Artists": "Kunstner",
|
||||
"Artists": "Kunstnere",
|
||||
"AuthenticationSucceededWithUserName": "{0} bekræftet med succes",
|
||||
"Books": "Bøger",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}",
|
||||
"Channels": "Kanaler",
|
||||
"ChapterNameValue": "Kapitel {0}",
|
||||
"Collections": "Samlinger",
|
||||
|
@ -14,41 +14,41 @@
|
|||
"FailedLoginAttemptWithUserName": "Fejlet loginforsøg fra {0}",
|
||||
"Favorites": "Favoritter",
|
||||
"Folders": "Mapper",
|
||||
"Genres": "Genre",
|
||||
"Genres": "Genrer",
|
||||
"HeaderAlbumArtists": "Albumkunstnere",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderCameraUploads": "Kamera Uploads",
|
||||
"HeaderContinueWatching": "Fortsæt Afspilning",
|
||||
"HeaderFavoriteAlbums": "Favoritalbum",
|
||||
"HeaderFavoriteArtists": "Favoritkunstnere",
|
||||
"HeaderFavoriteEpisodes": "Favoritepisoder",
|
||||
"HeaderFavoriteShows": "Favorit serier",
|
||||
"HeaderFavoriteSongs": "Favoritsange",
|
||||
"HeaderFavoriteEpisodes": "Favorit-afsnit",
|
||||
"HeaderFavoriteShows": "Favorit-serier",
|
||||
"HeaderFavoriteSongs": "Favorit-sange",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Næste",
|
||||
"HeaderRecordingGroups": "Optagegrupper",
|
||||
"HeaderRecordingGroups": "Optagelsesgrupper",
|
||||
"HomeVideos": "Hjemmevideoer",
|
||||
"Inherit": "Arv",
|
||||
"Inherit": "Nedarv",
|
||||
"ItemAddedWithName": "{0} blev tilføjet til biblioteket",
|
||||
"ItemRemovedWithName": "{0} blev fjernet fra biblioteket",
|
||||
"LabelIpAddressValue": "IP-adresse: {0}",
|
||||
"LabelRunningTimeValue": "Spilletid: {0}",
|
||||
"Latest": "Seneste",
|
||||
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server konfigurationssektion {0} er blevet opdateret",
|
||||
"MessageServerConfigurationUpdated": "Serverkonfiguration er blevet opdateret",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret",
|
||||
"MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
|
||||
"MixedContent": "Blandet indhold",
|
||||
"Movies": "Film",
|
||||
"Music": "Musik",
|
||||
"MusicVideos": "Musikvideoer",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameInstallFailed": "{0} installationen mislykkedes",
|
||||
"NameSeasonNumber": "Sæson {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NameSeasonUnknown": "Ukendt Sæson",
|
||||
"NewVersionIsAvailable": "En ny version af Jellyfin Server er tilgængelig til download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Opdatering til applikation tilgængelig",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Opdatering til applikation installeret",
|
||||
"NotificationOptionAudioPlayback": "Audioafspilning påbegyndt",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audioafspilning stoppet",
|
||||
"NotificationOptionAudioPlayback": "Lydafspilning påbegyndt",
|
||||
"NotificationOptionAudioPlaybackStopped": "Lydafspilning stoppet",
|
||||
"NotificationOptionCameraImageUploaded": "Kamerabillede uploadet",
|
||||
"NotificationOptionInstallationFailed": "Installationsfejl",
|
||||
"NotificationOptionNewLibraryContent": "Nyt indhold tilføjet",
|
||||
|
@ -70,16 +70,16 @@
|
|||
"ProviderValue": "Udbyder: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} fejlet",
|
||||
"ScheduledTaskStartedWithName": "{0} påbegyndt",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"Shows": "Shows",
|
||||
"ServerNameNeedsToBeRestarted": "{0} skal genstartes",
|
||||
"Shows": "Serier",
|
||||
"Songs": "Sange",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server indlæser. Prøv venligst igen om kort tid.",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server er i gang med at starte op. Prøv venligst igen om lidt.",
|
||||
"SubtitleDownloadFailureForItem": "Fejlet i download af undertekster for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke downloades fra {0} til {1}",
|
||||
"SubtitlesDownloadedForItem": "Undertekster downloadet for {0}",
|
||||
"Sync": "Synk",
|
||||
"System": "System",
|
||||
"TvShows": "TV Shows",
|
||||
"TvShows": "TV serier",
|
||||
"User": "Bruger",
|
||||
"UserCreatedWithName": "Bruger {0} er blevet oprettet",
|
||||
"UserDeletedWithName": "Brugeren {0} er blevet slettet",
|
||||
|
@ -88,10 +88,10 @@
|
|||
"UserOfflineFromDevice": "{0} har afbrudt fra {1}",
|
||||
"UserOnlineFromDevice": "{0} er online fra {1}",
|
||||
"UserPasswordChangedWithName": "Adgangskode er ændret for bruger {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserPolicyUpdatedWithName": "Brugerpolitik er blevet opdateret for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} har påbegyndt afspilning af {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
}
|
||||
|
|
|
@ -3,61 +3,61 @@
|
|||
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||
"Application": "Anwendung",
|
||||
"Artists": "Interpreten",
|
||||
"AuthenticationSucceededWithUserName": "{0} erfolgreich authentifiziert",
|
||||
"AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
|
||||
"Books": "Bücher",
|
||||
"CameraImageUploadedFrom": "Ein neues Bild wurde hochgeladen von {0}",
|
||||
"CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
|
||||
"Channels": "Kanäle",
|
||||
"ChapterNameValue": "Kapitel {0}",
|
||||
"Collections": "Sammlungen",
|
||||
"DeviceOfflineWithName": "{0} wurde getrennt",
|
||||
"DeviceOnlineWithName": "{0} ist verbunden",
|
||||
"DeviceOnlineWithName": "{0} hat sich verbunden",
|
||||
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
|
||||
"Favorites": "Favoriten",
|
||||
"Folders": "Verzeichnisse",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album-Künstler",
|
||||
"HeaderCameraUploads": "Kamera Uploads",
|
||||
"HeaderAlbumArtists": "Album-Interpreten",
|
||||
"HeaderCameraUploads": "Kamera-Uploads",
|
||||
"HeaderContinueWatching": "Weiterschauen",
|
||||
"HeaderFavoriteAlbums": "Lieblingsalben",
|
||||
"HeaderFavoriteArtists": "Interpreten Favoriten",
|
||||
"HeaderFavoriteArtists": "Lieblings-Interpreten",
|
||||
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
|
||||
"HeaderFavoriteShows": "Lieblingsserien",
|
||||
"HeaderFavoriteSongs": "Lieder Favoriten",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderFavoriteSongs": "Lieblingslieder",
|
||||
"HeaderLiveTV": "Live-TV",
|
||||
"HeaderNextUp": "Als Nächstes",
|
||||
"HeaderRecordingGroups": "Aufnahme-Gruppen",
|
||||
"HomeVideos": "Heimvideos",
|
||||
"Inherit": "Übernehmen",
|
||||
"ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt",
|
||||
"ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt",
|
||||
"LabelIpAddressValue": "IP Adresse: {0}",
|
||||
"LabelIpAddressValue": "IP-Adresse: {0}",
|
||||
"LabelRunningTimeValue": "Laufzeit: {0}",
|
||||
"Latest": "Neueste",
|
||||
"MessageApplicationUpdated": "Jellyfin Server wurde auf den neusten Stand gebracht.",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server wurde auf Version {0} aktualisiert",
|
||||
"MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert",
|
||||
"MessageServerConfigurationUpdated": "Server Einstellungen wurden aktualisiert",
|
||||
"MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
|
||||
"MixedContent": "Gemischte Inhalte",
|
||||
"Movies": "Filme",
|
||||
"Music": "Musik",
|
||||
"MusicVideos": "Musikvideos",
|
||||
"NameInstallFailed": "{0} Installation fehlgeschlagen",
|
||||
"NameInstallFailed": "Installation von {0} fehlgeschlagen",
|
||||
"NameSeasonNumber": "Staffel {0}",
|
||||
"NameSeasonUnknown": "Staffel unbekannt",
|
||||
"NewVersionIsAvailable": "Eine neue Version von Jellyfin Server steht zum Download bereit.",
|
||||
"NewVersionIsAvailable": "Eine neue Version von Jellyfin-Server steht zum Download bereit.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Anwendungsaktualisierung verfügbar",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Anwendungsaktualisierung installiert",
|
||||
"NotificationOptionAudioPlayback": "Audiowiedergabe gestartet",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audiowiedergabe gestoppt",
|
||||
"NotificationOptionCameraImageUploaded": "Kamera Bild hochgeladen",
|
||||
"NotificationOptionCameraImageUploaded": "Foto hochgeladen",
|
||||
"NotificationOptionInstallationFailed": "Installationsfehler",
|
||||
"NotificationOptionNewLibraryContent": "Neuer Inhalt hinzugefügt",
|
||||
"NotificationOptionPluginError": "Plugin Fehler",
|
||||
"NotificationOptionPluginError": "Plugin-Fehler",
|
||||
"NotificationOptionPluginInstalled": "Plugin installiert",
|
||||
"NotificationOptionPluginUninstalled": "Plugin deinstalliert",
|
||||
"NotificationOptionPluginUpdateInstalled": "Pluginaktualisierung installiert",
|
||||
"NotificationOptionServerRestartRequired": "Serverneustart notwendig",
|
||||
"NotificationOptionTaskFailed": "Geplante Aufgaben fehlgeschlagen",
|
||||
"NotificationOptionTaskFailed": "Geplante Aufgabe fehlgeschlagen",
|
||||
"NotificationOptionUserLockedOut": "Benutzer ausgeschlossen",
|
||||
"NotificationOptionVideoPlayback": "Videowiedergabe gestartet",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videowiedergabe gestoppt",
|
||||
|
@ -68,18 +68,18 @@
|
|||
"PluginUninstalledWithName": "{0} wurde deinstalliert",
|
||||
"PluginUpdatedWithName": "{0} wurde aktualisiert",
|
||||
"ProviderValue": "Anbieter: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} fehlgeschlagen",
|
||||
"ScheduledTaskStartedWithName": "{0} gestartet",
|
||||
"ScheduledTaskFailedWithName": "{0} ist fehlgeschlagen",
|
||||
"ScheduledTaskStartedWithName": "{0} wurde gestartet",
|
||||
"ServerNameNeedsToBeRestarted": "{0} muss neu gestartet werden",
|
||||
"Shows": "Serien",
|
||||
"Songs": "Songs",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server startet, bitte versuche es gleich noch einmal.",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin-Server startet, bitte versuche es gleich noch einmal.",
|
||||
"SubtitleDownloadFailureForItem": "Download der Untertitel fehlgeschlagen für {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden",
|
||||
"SubtitlesDownloadedForItem": "Untertitel heruntergeladen für {0}",
|
||||
"Sync": "Synchronisation",
|
||||
"System": "System",
|
||||
"TvShows": "TV Sendungen",
|
||||
"TvShows": "TV-Sendungen",
|
||||
"User": "Benutzer",
|
||||
"UserCreatedWithName": "Benutzer {0} wurde erstellt",
|
||||
"UserDeletedWithName": "Benutzer {0} wurde gelöscht",
|
||||
|
@ -88,10 +88,10 @@
|
|||
"UserOfflineFromDevice": "{0} wurde getrennt von {1}",
|
||||
"UserOnlineFromDevice": "{0} ist online von {1}",
|
||||
"UserPasswordChangedWithName": "Das Passwort für Benutzer {0} wurde geändert",
|
||||
"UserPolicyUpdatedWithName": "Benutzerrichtlinie wurde für {0} aktualisiert",
|
||||
"UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} gestartet",
|
||||
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} beendet",
|
||||
"ValueHasBeenAddedToLibrary": "{0} wurde ihrer Bibliothek hinzugefügt",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"UserPolicyUpdatedWithName": "Benutzerrichtlinie von {0} wurde aktualisiert",
|
||||
"UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet",
|
||||
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet",
|
||||
"ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
|
||||
"ValueSpecialEpisodeName": "Extra - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} has started playing {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"LabelIpAddressValue": "IP address: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"Latest": "Latest",
|
||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
||||
|
|
|
@ -5,46 +5,46 @@
|
|||
"Artists": "Artistas",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado correctamente",
|
||||
"Books": "Libros",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
|
||||
"Channels": "Canales",
|
||||
"ChapterNameValue": "Capítulo {0}",
|
||||
"Collections": "Colecciones",
|
||||
"DeviceOfflineWithName": "{0} se ha desconectado",
|
||||
"DeviceOnlineWithName": "{0} está conectado",
|
||||
"FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión a partir de {0}",
|
||||
"FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión desde {0}",
|
||||
"Favorites": "Favoritos",
|
||||
"Folders": "Carpetas",
|
||||
"Genres": "Géneros",
|
||||
"HeaderAlbumArtists": "Artistas del Álbum",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderAlbumArtists": "Artistas del álbum",
|
||||
"HeaderCameraUploads": "Subidas desde cámara",
|
||||
"HeaderContinueWatching": "Continuar viendo",
|
||||
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
||||
"HeaderFavoriteArtists": "Artistas favoritos",
|
||||
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
||||
"HeaderFavoriteShows": "Programas favoritos",
|
||||
"HeaderFavoriteSongs": "Canciones favoritas",
|
||||
"HeaderLiveTV": "TV en vivo",
|
||||
"HeaderLiveTV": "TV en directo",
|
||||
"HeaderNextUp": "Siguiendo",
|
||||
"HeaderRecordingGroups": "Grupos de grabación",
|
||||
"HomeVideos": "Vídeos de inicio",
|
||||
"HomeVideos": "Vídeos caseros",
|
||||
"Inherit": "Heredar",
|
||||
"ItemAddedWithName": "{0} se ha añadido a la biblioteca",
|
||||
"ItemRemovedWithName": "{0} se elimina de la biblioteca",
|
||||
"ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca",
|
||||
"LabelIpAddressValue": "Dirección IP: {0}",
|
||||
"LabelRunningTimeValue": "Tiempo de funcionamiento: {0}",
|
||||
"Latest": "Últimos",
|
||||
"MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "La sección de configuración del servidor {0} ha sido actualizado",
|
||||
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada",
|
||||
"MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
|
||||
"MixedContent": "Contenido mixto",
|
||||
"Movies": "Peliculas",
|
||||
"Movies": "Películas",
|
||||
"Music": "Música",
|
||||
"MusicVideos": "Videos musicales",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"MusicVideos": "Vídeos musicales",
|
||||
"NameInstallFailed": "{0} error de instalación",
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NameSeasonUnknown": "Temporada desconocida",
|
||||
"NewVersionIsAvailable": "Disponible una nueva versión de Jellyfin para descargar.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada",
|
||||
"NotificationOptionAudioPlayback": "Se inició la reproducción de audio",
|
||||
|
@ -56,13 +56,13 @@
|
|||
"NotificationOptionPluginInstalled": "Plugin instalado",
|
||||
"NotificationOptionPluginUninstalled": "Plugin desinstalado",
|
||||
"NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada",
|
||||
"NotificationOptionServerRestartRequired": "Requiere reinicio del servidor",
|
||||
"NotificationOptionServerRestartRequired": "Se requiere reinicio del servidor",
|
||||
"NotificationOptionTaskFailed": "Error de tarea programada",
|
||||
"NotificationOptionUserLockedOut": "Usuario bloqueado",
|
||||
"NotificationOptionVideoPlayback": "Se inició la reproducción de vídeo",
|
||||
"NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo detenida",
|
||||
"Photos": "Fotos",
|
||||
"Playlists": "Listas reproducción",
|
||||
"Playlists": "Listas de reproducción",
|
||||
"Plugin": "Plugin",
|
||||
"PluginInstalledWithName": "{0} se ha instalado",
|
||||
"PluginUninstalledWithName": "{0} se ha desinstalado",
|
||||
|
@ -70,16 +70,16 @@
|
|||
"ProviderValue": "Proveedor: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} falló",
|
||||
"ScheduledTaskStartedWithName": "{0} iniciada",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
|
||||
"Shows": "Series",
|
||||
"Songs": "Canciones",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
|
||||
"SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}",
|
||||
"SubtitlesDownloadedForItem": "Descargar subtítulos para {0}",
|
||||
"Sync": "Sincronizar",
|
||||
"System": "Sistema",
|
||||
"TvShows": "Series TV",
|
||||
"TvShows": "Series de TV",
|
||||
"User": "Usuario",
|
||||
"UserCreatedWithName": "El usuario {0} ha sido creado",
|
||||
"UserDeletedWithName": "El usuario {0} ha sido borrado",
|
||||
|
@ -88,10 +88,10 @@
|
|||
"UserOfflineFromDevice": "{0} se ha desconectado de {1}",
|
||||
"UserOnlineFromDevice": "{0} está en línea desde {1}",
|
||||
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} ha comenzado reproducir {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha parado de reproducir {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia",
|
||||
"ValueSpecialEpisodeName": "Especial - {0}",
|
||||
"VersionNumber": "Versión {0}"
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Serveur a été mis à jour en version {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
|
||||
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour.",
|
||||
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
|
||||
"MixedContent": "Contenu mixte",
|
||||
"Movies": "Films",
|
||||
"Music": "Musique",
|
||||
|
|
|
@ -5,48 +5,48 @@
|
|||
"Artists": "Előadók",
|
||||
"AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
|
||||
"Books": "Könyvek",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "Új kamerakép került feltöltésre {0}",
|
||||
"Channels": "Csatornák",
|
||||
"ChapterNameValue": "Jelenet {0}",
|
||||
"Collections": "Gyűjtemények",
|
||||
"DeviceOfflineWithName": "{0} kijelentkezett",
|
||||
"DeviceOnlineWithName": "{0} belépett",
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet {0}",
|
||||
"Favorites": "Kedvencek",
|
||||
"Folders": "Könyvtárak",
|
||||
"Genres": "Műfajok",
|
||||
"HeaderAlbumArtists": "Album Előadók",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderContinueWatching": "Vetítés(ek) folytatása",
|
||||
"HeaderCameraUploads": "Kamera feltöltések",
|
||||
"HeaderContinueWatching": "Folyamatban lévő filmek",
|
||||
"HeaderFavoriteAlbums": "Kedvenc Albumok",
|
||||
"HeaderFavoriteArtists": "Kedvenc Művészek",
|
||||
"HeaderFavoriteEpisodes": "Kedvenc Epizódok",
|
||||
"HeaderFavoriteShows": "Kedvenc Műsorok",
|
||||
"HeaderFavoriteSongs": "Kedvenc Dalok",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderLiveTV": "Élő TV",
|
||||
"HeaderNextUp": "Következik",
|
||||
"HeaderRecordingGroups": "Recording Groups",
|
||||
"HeaderRecordingGroups": "Felvételi csoportok",
|
||||
"HomeVideos": "Házi videók",
|
||||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip cím: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"ItemAddedWithName": "{0} hozzáadva a könyvtárhoz",
|
||||
"ItemRemovedWithName": "{0} eltávolítva a könyvtárból",
|
||||
"LabelIpAddressValue": "IP cím: {0}",
|
||||
"LabelRunningTimeValue": "Futási idő: {0}",
|
||||
"Latest": "Legújabb",
|
||||
"MessageApplicationUpdated": "Jellyfin Szerver frissítve",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész {0} frissítve",
|
||||
"MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve",
|
||||
"MixedContent": "Vegyes tartalom",
|
||||
"Movies": "Filmek",
|
||||
"Music": "Zene",
|
||||
"MusicVideos": "Zenei Videók",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameSeasonNumber": "Season {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Program frissítés elérhető",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Program frissítés telepítve",
|
||||
"NameInstallFailed": "{0} sikertelen telepítés",
|
||||
"NameSeasonNumber": "Évad {0}",
|
||||
"NameSeasonUnknown": "Ismeretlen évad",
|
||||
"NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Új programfrissítés érhető el",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Programfrissítés telepítve",
|
||||
"NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve",
|
||||
"NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
|
||||
|
@ -57,7 +57,7 @@
|
|||
"NotificationOptionPluginUninstalled": "Bővítmény eltávolítva",
|
||||
"NotificationOptionPluginUpdateInstalled": "Bővítmény frissítés telepítve",
|
||||
"NotificationOptionServerRestartRequired": "Szerver újraindítás szükséges",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionTaskFailed": "Ütemezett feladat hiba",
|
||||
"NotificationOptionUserLockedOut": "Felhasználó tiltva",
|
||||
"NotificationOptionVideoPlayback": "Videó lejátszás elkezdve",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve",
|
||||
|
@ -68,30 +68,30 @@
|
|||
"PluginUninstalledWithName": "{0} eltávolítva",
|
||||
"PluginUpdatedWithName": "{0} frissítve",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} failed",
|
||||
"ScheduledTaskStartedWithName": "{0} started",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"ScheduledTaskFailedWithName": "{0} hiba",
|
||||
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
||||
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
||||
"Shows": "Műsorok",
|
||||
"Songs": "Dalok",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Szerver betöltődik. Kérjük, próbáld meg újra később.",
|
||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}",
|
||||
"SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz {0}",
|
||||
"Sync": "Szinkronizál",
|
||||
"System": "Rendszer",
|
||||
"TvShows": "TV Műsorok",
|
||||
"User": "Felhasználó",
|
||||
"UserCreatedWithName": "User {0} has been created",
|
||||
"UserDeletedWithName": "User {0} has been deleted",
|
||||
"UserCreatedWithName": "{0} felhasználó létrehozva",
|
||||
"UserDeletedWithName": "{0} felhasználó törölve",
|
||||
"UserDownloadingItemWithValues": "{0} letölti {1}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserOfflineFromDevice": "{0} kijelentkezett innen {1}",
|
||||
"UserOnlineFromDevice": "{0} is online from {1}",
|
||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} befejezte a következőt {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"UserLockedOutWithName": "{0} felhasználó zárolva van",
|
||||
"UserOfflineFromDevice": "{0} kijelentkezett innen: {1}",
|
||||
"UserOnlineFromDevice": "{0} online itt: {1}",
|
||||
"UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
|
||||
"UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt: {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Verzió {0}"
|
||||
"VersionNumber": "Verzió: {0}"
|
||||
}
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
"Artists": "Artisti",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticato con successo",
|
||||
"Books": "Libri",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera {0}",
|
||||
"Channels": "Canali",
|
||||
"ChapterNameValue": "Capitolo {0}",
|
||||
"Collections": "Collezioni",
|
||||
"DeviceOfflineWithName": "{0} è stato disconnesso",
|
||||
"DeviceOnlineWithName": "{0} è connesso",
|
||||
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
|
||||
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
|
||||
"Favorites": "Preferiti",
|
||||
"Folders": "Cartelle",
|
||||
"Genres": "Generi",
|
||||
|
@ -19,9 +19,9 @@
|
|||
"HeaderCameraUploads": "Caricamenti Fotocamera",
|
||||
"HeaderContinueWatching": "Continua a guardare",
|
||||
"HeaderFavoriteAlbums": "Album preferiti",
|
||||
"HeaderFavoriteArtists": "Artisti preferiti",
|
||||
"HeaderFavoriteArtists": "Artisti Preferiti",
|
||||
"HeaderFavoriteEpisodes": "Episodi Preferiti",
|
||||
"HeaderFavoriteShows": "Show preferiti",
|
||||
"HeaderFavoriteShows": "Serie TV Preferite",
|
||||
"HeaderFavoriteSongs": "Brani Preferiti",
|
||||
"HeaderLiveTV": "Diretta TV",
|
||||
"HeaderNextUp": "Prossimo",
|
||||
|
|
|
@ -1,97 +1,97 @@
|
|||
{
|
||||
"Albums": "Альбомдар",
|
||||
"AppDeviceValues": "Қолданба: {0}, Құрылғы: {1}",
|
||||
"Application": "Қолданба",
|
||||
"Artists": "Орындаушылар",
|
||||
"AuthenticationSucceededWithUserName": "{0} түпнұсқалығын расталуы сәтті",
|
||||
"Books": "Кітаптар",
|
||||
"CameraImageUploadedFrom": "Жаңа сурет {0} камерасынан жүктеп алынды",
|
||||
"Channels": "Арналар",
|
||||
"ChapterNameValue": "{0}-сахна",
|
||||
"Collections": "Жиынтықтар",
|
||||
"DeviceOfflineWithName": "{0} ажыратылған",
|
||||
"DeviceOnlineWithName": "{0} қосылған",
|
||||
"FailedLoginAttemptWithUserName": "{0} тарапынан кіру әрекеті сәтсіз",
|
||||
"Favorites": "Таңдаулылар",
|
||||
"Folders": "Қалталар",
|
||||
"Genres": "Жанрлар",
|
||||
"HeaderAlbumArtists": "Альбом орындаушылары",
|
||||
"HeaderCameraUploads": "Камерадан жүктелгендер",
|
||||
"HeaderContinueWatching": "Қарауды жалғастыру",
|
||||
"HeaderFavoriteAlbums": "Таңдаулы альбомдар",
|
||||
"HeaderFavoriteArtists": "Таңдаулы орындаушылар",
|
||||
"HeaderFavoriteEpisodes": "Таңдаулы бөлімдер",
|
||||
"HeaderFavoriteShows": "Таңдаулы көрсетімдер",
|
||||
"HeaderFavoriteSongs": "Таңдаулы әуендер",
|
||||
"HeaderLiveTV": "Эфир",
|
||||
"HeaderNextUp": "Кезекті",
|
||||
"HeaderRecordingGroups": "Жазба топтары",
|
||||
"HomeVideos": "Үйлік бейнелер",
|
||||
"Inherit": "Мұраға иелену",
|
||||
"ItemAddedWithName": "{0} тасығышханаға үстелінді",
|
||||
"ItemRemovedWithName": "{0} тасығышханадан аласталды",
|
||||
"LabelIpAddressValue": "IP-мекенжайы: {0}",
|
||||
"LabelRunningTimeValue": "Іске қосылу уақыты: {0}",
|
||||
"Latest": "Ең кейінгі",
|
||||
"MessageApplicationUpdated": "Jellyfin Server жаңартылды.",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server {0} үшін жаңартылды",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Сервер теңшелімі ({0} бөлімі) жаңартылды",
|
||||
"MessageServerConfigurationUpdated": "Сервер теңшелімі жаңартылды",
|
||||
"MixedContent": "Аралас мазмұн",
|
||||
"Movies": "Фильмдер",
|
||||
"Music": "Музыка",
|
||||
"MusicVideos": "Музыкалық бейнелер",
|
||||
"NameInstallFailed": "{0} орнатылуы сәтсіз",
|
||||
"NameSeasonNumber": "{0}-маусым",
|
||||
"NameSeasonUnknown": "Белгісіз маусым",
|
||||
"NewVersionIsAvailable": "Жаңа Jellyfin Server нұсқасы жүктеп алуға қолжетімді.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Қолданба жаңартуы қолжетімді",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Қолданба жаңартуы орнатылды",
|
||||
"NotificationOptionAudioPlayback": "Дыбыс ойнатуы басталды",
|
||||
"NotificationOptionAudioPlaybackStopped": "Дыбыс ойнатуы тоқтатылды",
|
||||
"NotificationOptionCameraImageUploaded": "Камерадан фотосурет кері қотарылған",
|
||||
"NotificationOptionInstallationFailed": "Орнату сәтсіздігі",
|
||||
"NotificationOptionNewLibraryContent": "Жаңа мазмұн үстелген",
|
||||
"NotificationOptionPluginError": "Плагин сәтсіздігі",
|
||||
"NotificationOptionPluginInstalled": "Плагин орнатылды",
|
||||
"NotificationOptionPluginUninstalled": "Плагин орнатуы болдырылмады",
|
||||
"NotificationOptionPluginUpdateInstalled": "Плагин жаңартуы орнатылды",
|
||||
"NotificationOptionServerRestartRequired": "Серверді қайта іске қосу қажет",
|
||||
"NotificationOptionTaskFailed": "Жоспарлаған тапсырма сәтсіздігі",
|
||||
"NotificationOptionUserLockedOut": "Пайдаланушы құрсаулы",
|
||||
"NotificationOptionVideoPlayback": "Бейне ойнатуы басталды",
|
||||
"NotificationOptionVideoPlaybackStopped": "Бейне ойнатуы тоқтатылды",
|
||||
"Photos": "Фотосуреттер",
|
||||
"Playlists": "Ойнату тізімдері",
|
||||
"Plugin": "Плагин",
|
||||
"PluginInstalledWithName": "{0} орнатылды",
|
||||
"PluginUninstalledWithName": "{0} жойылды",
|
||||
"PluginUpdatedWithName": "{0} жаңартылды",
|
||||
"ProviderValue": "Жеткізуші: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} сәтсіз",
|
||||
"ScheduledTaskStartedWithName": "{0} іске қосылды",
|
||||
"ServerNameNeedsToBeRestarted": "{0} қайта іске қосу қажет",
|
||||
"Shows": "Көрсетімдер",
|
||||
"Songs": "Әуендер",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server жүктелуде. Әрекетті көп ұзамай қайталаңыз.",
|
||||
"Albums": "Álbomdar",
|
||||
"AppDeviceValues": "Qoldanba: {0}, Qurylǵy: {1}",
|
||||
"Application": "Qoldanba",
|
||||
"Artists": "Oryndaýshylar",
|
||||
"AuthenticationSucceededWithUserName": "{0} túpnusqalyǵyn rastalýy sátti",
|
||||
"Books": "Kitaptar",
|
||||
"CameraImageUploadedFrom": "Jańa sýret {0} kamerasynan júktep alyndy",
|
||||
"Channels": "Arnalar",
|
||||
"ChapterNameValue": "{0}-sahna",
|
||||
"Collections": "Jıyntyqtar",
|
||||
"DeviceOfflineWithName": "{0} ajyratylǵan",
|
||||
"DeviceOnlineWithName": "{0} qosylǵan",
|
||||
"FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz",
|
||||
"Favorites": "Tańdaýlylar",
|
||||
"Folders": "Qaltalar",
|
||||
"Genres": "Janrlar",
|
||||
"HeaderAlbumArtists": "Álbom oryndaýshylary",
|
||||
"HeaderCameraUploads": "Kameradan júktelgender",
|
||||
"HeaderContinueWatching": "Qaraýdy jalǵastyrý",
|
||||
"HeaderFavoriteAlbums": "Tańdaýly álbomdar",
|
||||
"HeaderFavoriteArtists": "Tańdaýly oryndaýshylar",
|
||||
"HeaderFavoriteEpisodes": "Tańdaýly bólimder",
|
||||
"HeaderFavoriteShows": "Tańdaýly kórsetimder",
|
||||
"HeaderFavoriteSongs": "Tańdaýly áýender",
|
||||
"HeaderLiveTV": "Efır",
|
||||
"HeaderNextUp": "Kezekti",
|
||||
"HeaderRecordingGroups": "Jazba toptary",
|
||||
"HomeVideos": "Úılik beıneler",
|
||||
"Inherit": "Muraǵa ıelený",
|
||||
"ItemAddedWithName": "{0} tasyǵyshhanaǵa ústelindi",
|
||||
"ItemRemovedWithName": "{0} tasyǵyshhanadan alastaldy",
|
||||
"LabelIpAddressValue": "IP-mekenjaıy: {0}",
|
||||
"LabelRunningTimeValue": "Oınatý ýaqyty: {0}",
|
||||
"Latest": "Eń keıingi",
|
||||
"MessageApplicationUpdated": "Jellyfin Serveri jańartyldy",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Serveri {0} deńgeıge jańartyldy",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server teńsheliminiń {0} bólimi jańartyldy",
|
||||
"MessageServerConfigurationUpdated": "Server teńshelimi jańartyldy",
|
||||
"MixedContent": "Aralas mazmun",
|
||||
"Movies": "Fılmder",
|
||||
"Music": "Mýzyka",
|
||||
"MusicVideos": "Mýzykalyq beıneler",
|
||||
"NameInstallFailed": "{0} ornatylýy sátsiz",
|
||||
"NameSeasonNumber": "{0}-maýsym",
|
||||
"NameSeasonUnknown": "Belgisiz maýsym",
|
||||
"NewVersionIsAvailable": "Jańa Jellyfin Server nusqasy júktep alýǵa qoljetimdi.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Qoldanba jańartýy qoljetimdi",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Qoldanba jańartýy ornatyldy",
|
||||
"NotificationOptionAudioPlayback": "Dybys oınatýy bastaldy",
|
||||
"NotificationOptionAudioPlaybackStopped": "Dybys oınatýy toqtatyldy",
|
||||
"NotificationOptionCameraImageUploaded": "Kameradan fotosýret keri qotarylǵan",
|
||||
"NotificationOptionInstallationFailed": "Ornatý sátsizdigi",
|
||||
"NotificationOptionNewLibraryContent": "Jańa mazmun ústelgen",
|
||||
"NotificationOptionPluginError": "Plagın sátsizdigi",
|
||||
"NotificationOptionPluginInstalled": "Plagın ornatyldy",
|
||||
"NotificationOptionPluginUninstalled": "Plagın ornatýy boldyrylmady",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plagın jańartýy ornatyldy",
|
||||
"NotificationOptionServerRestartRequired": "Serverdi qaıta iske qosý qajet",
|
||||
"NotificationOptionTaskFailed": "Josparlaǵan tapsyrma sátsizdigi",
|
||||
"NotificationOptionUserLockedOut": "Paıdalanýshy qursaýly",
|
||||
"NotificationOptionVideoPlayback": "Beıne oınatýy bastaldy",
|
||||
"NotificationOptionVideoPlaybackStopped": "Beıne oınatýy toqtatyldy",
|
||||
"Photos": "Fotosýretter",
|
||||
"Playlists": "Oınatý tizimderi",
|
||||
"Plugin": "Plagın",
|
||||
"PluginInstalledWithName": "{0} ornatyldy",
|
||||
"PluginUninstalledWithName": "{0} joıyldy",
|
||||
"PluginUpdatedWithName": "{0} jańartyldy",
|
||||
"ProviderValue": "Jetkizýshi: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} sátsiz",
|
||||
"ScheduledTaskStartedWithName": "{0} iske qosyldy",
|
||||
"ServerNameNeedsToBeRestarted": "{0} qaıta iske qosý qajet",
|
||||
"Shows": "Kórsetimder",
|
||||
"Songs": "Áýender",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server júktelýde. Áreketti kóp uzamaı qaıtalańyz.",
|
||||
"SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitlesDownloadedForItem": "{0} үшін субтитрлер жүктеліп алынды",
|
||||
"Sync": "Үндестіру",
|
||||
"System": "Жүйе",
|
||||
"TvShows": "ТД-көрсетімдер",
|
||||
"User": "Пайдаланушы",
|
||||
"UserCreatedWithName": "Пайдаланушы {0} жасалған",
|
||||
"UserDeletedWithName": "Пайдаланушы {0} жойылған",
|
||||
"UserDownloadingItemWithValues": "{0} мынаны жүктеп алуда: {1}",
|
||||
"UserLockedOutWithName": "Пайдаланушы {0} құрсаулы",
|
||||
"UserOfflineFromDevice": "{0} - {1} тарапынан ажыратылған",
|
||||
"UserOnlineFromDevice": "{0} - {1} арқылы қосылған",
|
||||
"UserPasswordChangedWithName": "Пайдаланушы {0} үшін құпия сөз өзгертілді",
|
||||
"UserPolicyUpdatedWithName": "Пайдаланушы {0} үшін саясаттары жаңартылды",
|
||||
"UserStartedPlayingItemWithValues": "{0} - {1} ойнатуын {2} бастады",
|
||||
"UserStoppedPlayingItemWithValues": "{0} - {1} ойнатуын {2} тоқтатты",
|
||||
"ValueHasBeenAddedToLibrary": "{0} (тасығышханаға үстелінді)",
|
||||
"ValueSpecialEpisodeName": "Арнайы - {0}",
|
||||
"VersionNumber": "Нұсқасы: {0}"
|
||||
"SubtitleDownloadFailureFromForItem": "{1} úshin sýbtıtrlerdi {0} kózinen júktep alý sátsiz",
|
||||
"SubtitlesDownloadedForItem": "{0} úshin sýbtıtrler júktelip alyndy",
|
||||
"Sync": "Úndestirý",
|
||||
"System": "Júıe",
|
||||
"TvShows": "TD-kórsetimder",
|
||||
"User": "Paıdalanýshy",
|
||||
"UserCreatedWithName": "Paıdalanýshy {0} jasalǵan",
|
||||
"UserDeletedWithName": "Paıdalanýshy {0} joıylǵan",
|
||||
"UserDownloadingItemWithValues": "{0} mynany júktep alýda: {1}",
|
||||
"UserLockedOutWithName": "Paıdalanýshy {0} qursaýly",
|
||||
"UserOfflineFromDevice": "{0} - {1} tarapynan ajyratylǵan",
|
||||
"UserOnlineFromDevice": "{0} - {1} arqyly qosylǵan",
|
||||
"UserPasswordChangedWithName": "Paıdalanýshy {0} úshin paról ózgertildi",
|
||||
"UserPolicyUpdatedWithName": "Paıdalanýshy {0} úshin saıasattary jańartyldy",
|
||||
"UserStartedPlayingItemWithValues": "{0} - {1} oınatýyn {2} bastady",
|
||||
"UserStoppedPlayingItemWithValues": "{0} - {1} oınatýyn {2} toqtatty",
|
||||
"ValueHasBeenAddedToLibrary": "{0} (tasyǵyshhanaǵa ústelindi)",
|
||||
"ValueSpecialEpisodeName": "Arnaıy - {0}",
|
||||
"VersionNumber": "Nusqasy {0}"
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"Albums": "Albums",
|
||||
"Albums": "Album-album",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"Application": "Application",
|
||||
"Artists": "Artists",
|
||||
"Artists": "Artis-artis",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"Books": "Books",
|
||||
"Books": "Buku-buku",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"Channels": "Channels",
|
||||
"ChapterNameValue": "Chapter {0}",
|
||||
|
|
|
@ -5,28 +5,28 @@
|
|||
"Artists": "Artiesten",
|
||||
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
|
||||
"Books": "Boeken",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd via {0}",
|
||||
"Channels": "Kanalen",
|
||||
"ChapterNameValue": "Hoofdstuk {0}",
|
||||
"Collections": "Collecties",
|
||||
"DeviceOfflineWithName": "{0} is losgekoppeld",
|
||||
"DeviceOfflineWithName": "{0} heeft de verbinding verbroken",
|
||||
"DeviceOnlineWithName": "{0} is verbonden",
|
||||
"FailedLoginAttemptWithUserName": "Mislukte aanmeld poging van {0}",
|
||||
"Favorites": "Favorieten",
|
||||
"Folders": "Mappen",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album artiesten",
|
||||
"HeaderCameraUploads": "Camera uploads",
|
||||
"HeaderAlbumArtists": "Albumartiesten",
|
||||
"HeaderCameraUploads": "Camera-uploads",
|
||||
"HeaderContinueWatching": "Kijken hervatten",
|
||||
"HeaderFavoriteAlbums": "Favoriete albums",
|
||||
"HeaderFavoriteArtists": "Favoriete artiesten",
|
||||
"HeaderFavoriteEpisodes": "Favoriete afleveringen",
|
||||
"HeaderFavoriteShows": "Favoriete shows",
|
||||
"HeaderFavoriteSongs": "Favoriete titels",
|
||||
"HeaderFavoriteSongs": "Favoriete nummers",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Volgende",
|
||||
"HeaderRecordingGroups": "Opnamegroepen",
|
||||
"HomeVideos": "Thuis video's",
|
||||
"HomeVideos": "Start video's",
|
||||
"Inherit": "Overerven",
|
||||
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
|
||||
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
|
||||
|
@ -34,22 +34,22 @@
|
|||
"LabelRunningTimeValue": "Looptijd: {0}",
|
||||
"Latest": "Nieuwste",
|
||||
"MessageApplicationUpdated": "Jellyfin Server is bijgewerkt",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server is bijgewerkt naar {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Sectie {0} van de server configuratie is bijgewerkt",
|
||||
"MessageServerConfigurationUpdated": "Server configuratie is bijgewerkt",
|
||||
"MixedContent": "Gemengde inhoud",
|
||||
"Movies": "Films",
|
||||
"Music": "Muziek",
|
||||
"MusicVideos": "Muziekvideo's",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameInstallFailed": "{0} installatie mislukt",
|
||||
"NameSeasonNumber": "Seizoen {0}",
|
||||
"NameSeasonUnknown": "Seizoen onbekend",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NewVersionIsAvailable": "Een nieuwe versie van Jellyfin Server is beschikbaar om te downloaden.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Programma-update beschikbaar",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Programma-update geïnstalleerd",
|
||||
"NotificationOptionAudioPlayback": "Geluid gestart",
|
||||
"NotificationOptionAudioPlaybackStopped": "Geluid gestopt",
|
||||
"NotificationOptionCameraImageUploaded": "Camera afbeelding geüpload",
|
||||
"NotificationOptionAudioPlayback": "Muziek gestart",
|
||||
"NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
|
||||
"NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
|
||||
"NotificationOptionInstallationFailed": "Installatie mislukt",
|
||||
"NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
|
||||
"NotificationOptionPluginError": "Plug-in fout",
|
||||
|
@ -70,12 +70,12 @@
|
|||
"ProviderValue": "Aanbieder: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} is mislukt",
|
||||
"ScheduledTaskStartedWithName": "{0} is gestart",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"ServerNameNeedsToBeRestarted": "{0} moet herstart worden",
|
||||
"Shows": "Series",
|
||||
"Songs": "Titels",
|
||||
"Songs": "Nummers",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server is aan het laden, probeer het later opnieuw.",
|
||||
"SubtitleDownloadFailureForItem": "Downloaden van ondertiteling voor {0} is mislukt",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Ondertitels konden niet gedownload worden van {0} voor {1}",
|
||||
"SubtitlesDownloadedForItem": "Ondertiteling voor {0} is gedownload",
|
||||
"Sync": "Synchronisatie",
|
||||
"System": "Systeem",
|
||||
|
@ -89,9 +89,9 @@
|
|||
"UserOnlineFromDevice": "{0} heeft verbinding met {1}",
|
||||
"UserPasswordChangedWithName": "Wachtwoord voor {0} is gewijzigd",
|
||||
"UserPolicyUpdatedWithName": "Gebruikersbeleid gewijzigd voor {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} heeft afspelen van {1} gestart",
|
||||
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"UserStartedPlayingItemWithValues": "{0} heeft afspelen van {1} gestart op {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
|
||||
"ValueSpecialEpisodeName": "Speciaal - {0}",
|
||||
"VersionNumber": "Versie {0}"
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"Artists": "Исполнители",
|
||||
"AuthenticationSucceededWithUserName": "{0} - авторизация успешна",
|
||||
"Books": "Литература",
|
||||
"CameraImageUploadedFrom": "Новое фото было выложено с {0}",
|
||||
"CameraImageUploadedFrom": "Новое фото было выложено с камеры {0}",
|
||||
"Channels": "Каналы",
|
||||
"ChapterNameValue": "Сцена {0}",
|
||||
"Collections": "Коллекции",
|
||||
|
@ -31,20 +31,20 @@
|
|||
"ItemAddedWithName": "{0} - добавлено в медиатеку",
|
||||
"ItemRemovedWithName": "{0} - изъято из медиатеки",
|
||||
"LabelIpAddressValue": "IP-адрес: {0}",
|
||||
"LabelRunningTimeValue": "Время выполнения: {0}",
|
||||
"LabelRunningTimeValue": "Длительность: {0}",
|
||||
"Latest": "Новейшее",
|
||||
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена",
|
||||
"MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена",
|
||||
"MixedContent": "Смешанное содержание",
|
||||
"MixedContent": "Смешанное содержимое",
|
||||
"Movies": "Кино",
|
||||
"Music": "Музыка",
|
||||
"MusicVideos": "Муз. видео",
|
||||
"NameInstallFailed": "Установка {0} неудачна",
|
||||
"NameSeasonNumber": "Сезон {0}",
|
||||
"NameSeasonUnknown": "Сезон неопознан",
|
||||
"NewVersionIsAvailable": "Имеется новая версия Jellyfin Server",
|
||||
"NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
|
||||
"NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но",
|
||||
|
@ -75,7 +75,7 @@
|
|||
"Songs": "Композиции",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
|
||||
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
|
||||
"SubtitlesDownloadedForItem": "Субтитры к {0} загружены",
|
||||
"Sync": "Синхро",
|
||||
"System": "Система",
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
/// <summary>
|
||||
/// Class ChapterImagesTask
|
||||
/// </summary>
|
||||
class ChapterImagesTask : IScheduledTask
|
||||
public class ChapterImagesTask : IScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
|
|
|
@ -41,6 +41,27 @@ namespace Emby.Server.Implementations.Serialization
|
|||
ServiceStack.Text.JsonSerializer.SerializeToStream(obj, obj.GetType(), stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes to stream.
|
||||
/// </summary>
|
||||
/// <param name="obj">The obj.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <exception cref="ArgumentNullException">obj</exception>
|
||||
public void SerializeToStream<T>(T obj, Stream stream)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
ServiceStack.Text.JsonSerializer.SerializeToStream<T>(obj, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes to file.
|
||||
/// </summary>
|
||||
|
|
|
@ -15,21 +15,17 @@ namespace Emby.Server.Implementations
|
|||
/// </summary>
|
||||
public ServerApplicationPaths(
|
||||
string programDataPath,
|
||||
string appFolderPath,
|
||||
string applicationResourcesPath,
|
||||
string logDirectoryPath = null,
|
||||
string configurationDirectoryPath = null,
|
||||
string cacheDirectoryPath = null)
|
||||
string logDirectoryPath,
|
||||
string configurationDirectoryPath,
|
||||
string cacheDirectoryPath)
|
||||
: base(programDataPath,
|
||||
appFolderPath,
|
||||
logDirectoryPath,
|
||||
configurationDirectoryPath,
|
||||
cacheDirectoryPath)
|
||||
{
|
||||
ApplicationResourcesPath = applicationResourcesPath;
|
||||
}
|
||||
|
||||
public string ApplicationResourcesPath { get; private set; }
|
||||
public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the base root media directory
|
||||
|
@ -148,7 +144,6 @@ namespace Emby.Server.Implementations
|
|||
set => _internalMetadataPath = value;
|
||||
}
|
||||
|
||||
private const string _virtualInternalMetadataPath = "%MetadataPath%";
|
||||
public string VirtualInternalMetadataPath => _virtualInternalMetadataPath;
|
||||
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
|
|||
|
||||
namespace Emby.Server.Implementations.Sorting
|
||||
{
|
||||
class AiredEpisodeOrderComparer : IBaseItemComparer
|
||||
public class AiredEpisodeOrderComparer : IBaseItemComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares the specified x.
|
||||
|
|
|
@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying;
|
|||
|
||||
namespace Emby.Server.Implementations.Sorting
|
||||
{
|
||||
class SeriesSortNameComparer : IBaseItemComparer
|
||||
public class SeriesSortNameComparer : IBaseItemComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares the specified x.
|
||||
|
|
|
@ -116,6 +116,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
private readonly IApplicationHost _applicationHost;
|
||||
|
||||
private readonly ICryptoProvider _cryptographyProvider;
|
||||
private readonly IZipClient _zipClient;
|
||||
|
||||
// netframework or netcore
|
||||
private readonly string _packageRuntime;
|
||||
|
@ -129,6 +130,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
ICryptoProvider cryptographyProvider,
|
||||
IZipClient zipClient,
|
||||
string packageRuntime)
|
||||
{
|
||||
if (loggerFactory == null)
|
||||
|
@ -146,6 +148,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
_zipClient = zipClient;
|
||||
_packageRuntime = packageRuntime;
|
||||
_logger = loggerFactory.CreateLogger(nameof(InstallationManager));
|
||||
}
|
||||
|
@ -526,14 +529,18 @@ namespace Emby.Server.Implementations.Updates
|
|||
|
||||
private async Task PerformPackageInstallation(IProgress<double> progress, string target, PackageVersionInfo package, CancellationToken cancellationToken)
|
||||
{
|
||||
// Target based on if it is an archive or single assembly
|
||||
// zip archives are assumed to contain directory structures relative to our ProgramDataPath
|
||||
var extension = Path.GetExtension(package.targetFilename);
|
||||
var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".rar", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".7z", StringComparison.OrdinalIgnoreCase);
|
||||
var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!isArchive)
|
||||
{
|
||||
_logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
target = Path.Combine(isArchive ? _appPaths.TempUpdatePath : _appPaths.PluginsPath, package.targetFilename);
|
||||
target = Path.Combine(_appPaths.PluginsPath, Path.GetFileNameWithoutExtension(package.targetFilename));
|
||||
}
|
||||
|
||||
// Download to temporary file so that, if interrupted, it won't destroy the existing installation
|
||||
|
@ -547,36 +554,19 @@ namespace Emby.Server.Implementations.Updates
|
|||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Validate with a checksum
|
||||
var packageChecksum = string.IsNullOrWhiteSpace(package.checksum) ? Guid.Empty : new Guid(package.checksum);
|
||||
if (!packageChecksum.Equals(Guid.Empty)) // support for legacy uploads for now
|
||||
{
|
||||
using (var stream = File.OpenRead(tempFile))
|
||||
{
|
||||
var check = Guid.Parse(BitConverter.ToString(_cryptographyProvider.ComputeMD5(stream)).Replace("-", string.Empty));
|
||||
if (check != packageChecksum)
|
||||
{
|
||||
throw new Exception(string.Format("Download validation failed for {0}. Probably corrupted during transfer.", package.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
// TODO: Validate with a checksum, *properly*
|
||||
|
||||
// Success - move it to the real target
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(target));
|
||||
File.Copy(tempFile, target, true);
|
||||
//If it is an archive - write out a version file so we know what it is
|
||||
if (isArchive)
|
||||
using (var stream = File.OpenRead(tempFile))
|
||||
{
|
||||
File.WriteAllText(target + ".ver", package.versionStr);
|
||||
_zipClient.ExtractAllFromZip(stream, target, true);
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error attempting to move file from {TempFile} to {TargetFile}", tempFile, target);
|
||||
_logger.LogError(ex, "Error attempting to extract {TempFile} to {TargetFile}", tempFile, target);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
|
|
@ -282,7 +282,7 @@ namespace Jellyfin.Drawing.Skia
|
|||
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
||||
|
||||
// decode
|
||||
var _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||
_ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||
|
||||
origin = codec.EncodedOrigin;
|
||||
|
||||
|
|
|
@ -5,28 +5,47 @@ using Emby.Server.Implementations.HttpServer;
|
|||
using Jellyfin.Server.SocketSharp;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server
|
||||
{
|
||||
public class CoreAppHost : ApplicationHost
|
||||
{
|
||||
public CoreAppHost(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, StartupOptions options, IFileSystem fileSystem, IEnvironmentInfo environmentInfo, MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder, MediaBrowser.Common.Net.INetworkManager networkManager)
|
||||
: base(applicationPaths, loggerFactory, options, fileSystem, environmentInfo, imageEncoder, networkManager)
|
||||
public CoreAppHost(
|
||||
ServerApplicationPaths applicationPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
StartupOptions options,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environmentInfo,
|
||||
MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder,
|
||||
MediaBrowser.Common.Net.INetworkManager networkManager,
|
||||
IConfiguration configuration)
|
||||
: base(
|
||||
applicationPaths,
|
||||
loggerFactory,
|
||||
options,
|
||||
fileSystem,
|
||||
environmentInfo,
|
||||
imageEncoder,
|
||||
networkManager,
|
||||
configuration)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool CanSelfRestart => StartupOptions.RestartPath != null;
|
||||
|
||||
protected override bool SupportsDualModeSockets => true;
|
||||
|
||||
protected override void RestartInternal() => Program.Restart();
|
||||
|
||||
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
|
||||
=> new[] { typeof(CoreAppHost).Assembly };
|
||||
{
|
||||
yield return typeof(CoreAppHost).Assembly;
|
||||
}
|
||||
|
||||
protected override void ShutdownInternal() => Program.Shutdown();
|
||||
|
||||
protected override bool SupportsDualModeSockets => true;
|
||||
|
||||
protected override IHttpListener CreateHttpListener()
|
||||
=> new WebSocketSharpListener(
|
||||
Logger,
|
||||
|
@ -37,7 +56,6 @@ namespace Jellyfin.Server
|
|||
CryptographyProvider,
|
||||
SupportsDualModeSockets,
|
||||
FileSystemManager,
|
||||
EnvironmentInfo
|
||||
);
|
||||
EnvironmentInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- We need C# 7.1 for async main-->
|
||||
<LangVersion>latest</LangVersion>
|
||||
<!-- Disable documentation warnings (for now) -->
|
||||
<NoWarn>SA1600;CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -20,6 +23,10 @@
|
|||
<EmbeddedResource Include="Resources/Configuration/*" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code analysers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
|
||||
|
|
|
@ -21,6 +21,7 @@ using MediaBrowser.Controller.Drawing;
|
|||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Serilog.AspNetCore;
|
||||
|
@ -34,6 +35,7 @@ namespace Jellyfin.Server
|
|||
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory();
|
||||
private static ILogger _logger;
|
||||
private static bool _restartOnShutdown;
|
||||
private static IConfiguration appConfig;
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
|
@ -56,13 +58,32 @@ namespace Jellyfin.Server
|
|||
errs => Task.FromResult(0)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
if (!_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Restart()
|
||||
{
|
||||
_restartOnShutdown = true;
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
private static async Task StartApp(StartupOptions options)
|
||||
{
|
||||
ServerApplicationPaths appPaths = CreateApplicationPaths(options);
|
||||
|
||||
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
|
||||
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
|
||||
await CreateLogger(appPaths);
|
||||
|
||||
appConfig = await CreateConfiguration(appPaths).ConfigureAwait(false);
|
||||
|
||||
CreateLogger(appConfig, appPaths);
|
||||
|
||||
_logger = _loggerFactory.CreateLogger("Main");
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e)
|
||||
|
@ -75,6 +96,7 @@ namespace Jellyfin.Server
|
|||
{
|
||||
return; // Already shutting down
|
||||
}
|
||||
|
||||
e.Cancel = true;
|
||||
_logger.LogInformation("Ctrl+C, shutting down");
|
||||
Environment.ExitCode = 128 + 2;
|
||||
|
@ -88,6 +110,7 @@ namespace Jellyfin.Server
|
|||
{
|
||||
return; // Already shutting down
|
||||
}
|
||||
|
||||
_logger.LogInformation("Received a SIGTERM signal, shutting down");
|
||||
Environment.ExitCode = 128 + 15;
|
||||
Shutdown();
|
||||
|
@ -101,9 +124,9 @@ namespace Jellyfin.Server
|
|||
SQLitePCL.Batteries_V2.Init();
|
||||
|
||||
// Allow all https requests
|
||||
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
|
||||
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; } );
|
||||
|
||||
var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, null, appPaths.TempDirectory, true);
|
||||
var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, appPaths);
|
||||
|
||||
using (var appHost = new CoreAppHost(
|
||||
appPaths,
|
||||
|
@ -112,20 +135,21 @@ namespace Jellyfin.Server
|
|||
fileSystem,
|
||||
environmentInfo,
|
||||
new NullImageEncoder(),
|
||||
new NetworkManager(_loggerFactory, environmentInfo)))
|
||||
new NetworkManager(_loggerFactory, environmentInfo),
|
||||
appConfig))
|
||||
{
|
||||
await appHost.Init();
|
||||
await appHost.Init(new ServiceCollection()).ConfigureAwait(false);
|
||||
|
||||
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
|
||||
|
||||
await appHost.RunStartupTasks();
|
||||
await appHost.RunStartupTasks().ConfigureAwait(false);
|
||||
|
||||
// TODO: read input for a stop command
|
||||
|
||||
try
|
||||
{
|
||||
// Block main thread until shutdown
|
||||
await Task.Delay(-1, _tokenSource.Token);
|
||||
await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
|
@ -139,136 +163,185 @@ namespace Jellyfin.Server
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the data, config and log paths from the variety of inputs(command line args,
|
||||
/// environment variables) or decide on what default to use. For Windows it's %AppPath%
|
||||
/// for everything else the XDG approach is followed:
|
||||
/// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
/// </summary>
|
||||
/// <param name="options">StartupOptions</param>
|
||||
/// <returns>ServerApplicationPaths</returns>
|
||||
private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
|
||||
{
|
||||
string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
|
||||
if (string.IsNullOrEmpty(programDataPath))
|
||||
// dataDir
|
||||
// IF --datadir
|
||||
// ELSE IF $JELLYFIN_DATA_PATH
|
||||
// ELSE IF windows, use <%APPDATA%>/jellyfin
|
||||
// ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin
|
||||
// ELSE use $HOME/.local/share/jellyfin
|
||||
var dataDir = options.DataDir;
|
||||
|
||||
if (string.IsNullOrEmpty(dataDir))
|
||||
{
|
||||
if (options.DataDir != null)
|
||||
{
|
||||
programDataPath = options.DataDir;
|
||||
}
|
||||
else
|
||||
dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
|
||||
|
||||
if (string.IsNullOrEmpty(dataDir))
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
|
||||
programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
|
||||
// If $XDG_DATA_HOME is either not set or empty, $HOME/.local/share should be used.
|
||||
if (string.IsNullOrEmpty(programDataPath))
|
||||
dataDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
|
||||
|
||||
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
|
||||
if (string.IsNullOrEmpty(dataDir))
|
||||
{
|
||||
programDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
|
||||
dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
|
||||
}
|
||||
}
|
||||
|
||||
programDataPath = Path.Combine(programDataPath, "jellyfin");
|
||||
dataDir = Path.Combine(dataDir, "jellyfin");
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(programDataPath))
|
||||
{
|
||||
Console.WriteLine("Cannot continue without path to program data folder (try -programdata)");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(programDataPath);
|
||||
}
|
||||
// configDir
|
||||
// IF --configdir
|
||||
// ELSE IF $JELLYFIN_CONFIG_DIR
|
||||
// ELSE IF --datadir, use <datadir>/config (assume portable run)
|
||||
// ELSE IF <datadir>/config exists, use that
|
||||
// ELSE IF windows, use <datadir>/config
|
||||
// ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
|
||||
// ELSE $HOME/.config/jellyfin
|
||||
var configDir = options.ConfigDir;
|
||||
|
||||
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
|
||||
if (string.IsNullOrEmpty(configDir))
|
||||
{
|
||||
if (options.ConfigDir != null)
|
||||
configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
|
||||
|
||||
if (string.IsNullOrEmpty(configDir))
|
||||
{
|
||||
configDir = options.ConfigDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let BaseApplicationPaths set up the default value
|
||||
configDir = null;
|
||||
if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Hang config folder off already set dataDir
|
||||
configDir = Path.Combine(dataDir, "config");
|
||||
}
|
||||
else
|
||||
{
|
||||
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
|
||||
configDir = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
|
||||
|
||||
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME /.config should be used.
|
||||
if (string.IsNullOrEmpty(configDir))
|
||||
{
|
||||
configDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
|
||||
}
|
||||
|
||||
configDir = Path.Combine(configDir, "jellyfin");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (configDir != null)
|
||||
{
|
||||
Directory.CreateDirectory(configDir);
|
||||
}
|
||||
// cacheDir
|
||||
// IF --cachedir
|
||||
// ELSE IF $JELLYFIN_CACHE_DIR
|
||||
// ELSE IF windows, use <datadir>/cache
|
||||
// ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin
|
||||
// ELSE HOME/.cache/jellyfin
|
||||
var cacheDir = options.CacheDir;
|
||||
|
||||
string cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
|
||||
if (string.IsNullOrEmpty(cacheDir))
|
||||
{
|
||||
if (options.CacheDir != null)
|
||||
cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
|
||||
|
||||
if (string.IsNullOrEmpty(cacheDir))
|
||||
{
|
||||
cacheDir = options.CacheDir;
|
||||
}
|
||||
else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
|
||||
cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
|
||||
// If $XDG_CACHE_HOME is either not set or empty, $HOME/.cache should be used.
|
||||
if (string.IsNullOrEmpty(cacheDir))
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
|
||||
// Hang cache folder off already set dataDir
|
||||
cacheDir = Path.Combine(dataDir, "cache");
|
||||
}
|
||||
else
|
||||
{
|
||||
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
|
||||
cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
|
||||
|
||||
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
|
||||
if (string.IsNullOrEmpty(cacheDir))
|
||||
{
|
||||
cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
|
||||
}
|
||||
|
||||
cacheDir = Path.Combine(cacheDir, "jellyfin");
|
||||
}
|
||||
cacheDir = Path.Combine(cacheDir, "jellyfin");
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheDir != null)
|
||||
{
|
||||
Directory.CreateDirectory(cacheDir);
|
||||
}
|
||||
// logDir
|
||||
// IF --logdir
|
||||
// ELSE IF $JELLYFIN_LOG_DIR
|
||||
// ELSE IF --datadir, use <datadir>/log (assume portable run)
|
||||
// ELSE <datadir>/log
|
||||
var logDir = options.LogDir;
|
||||
|
||||
string logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
{
|
||||
if (options.LogDir != null)
|
||||
logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
|
||||
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
{
|
||||
logDir = options.LogDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let BaseApplicationPaths set up the default value
|
||||
logDir = null;
|
||||
// Hang log folder off already set dataDir
|
||||
logDir = Path.Combine(dataDir, "log");
|
||||
}
|
||||
}
|
||||
|
||||
if (logDir != null)
|
||||
// Ensure the main folders exist before we continue
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(dataDir);
|
||||
Directory.CreateDirectory(logDir);
|
||||
Directory.CreateDirectory(configDir);
|
||||
Directory.CreateDirectory(cacheDir);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Console.Error.WriteLine("Error whilst attempting to create folder");
|
||||
Console.Error.WriteLine(ex.ToString());
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
string appPath = AppContext.BaseDirectory;
|
||||
|
||||
return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir);
|
||||
return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir);
|
||||
}
|
||||
|
||||
private static async Task CreateLogger(IApplicationPaths appPaths)
|
||||
private static async Task<IConfiguration> CreateConfiguration(IApplicationPaths appPaths)
|
||||
{
|
||||
string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json");
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
// For some reason the csproj name is used instead of the assembly name
|
||||
using (Stream rscstr = typeof(Program).Assembly
|
||||
.GetManifestResourceStream("Jellyfin.Server.Resources.Configuration.logging.json"))
|
||||
using (Stream fstr = File.Open(configPath, FileMode.CreateNew))
|
||||
{
|
||||
await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return new ConfigurationBuilder()
|
||||
.SetBasePath(appPaths.ConfigurationDirectoryPath)
|
||||
.AddJsonFile("logging.json")
|
||||
.AddEnvironmentVariables("JELLYFIN_")
|
||||
.AddInMemoryCollection(ConfigurationOptions.Configuration)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static void CreateLogger(IConfiguration configuration, IApplicationPaths appPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json");
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
// For some reason the csproj name is used instead of the assembly name
|
||||
using (Stream rscstr = typeof(Program).Assembly
|
||||
.GetManifestResourceStream("Jellyfin.Server.Resources.Configuration.logging.json"))
|
||||
using (Stream fstr = File.Open(configPath, FileMode.CreateNew))
|
||||
{
|
||||
await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(appPaths.ConfigurationDirectoryPath)
|
||||
.AddJsonFile("logging.json")
|
||||
.AddEnvironmentVariables("JELLYFIN_")
|
||||
.Build();
|
||||
|
||||
// Serilog.Log is used by SerilogLoggerFactory when no logger is specified
|
||||
Serilog.Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(configuration)
|
||||
|
@ -290,7 +363,7 @@ namespace Jellyfin.Server
|
|||
}
|
||||
}
|
||||
|
||||
public static IImageEncoder GetImageEncoder(
|
||||
private static IImageEncoder GetImageEncoder(
|
||||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILocalizationManager localizationManager)
|
||||
|
@ -331,26 +404,12 @@ namespace Jellyfin.Server
|
|||
{
|
||||
return MediaBrowser.Model.System.OperatingSystem.BSD;
|
||||
}
|
||||
|
||||
throw new Exception($"Can't resolve OS with description: '{osDescription}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
if (!_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Restart()
|
||||
{
|
||||
_restartOnShutdown = true;
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
private static void StartNewInstance(StartupOptions options)
|
||||
{
|
||||
_logger.LogInformation("Starting new instance");
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
internal static string GetParameter(string header, string attr)
|
||||
{
|
||||
int ap = header.IndexOf(attr);
|
||||
int ap = header.IndexOf(attr, StringComparison.Ordinal);
|
||||
if (ap == -1)
|
||||
{
|
||||
return null;
|
||||
|
@ -82,9 +82,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// We use a substream, as in 2.x we will support large uploads streamed to disk,
|
||||
//
|
||||
var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
|
||||
files[e.Name] = sub;
|
||||
}
|
||||
|
@ -127,8 +125,12 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
|
||||
|
||||
protected bool validate_cookies, validate_query_string, validate_form;
|
||||
protected bool checked_cookies, checked_query_string, checked_form;
|
||||
protected bool validate_cookies { get; set; }
|
||||
protected bool validate_query_string { get; set; }
|
||||
protected bool validate_form { get; set; }
|
||||
protected bool checked_cookies { get; set; }
|
||||
protected bool checked_query_string { get; set; }
|
||||
protected bool checked_form { get; set; }
|
||||
|
||||
private static void ThrowValidationException(string name, string key, string value)
|
||||
{
|
||||
|
@ -138,8 +140,12 @@ namespace Jellyfin.Server.SocketSharp
|
|||
v = v.Substring(0, 16) + "...\"";
|
||||
}
|
||||
|
||||
string msg = string.Format("A potentially dangerous Request.{0} value was " +
|
||||
"detected from the client ({1}={2}).", name, key, v);
|
||||
string msg = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
|
||||
name,
|
||||
key,
|
||||
v);
|
||||
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
@ -179,6 +185,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
for (int idx = 1; idx < len; idx++)
|
||||
{
|
||||
char next = val[idx];
|
||||
|
||||
// See http://secunia.com/advisories/14325
|
||||
if (current == '<' || current == '\xff1c')
|
||||
{
|
||||
|
@ -256,6 +263,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
value.Append((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == -1)
|
||||
{
|
||||
AddRawKeyValue(form, key, value);
|
||||
|
@ -271,6 +279,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
key.Append((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == -1)
|
||||
{
|
||||
AddRawKeyValue(form, key, value);
|
||||
|
@ -308,6 +317,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
result.Append(key);
|
||||
result.Append('=');
|
||||
}
|
||||
|
||||
result.Append(pair.Value);
|
||||
}
|
||||
|
||||
|
@ -429,13 +439,13 @@ namespace Jellyfin.Server.SocketSharp
|
|||
real = position + d;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(nameof(origin));
|
||||
throw new ArgumentException("Unknown SeekOrigin value", nameof(origin));
|
||||
}
|
||||
|
||||
long virt = real - offset;
|
||||
if (virt < 0 || virt > Length)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
throw new ArgumentException("Invalid position", nameof(d));
|
||||
}
|
||||
|
||||
position = s.Seek(real, SeekOrigin.Begin);
|
||||
|
@ -491,11 +501,6 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public Stream InputStream => stream;
|
||||
}
|
||||
|
||||
private class Helpers
|
||||
{
|
||||
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
|
||||
}
|
||||
|
||||
internal static class StrUtils
|
||||
{
|
||||
public static bool StartsWith(string str1, string str2, bool ignore_case)
|
||||
|
@ -533,12 +538,17 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public class Element
|
||||
{
|
||||
public string ContentType;
|
||||
public string Name;
|
||||
public string Filename;
|
||||
public Encoding Encoding;
|
||||
public long Start;
|
||||
public long Length;
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Filename { get; set; }
|
||||
|
||||
public Encoding Encoding { get; set; }
|
||||
|
||||
public long Start { get; set; }
|
||||
|
||||
public long Length { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
@ -547,15 +557,23 @@ namespace Jellyfin.Server.SocketSharp
|
|||
}
|
||||
}
|
||||
|
||||
private Stream data;
|
||||
private string boundary;
|
||||
private byte[] boundary_bytes;
|
||||
private byte[] buffer;
|
||||
private bool at_eof;
|
||||
private Encoding encoding;
|
||||
private StringBuilder sb;
|
||||
private const byte LF = (byte)'\n';
|
||||
|
||||
private const byte LF = (byte)'\n', CR = (byte)'\r';
|
||||
private const byte CR = (byte)'\r';
|
||||
|
||||
private Stream data;
|
||||
|
||||
private string boundary;
|
||||
|
||||
private byte[] boundaryBytes;
|
||||
|
||||
private byte[] buffer;
|
||||
|
||||
private bool atEof;
|
||||
|
||||
private Encoding encoding;
|
||||
|
||||
private StringBuilder sb;
|
||||
|
||||
// See RFC 2046
|
||||
// In the case of multipart entities, in which one or more different
|
||||
|
@ -570,18 +588,48 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public HttpMultipart(Stream data, string b, Encoding encoding)
|
||||
{
|
||||
this.data = data;
|
||||
//DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET
|
||||
//var ms = new MemoryStream(32 * 1024);
|
||||
//data.CopyTo(ms);
|
||||
//this.data = ms;
|
||||
|
||||
boundary = b;
|
||||
boundary_bytes = encoding.GetBytes(b);
|
||||
buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--'
|
||||
boundaryBytes = encoding.GetBytes(b);
|
||||
buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
|
||||
this.encoding = encoding;
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
public Element ReadNextElement()
|
||||
{
|
||||
if (atEof || ReadBoundary())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var elem = new Element();
|
||||
string header;
|
||||
while ((header = ReadHeaders()) != null)
|
||||
{
|
||||
if (StrUtils.StartsWith(header, "Content-Disposition:", true))
|
||||
{
|
||||
elem.Name = GetContentDispositionAttribute(header, "name");
|
||||
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
|
||||
}
|
||||
else if (StrUtils.StartsWith(header, "Content-Type:", true))
|
||||
{
|
||||
elem.ContentType = header.Substring("Content-Type:".Length).Trim();
|
||||
elem.Encoding = GetEncoding(elem.ContentType);
|
||||
}
|
||||
}
|
||||
|
||||
long start = data.Position;
|
||||
elem.Start = start;
|
||||
long pos = MoveToNextBoundary();
|
||||
if (pos == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
elem.Length = pos - start;
|
||||
return elem;
|
||||
}
|
||||
|
||||
private string ReadLine()
|
||||
{
|
||||
// CRLF or LF are ok as line endings.
|
||||
|
@ -600,6 +648,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
break;
|
||||
}
|
||||
|
||||
got_cr = b == CR;
|
||||
sb.Append((char)b);
|
||||
}
|
||||
|
@ -769,7 +818,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (!CompareBytes(boundary_bytes, buffer))
|
||||
if (!CompareBytes(boundaryBytes, buffer))
|
||||
{
|
||||
state = 0;
|
||||
data.Position = retval + 2;
|
||||
|
@ -785,7 +834,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
|
||||
{
|
||||
at_eof = true;
|
||||
atEof = true;
|
||||
}
|
||||
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
|
||||
{
|
||||
|
@ -800,6 +849,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
c = data.ReadByte();
|
||||
continue;
|
||||
}
|
||||
|
||||
data.Position = retval + 2;
|
||||
if (got_cr)
|
||||
{
|
||||
|
@ -818,42 +868,6 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return retval;
|
||||
}
|
||||
|
||||
public Element ReadNextElement()
|
||||
{
|
||||
if (at_eof || ReadBoundary())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var elem = new Element();
|
||||
string header;
|
||||
while ((header = ReadHeaders()) != null)
|
||||
{
|
||||
if (StrUtils.StartsWith(header, "Content-Disposition:", true))
|
||||
{
|
||||
elem.Name = GetContentDispositionAttribute(header, "name");
|
||||
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
|
||||
}
|
||||
else if (StrUtils.StartsWith(header, "Content-Type:", true))
|
||||
{
|
||||
elem.ContentType = header.Substring("Content-Type:".Length).Trim();
|
||||
elem.Encoding = GetEncoding(elem.ContentType);
|
||||
}
|
||||
}
|
||||
|
||||
long start = 0;
|
||||
start = data.Position;
|
||||
elem.Start = start;
|
||||
long pos = MoveToNextBoundary();
|
||||
if (pos == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
elem.Length = pos - start;
|
||||
return elem;
|
||||
}
|
||||
|
||||
private static string StripPath(string path)
|
||||
{
|
||||
if (path == null || path.Length == 0)
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private bool _disposed = false;
|
||||
|
||||
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
|
||||
{
|
||||
|
@ -40,9 +41,9 @@ namespace Jellyfin.Server.SocketSharp
|
|||
_logger = logger;
|
||||
WebSocket = socket;
|
||||
|
||||
socket.OnMessage += socket_OnMessage;
|
||||
socket.OnClose += socket_OnClose;
|
||||
socket.OnError += socket_OnError;
|
||||
socket.OnMessage += OnSocketMessage;
|
||||
socket.OnClose += OnSocketClose;
|
||||
socket.OnError += OnSocketError;
|
||||
|
||||
WebSocket.ConnectAsServer();
|
||||
}
|
||||
|
@ -52,29 +53,22 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return _taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e)
|
||||
private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e)
|
||||
{
|
||||
_logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty);
|
||||
//Closed?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
// Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e)
|
||||
private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e)
|
||||
{
|
||||
_taskCompletionSource.TrySetResult(true);
|
||||
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e)
|
||||
private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e)
|
||||
{
|
||||
//if (!string.IsNullOrEmpty(e.Data))
|
||||
//{
|
||||
// if (OnReceive != null)
|
||||
// {
|
||||
// OnReceive(e.Data);
|
||||
// }
|
||||
// return;
|
||||
//}
|
||||
if (OnReceiveBytes != null)
|
||||
{
|
||||
OnReceiveBytes(e.RawData);
|
||||
|
@ -117,6 +111,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -125,16 +120,23 @@ namespace Jellyfin.Server.SocketSharp
|
|||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispose)
|
||||
{
|
||||
WebSocket.OnMessage -= socket_OnMessage;
|
||||
WebSocket.OnClose -= socket_OnClose;
|
||||
WebSocket.OnError -= socket_OnError;
|
||||
WebSocket.OnMessage -= OnSocketMessage;
|
||||
WebSocket.OnClose -= OnSocketClose;
|
||||
WebSocket.OnError -= OnSocketError;
|
||||
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
||||
WebSocket.Close();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -142,11 +144,5 @@ namespace Jellyfin.Server.SocketSharp
|
|||
/// </summary>
|
||||
/// <value>The receive action.</value>
|
||||
public Action<byte[]> OnReceiveBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the on receive.
|
||||
/// </summary>
|
||||
/// <value>The on receive.</value>
|
||||
public Action<string> OnReceive { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,9 +34,16 @@ namespace Jellyfin.Server.SocketSharp
|
|||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||
private CancellationToken _disposeCancellationToken;
|
||||
|
||||
public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IStreamHelper streamHelper,
|
||||
INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider,
|
||||
bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment)
|
||||
public WebSocketSharpListener(
|
||||
ILogger logger,
|
||||
X509Certificate certificate,
|
||||
IStreamHelper streamHelper,
|
||||
INetworkManager networkManager,
|
||||
ISocketFactory socketFactory,
|
||||
ICryptoProvider cryptoProvider,
|
||||
bool enableDualMode,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environment)
|
||||
{
|
||||
_logger = logger;
|
||||
_certificate = certificate;
|
||||
|
@ -61,7 +68,9 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public void Start(IEnumerable<string> urlPrefixes)
|
||||
{
|
||||
if (_listener == null)
|
||||
{
|
||||
_listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment);
|
||||
}
|
||||
|
||||
_listener.EnableDualMode = _enableDualMode;
|
||||
|
||||
|
@ -83,15 +92,18 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
private void ProcessContext(HttpListenerContext context)
|
||||
{
|
||||
var _ = Task.Run(async () => await InitTask(context, _disposeCancellationToken));
|
||||
_ = Task.Run(async () => await InitTask(context, _disposeCancellationToken).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
private static void LogRequest(ILogger logger, HttpListenerRequest request)
|
||||
{
|
||||
var url = request.Url.ToString();
|
||||
|
||||
logger.LogInformation("{0} {1}. UserAgent: {2}",
|
||||
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
|
||||
logger.LogInformation(
|
||||
"{0} {1}. UserAgent: {2}",
|
||||
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod,
|
||||
url,
|
||||
request.UserAgent ?? string.Empty);
|
||||
}
|
||||
|
||||
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
|
||||
|
@ -201,7 +213,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
//TODO Investigate and properly fix.
|
||||
// TODO: Investigate and properly fix.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -223,38 +235,39 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public Task Stop()
|
||||
{
|
||||
_disposeCancellationTokenSource.Cancel();
|
||||
|
||||
if (_listener != null)
|
||||
{
|
||||
_listener.Close();
|
||||
}
|
||||
_listener?.Close();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources and disposes of the managed resources used.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
private readonly object _disposeLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources and disposes of the managed resources used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">Whether or not the managed resources should be disposed</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
lock (_disposeLock)
|
||||
if (_disposed)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
//release unmanaged resources here...
|
||||
_disposed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Stop().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
|
@ -24,31 +25,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
this.request = httpContext.Request;
|
||||
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
|
||||
|
||||
//HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
|
||||
}
|
||||
|
||||
private static string GetHandlerPathIfAny(string listenerUrl)
|
||||
{
|
||||
if (listenerUrl == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase);
|
||||
if (pos == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var startHostUrl = listenerUrl.Substring(pos + "://".Length);
|
||||
var endPos = startHostUrl.IndexOf('/');
|
||||
if (endPos == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var endHostUrl = startHostUrl.Substring(endPos + 1);
|
||||
return string.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/');
|
||||
// HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
|
||||
}
|
||||
|
||||
public HttpListenerRequest HttpRequest => request;
|
||||
|
@ -69,9 +46,11 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public string UserHostAddress => request.UserHostAddress;
|
||||
|
||||
public string XForwardedFor => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
|
||||
public string XForwardedFor
|
||||
=> string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
|
||||
|
||||
public int? XForwardedPort => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]);
|
||||
public int? XForwardedPort
|
||||
=> string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture);
|
||||
|
||||
public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"];
|
||||
|
||||
|
@ -99,7 +78,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
name = name.Trim(HttpTrimCharacters);
|
||||
|
||||
// First, check for correctly formed multi-line value
|
||||
// Second, check for absenece of CTL characters
|
||||
// Second, check for absence of CTL characters
|
||||
int crlf = 0;
|
||||
for (int i = 0; i < name.Length; ++i)
|
||||
{
|
||||
|
@ -107,6 +86,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
switch (crlf)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if (c == '\r')
|
||||
{
|
||||
crlf = 1;
|
||||
|
@ -121,29 +101,39 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
throw new ArgumentException("net_WebHeaderInvalidControlChars");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
crlf = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
if (c == ' ' || c == '\t')
|
||||
{
|
||||
crlf = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (crlf != 0)
|
||||
{
|
||||
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
@ -156,6 +146,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -216,8 +207,15 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
foreach (var acceptsType in acceptContentTypes)
|
||||
{
|
||||
var contentType = HttpResultFactory.GetRealContentType(acceptsType);
|
||||
acceptsAnything = acceptsAnything || contentType == "*/*";
|
||||
// TODO: @bond move to Span when Span.Split lands
|
||||
// https://github.com/dotnet/corefx/issues/26528
|
||||
var contentType = acceptsType?.Split(';')[0].Trim();
|
||||
acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (acceptsAnything)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (acceptsAnything)
|
||||
|
@ -226,7 +224,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
return defaultContentType;
|
||||
}
|
||||
else if (serverDefaultContentType != null)
|
||||
else
|
||||
{
|
||||
return serverDefaultContentType;
|
||||
}
|
||||
|
@ -269,11 +267,11 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
private static string GetQueryStringContentType(IRequest httpReq)
|
||||
{
|
||||
var format = httpReq.QueryString["format"];
|
||||
ReadOnlySpan<char> format = httpReq.QueryString["format"];
|
||||
if (format == null)
|
||||
{
|
||||
const int formatMaxLength = 4;
|
||||
var pi = httpReq.PathInfo;
|
||||
ReadOnlySpan<char> pi = httpReq.PathInfo;
|
||||
if (pi == null || pi.Length <= formatMaxLength)
|
||||
{
|
||||
return null;
|
||||
|
@ -281,7 +279,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
if (pi[0] == '/')
|
||||
{
|
||||
pi = pi.Substring(1);
|
||||
pi = pi.Slice(1);
|
||||
}
|
||||
|
||||
format = LeftPart(pi, '/');
|
||||
|
@ -315,6 +313,17 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return pos == -1 ? strVal : strVal.Substring(0, pos);
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<char> LeftPart(ReadOnlySpan<char> strVal, char needle)
|
||||
{
|
||||
if (strVal == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var pos = strVal.IndexOf(needle);
|
||||
return pos == -1 ? strVal : strVal.Slice(0, pos);
|
||||
}
|
||||
|
||||
public static string HandlerFactoryPath;
|
||||
|
||||
private string pathInfo;
|
||||
|
@ -326,7 +335,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
var mode = HandlerFactoryPath;
|
||||
|
||||
var pos = request.RawUrl.IndexOf("?", StringComparison.Ordinal);
|
||||
var pos = request.RawUrl.IndexOf('?', StringComparison.Ordinal);
|
||||
if (pos != -1)
|
||||
{
|
||||
var path = request.RawUrl.Substring(0, pos);
|
||||
|
@ -343,6 +352,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
|
||||
this.pathInfo = NormalizePathInfo(pathInfo, mode);
|
||||
}
|
||||
|
||||
return this.pathInfo;
|
||||
}
|
||||
}
|
||||
|
@ -444,7 +454,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public string ContentType => request.ContentType;
|
||||
|
||||
public Encoding contentEncoding;
|
||||
private Encoding contentEncoding;
|
||||
public Encoding ContentEncoding
|
||||
{
|
||||
get => contentEncoding ?? request.ContentEncoding;
|
||||
|
@ -502,16 +512,20 @@ namespace Jellyfin.Server.SocketSharp
|
|||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return httpFiles;
|
||||
}
|
||||
}
|
||||
|
||||
public static string NormalizePathInfo(string pathInfo, string handlerPath)
|
||||
{
|
||||
var trimmed = pathInfo.TrimStart('/');
|
||||
if (handlerPath != null && trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase))
|
||||
if (handlerPath != null)
|
||||
{
|
||||
return trimmed.Substring(handlerPath.Length);
|
||||
var trimmed = pathInfo.TrimStart('/');
|
||||
if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return trimmed.Substring(handlerPath.Length);
|
||||
}
|
||||
}
|
||||
|
||||
return pathInfo;
|
||||
|
|
|
@ -13,12 +13,12 @@ using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
|
|||
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
|
||||
using IRequest = MediaBrowser.Model.Services.IRequest;
|
||||
|
||||
|
||||
namespace Jellyfin.Server.SocketSharp
|
||||
{
|
||||
public class WebSocketSharpResponse : IHttpResponse
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly HttpListenerResponse _response;
|
||||
|
||||
public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request)
|
||||
|
@ -30,7 +30,9 @@ namespace Jellyfin.Server.SocketSharp
|
|||
}
|
||||
|
||||
public IRequest Request { get; private set; }
|
||||
|
||||
public Dictionary<string, object> Items { get; private set; }
|
||||
|
||||
public object OriginalResponse => _response;
|
||||
|
||||
public int StatusCode
|
||||
|
@ -51,7 +53,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
set => _response.ContentType = value;
|
||||
}
|
||||
|
||||
//public ICookies Cookies { get; set; }
|
||||
public QueryParamCollection Headers => _response.Headers;
|
||||
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
|
@ -64,8 +66,6 @@ namespace Jellyfin.Server.SocketSharp
|
|||
_response.AddHeader(name, value);
|
||||
}
|
||||
|
||||
public QueryParamCollection Headers => _response.Headers;
|
||||
|
||||
public string GetHeader(string name)
|
||||
{
|
||||
return _response.Headers[name];
|
||||
|
@ -114,9 +114,9 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public void SetContentLength(long contentLength)
|
||||
{
|
||||
//you can happily set the Content-Length header in Asp.Net
|
||||
//but HttpListener will complain if you do - you have to set ContentLength64 on the response.
|
||||
//workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
|
||||
// you can happily set the Content-Length header in Asp.Net
|
||||
// but HttpListener will complain if you do - you have to set ContentLength64 on the response.
|
||||
// workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
|
||||
_response.ContentLength64 = contentLength;
|
||||
}
|
||||
|
||||
|
@ -147,15 +147,12 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
sb.Append($";domain={cookie.Domain}");
|
||||
}
|
||||
//else if (restrictAllCookiesToDomain != null)
|
||||
//{
|
||||
// sb.Append($";domain={restrictAllCookiesToDomain}");
|
||||
//}
|
||||
|
||||
if (cookie.Secure)
|
||||
{
|
||||
sb.Append(";Secure");
|
||||
}
|
||||
|
||||
if (cookie.HttpOnly)
|
||||
{
|
||||
sb.Append(";HttpOnly");
|
||||
|
@ -164,7 +161,6 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
public bool SendChunked
|
||||
{
|
||||
get => _response.SendChunked;
|
||||
|
|
|
@ -9,6 +9,7 @@ using MediaBrowser.Controller.Net;
|
|||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Services;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Api
|
||||
|
@ -118,8 +119,7 @@ namespace MediaBrowser.Api
|
|||
{
|
||||
var options = new DtoOptions();
|
||||
|
||||
var hasFields = request as IHasItemFields;
|
||||
if (hasFields != null)
|
||||
if (request is IHasItemFields hasFields)
|
||||
{
|
||||
options.Fields = hasFields.GetItemFields();
|
||||
}
|
||||
|
@ -133,9 +133,11 @@ namespace MediaBrowser.Api
|
|||
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
var list = options.Fields.ToList();
|
||||
list.Add(Model.Querying.ItemFields.RecursiveItemCount);
|
||||
options.Fields = list.ToArray();
|
||||
int oldLen = options.Fields.Length;
|
||||
var arr = new ItemFields[oldLen + 1];
|
||||
options.Fields.CopyTo(arr, 0);
|
||||
arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
|
||||
options.Fields = arr;
|
||||
}
|
||||
|
||||
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
|
@ -146,9 +148,12 @@ namespace MediaBrowser.Api
|
|||
client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
var list = options.Fields.ToList();
|
||||
list.Add(Model.Querying.ItemFields.ChildCount);
|
||||
options.Fields = list.ToArray();
|
||||
|
||||
int oldLen = options.Fields.Length;
|
||||
var arr = new ItemFields[oldLen + 1];
|
||||
options.Fields.CopyTo(arr, 0);
|
||||
arr[oldLen] = Model.Querying.ItemFields.ChildCount;
|
||||
options.Fields = arr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,7 +172,16 @@ namespace MediaBrowser.Api
|
|||
|
||||
if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
|
||||
{
|
||||
options.ImageTypes = (hasDtoOptions.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
|
||||
if (string.IsNullOrEmpty(hasDtoOptions.EnableImageTypes))
|
||||
{
|
||||
options.ImageTypes = Array.Empty<ImageType>();
|
||||
}
|
||||
else
|
||||
{
|
||||
options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -173,14 +173,8 @@ namespace MediaBrowser.Api
|
|||
_fileSystem.DeleteFile(file);
|
||||
}
|
||||
|
||||
public object Get(GetDefaultDirectoryBrowser request)
|
||||
{
|
||||
var result = new DefaultDirectoryBrowserInfo();
|
||||
|
||||
result.Path = _fileSystem.DefaultDirectory;
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
public object Get(GetDefaultDirectoryBrowser request) =>
|
||||
ToOptimizedResult(new DefaultDirectoryBrowserInfo {Path = null});
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
|
|
|
@ -8,7 +8,6 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
|
@ -71,7 +70,6 @@ namespace MediaBrowser.Api.Playback
|
|||
protected IMediaSourceManager MediaSourceManager { get; private set; }
|
||||
protected IJsonSerializer JsonSerializer { get; private set; }
|
||||
|
||||
public static IHttpClient HttpClient;
|
||||
protected IAuthorizationContext AuthorizationContext { get; private set; }
|
||||
|
||||
protected EncodingHelper EncodingHelper { get; set; }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
|
@ -33,6 +34,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
public class AudioService : BaseProgressiveStreamingService
|
||||
{
|
||||
public AudioService(
|
||||
IHttpClient httpClient,
|
||||
IServerConfigurationManager serverConfig,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
|
@ -46,7 +48,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext,
|
||||
IEnvironmentInfo environmentInfo)
|
||||
: base(serverConfig,
|
||||
: base(httpClient,
|
||||
serverConfig,
|
||||
userManager,
|
||||
libraryManager,
|
||||
isoManager,
|
||||
|
|
|
@ -26,8 +26,10 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
public abstract class BaseProgressiveStreamingService : BaseStreamingService
|
||||
{
|
||||
protected readonly IEnvironmentInfo EnvironmentInfo;
|
||||
protected IHttpClient HttpClient { get; private set; }
|
||||
|
||||
public BaseProgressiveStreamingService(
|
||||
IHttpClient httpClient,
|
||||
IServerConfigurationManager serverConfig,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
|
@ -55,6 +57,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
authorizationContext)
|
||||
{
|
||||
EnvironmentInfo = environmentInfo;
|
||||
HttpClient = httpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
|
@ -69,6 +70,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
public class VideoService : BaseProgressiveStreamingService
|
||||
{
|
||||
public VideoService(
|
||||
IHttpClient httpClient,
|
||||
IServerConfigurationManager serverConfig,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
|
@ -82,7 +84,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext,
|
||||
IEnvironmentInfo environmentInfo)
|
||||
: base(serverConfig,
|
||||
: base(httpClient,
|
||||
serverConfig,
|
||||
userManager,
|
||||
libraryManager,
|
||||
isoManager,
|
||||
|
|
|
@ -77,6 +77,7 @@ namespace MediaBrowser.Api.Playback
|
|||
public class UniversalAudioService : BaseApiService
|
||||
{
|
||||
public UniversalAudioService(
|
||||
IHttpClient httpClient,
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
|
@ -95,6 +96,7 @@ namespace MediaBrowser.Api.Playback
|
|||
IEnvironmentInfo environmentInfo,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
ServerConfigurationManager = serverConfigurationManager;
|
||||
UserManager = userManager;
|
||||
LibraryManager = libraryManager;
|
||||
|
@ -115,6 +117,7 @@ namespace MediaBrowser.Api.Playback
|
|||
_logger = loggerFactory.CreateLogger(nameof(UniversalAudioService));
|
||||
}
|
||||
|
||||
protected IHttpClient HttpClient { get; private set; }
|
||||
protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
|
||||
protected IUserManager UserManager { get; private set; }
|
||||
protected ILibraryManager LibraryManager { get; private set; }
|
||||
|
@ -323,7 +326,8 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
else
|
||||
{
|
||||
var service = new AudioService(ServerConfigurationManager,
|
||||
var service = new AudioService(HttpClient,
|
||||
ServerConfigurationManager,
|
||||
UserManager,
|
||||
LibraryManager,
|
||||
IsoManager,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue