merging with master to clear merge conflict

This commit is contained in:
Phallacy 2019-02-20 00:46:13 -08:00
commit a0d31a49a0
143 changed files with 3162 additions and 3215 deletions

8
.copr/Makefile Normal file
View 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)"

View file

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

View file

@ -1,12 +1,111 @@
---
kind: pipeline kind: pipeline
name: build name: build-debug
steps: steps:
- name: submodules - name: submodules
image: docker:git image: docker:git
commands: commands:
- git submodule update --init --recursive - git submodule update --init --recursive
- name: build - name: build
image: microsoft/dotnet:2-sdk image: microsoft/dotnet:2-sdk
commands: 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

View file

@ -15,6 +15,10 @@ insert_final_newline = true
end_of_line = lf end_of_line = lf
max_line_length = null max_line_length = null
# YAML indentation
[*.{yml,yaml}]
indent_size = 2
# XML indentation # XML indentation
[*.{csproj,xml}] [*.{csproj,xml}]
indent_size = 2 indent_size = 2
@ -55,15 +59,77 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
############################### ###############################
# Naming Conventions # # Naming Conventions #
############################### ###############################
# Style Definitions # Style Definitions (From Roslyn)
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields # Non-private static fields are PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = * dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const 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 # # C# Coding Conventions #
############################### ###############################

4
.gitignore vendored
View file

@ -263,4 +263,6 @@ deployment/**/pkg-dist/
deployment/**/pkg-dist-tmp/ deployment/**/pkg-dist-tmp/
deployment/collect-dist/ deployment/collect-dist/
jellyfin_version.ini jellyfin_version.ini
ci/

View file

@ -165,7 +165,7 @@ namespace BDInfo
foreach (var file in files) foreach (var file in files)
{ {
PlaylistFiles.Add( 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) foreach (var file in files)
{ {
StreamClipFiles.Add( StreamClipFiles.Add(
file.Name.ToUpper(), new TSStreamClipFile(file, _fileSystem)); file.Name.ToUpper(), new TSStreamClipFile(file));
} }
} }

View file

@ -28,7 +28,6 @@ namespace BDInfo
{ {
public class TSPlaylistFile public class TSPlaylistFile
{ {
private readonly IFileSystem _fileSystem;
private FileSystemMetadata FileInfo = null; private FileSystemMetadata FileInfo = null;
public string FileType = null; public string FileType = null;
public bool IsInitialized = false; public bool IsInitialized = false;
@ -64,21 +63,19 @@ namespace BDInfo
new List<TSGraphicsStream>(); new List<TSGraphicsStream>();
public TSPlaylistFile(BDROM bdrom, public TSPlaylistFile(BDROM bdrom,
FileSystemMetadata fileInfo, IFileSystem fileSystem) FileSystemMetadata fileInfo)
{ {
BDROM = bdrom; BDROM = bdrom;
FileInfo = fileInfo; FileInfo = fileInfo;
_fileSystem = fileSystem;
Name = fileInfo.Name.ToUpper(); Name = fileInfo.Name.ToUpper();
} }
public TSPlaylistFile(BDROM bdrom, public TSPlaylistFile(BDROM bdrom,
string name, string name,
List<TSStreamClip> clips, IFileSystem fileSystem) List<TSStreamClip> clips)
{ {
BDROM = bdrom; BDROM = bdrom;
Name = name; Name = name;
_fileSystem = fileSystem;
IsCustom = true; IsCustom = true;
foreach (var clip in clips) foreach (var clip in clips)
{ {

View file

@ -28,7 +28,6 @@ namespace BDInfo
{ {
public class TSStreamClipFile public class TSStreamClipFile
{ {
private readonly IFileSystem _fileSystem;
public FileSystemMetadata FileInfo = null; public FileSystemMetadata FileInfo = null;
public string FileType = null; public string FileType = null;
public bool IsValid = false; public bool IsValid = false;
@ -37,10 +36,9 @@ namespace BDInfo
public Dictionary<ushort, TSStream> Streams = public Dictionary<ushort, TSStream> Streams =
new Dictionary<ushort, TSStream>(); new Dictionary<ushort, TSStream>();
public TSStreamClipFile(FileSystemMetadata fileInfo, IFileSystem fileSystem) public TSStreamClipFile(FileSystemMetadata fileInfo)
{ {
FileInfo = fileInfo; FileInfo = fileInfo;
_fileSystem = fileSystem;
Name = fileInfo.Name.ToUpper(); Name = fileInfo.Name.ToUpper();
} }

View file

@ -18,6 +18,7 @@
- [dkanada](https://github.com/dkanada) - [dkanada](https://github.com/dkanada)
- [LogicalPhallacy](https://github.com/LogicalPhallacy/) - [LogicalPhallacy](https://github.com/LogicalPhallacy/)
- [RazeLighter777](https://github.com/RazeLighter777) - [RazeLighter777](https://github.com/RazeLighter777)
- [WillWill56](https://github.com/WillWill56)
# Emby Contributors # Emby Contributors

View file

@ -3,9 +3,8 @@ ARG DOTNET_VERSION=2
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
WORKDIR /repo WORKDIR /repo
COPY . . COPY . .
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
&& dotnet clean \ RUN dotnet publish \
&& dotnet publish \
--configuration release \ --configuration release \
--output /jellyfin \ --output /jellyfin \
Jellyfin.Server Jellyfin.Server
@ -18,9 +17,11 @@ RUN apt-get update \
libfontconfig1 \ libfontconfig1 \
&& apt-get clean autoclean \ && apt-get clean autoclean \
&& apt-get autoremove \ && 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=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
EXPOSE 8096 EXPOSE 8096
VOLUME /config /media VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache

View file

@ -1,24 +1,36 @@
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.0 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 WORKDIR /repo
COPY . . COPY . .
#TODO Remove or update the sed line when we update dotnet version. ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \ # TODO Remove or update the sed line when we update dotnet version.
&& find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; \ RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
&& dotnet clean -maxcpucount:1 \ # Discard objs - may cause failures if exists
&& dotnet publish \ RUN find . -type d -name obj | xargs -r rm -r
-maxcpucount:1 \ # Build
RUN dotnet publish \
-r linux-arm \
--configuration release \ --configuration release \
--output /jellyfin \ --output /jellyfin \
Jellyfin.Server Jellyfin.Server
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7 FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7
COPY --from=qemu_extract qemu-arm-static /usr/bin
RUN apt-get update \ 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 COPY --from=builder /jellyfin /jellyfin
EXPOSE 8096 EXPOSE 8096
VOLUME /config /media VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache

View file

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

View file

@ -38,7 +38,9 @@ namespace Emby.Dlna
IFileSystem fileSystem, IFileSystem fileSystem,
IApplicationPaths appPaths, IApplicationPaths appPaths,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo) IJsonSerializer jsonSerializer,
IServerApplicationHost appHost,
IAssemblyInfo assemblyInfo)
{ {
_xmlSerializer = xmlSerializer; _xmlSerializer = xmlSerializer;
_fileSystem = fileSystem; _fileSystem = fileSystem;

View file

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

View file

@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using Emby.Dlna.Server; using Emby.Dlna.Server;
@ -733,26 +734,21 @@ namespace Emby.Dlna.PlayTo
return (true, null); return (true, null);
} }
XElement uPnpResponse; XElement uPnpResponse = null;
// Handle different variations sent back by devices
try try
{ {
uPnpResponse = XElement.Parse(trackString); uPnpResponse = ParseResponse(trackString);
} }
catch (Exception) catch (Exception ex)
{ {
// first try to add a root node with a dlna namesapce _logger.LogError(ex, "Uncaught exception while parsing xml");
try }
{
uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>"); if (uPnpResponse == null)
uPnpResponse = uPnpResponse.Descendants().First(); {
} _logger.LogError("Failed to parse xml: \n {Xml}", trackString);
catch (Exception ex) return (true, null);
{
_logger.LogError(ex, "Unable to parse xml {0}", trackString);
return (true, null);
}
} }
var e = uPnpResponse.Element(uPnpNamespaces.items); var e = uPnpResponse.Element(uPnpNamespaces.items);
@ -762,6 +758,43 @@ namespace Emby.Dlna.PlayTo
return (true, uTrack); return (true, uTrack);
} }
private XElement ParseResponse(string xml)
{
// Handle different variations sent back by devices
try
{
return XElement.Parse(xml);
}
catch (XmlException)
{
}
// first try to add a root node with a dlna namesapce
try
{
return XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + xml + "</data>")
.Descendants()
.First();
}
catch (XmlException)
{
}
// some devices send back invalid xml
try
{
return XElement.Parse(xml.Replace("&", "&amp;"));
}
catch (XmlException)
{
}
return null;
}
private static uBaseObject CreateUBaseObject(XElement container, string trackUri) private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
{ {
if (container == null) if (container == null)

View file

@ -89,11 +89,6 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
{
return;
}
var cancellationToken = _disposeCancellationTokenSource.Token; var cancellationToken = _disposeCancellationTokenSource.Token;
await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false); await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false);
@ -105,6 +100,11 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
{
return;
}
await AddDevice(info, location, cancellationToken).ConfigureAwait(false); await AddDevice(info, location, cancellationToken).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)

View file

@ -19,7 +19,6 @@ namespace IsoMounter
private readonly IEnvironmentInfo EnvironmentInfo; private readonly IEnvironmentInfo EnvironmentInfo;
private readonly bool ExecutablesAvailable; private readonly bool ExecutablesAvailable;
private readonly IFileSystem FileSystem;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly string MountCommand; private readonly string MountCommand;
private readonly string MountPointRoot; private readonly string MountPointRoot;
@ -31,11 +30,10 @@ namespace IsoMounter
#region Constructor(s) #region Constructor(s)
public LinuxIsoManager(ILogger logger, IFileSystem fileSystem, IEnvironmentInfo environment, IProcessFactory processFactory) public LinuxIsoManager(ILogger logger, IEnvironmentInfo environment, IProcessFactory processFactory)
{ {
EnvironmentInfo = environment; EnvironmentInfo = environment;
FileSystem = fileSystem;
_logger = logger; _logger = logger;
ProcessFactory = processFactory; ProcessFactory = processFactory;

View file

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

View file

@ -5,21 +5,17 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Activity; using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Notifications; using MediaBrowser.Model.Notifications;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Notifications namespace Emby.Notifications
@ -29,43 +25,40 @@ namespace Emby.Notifications
/// </summary> /// </summary>
public class Notifications : IServerEntryPoint public class Notifications : IServerEntryPoint
{ {
private readonly IInstallationManager _installationManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ITaskManager _taskManager;
private readonly INotificationManager _notificationManager; private readonly INotificationManager _notificationManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ISessionManager _sessionManager;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private Timer LibraryUpdateTimer { get; set; } private Timer LibraryUpdateTimer { get; set; }
private readonly object _libraryChangedSyncLock = new object(); private readonly object _libraryChangedSyncLock = new object();
private readonly IConfigurationManager _config; private readonly IConfigurationManager _config;
private readonly IDeviceManager _deviceManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IActivityManager _activityManager; private readonly IActivityManager _activityManager;
private string[] _coreNotificationTypes; 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; _logger = logger;
_taskManager = taskManager;
_notificationManager = notificationManager; _notificationManager = notificationManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_sessionManager = sessionManager;
_appHost = appHost; _appHost = appHost;
_config = config; _config = config;
_deviceManager = deviceManager;
_localization = localization; _localization = localization;
_activityManager = activityManager; _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() public Task RunAsync()
@ -124,10 +117,9 @@ namespace Emby.Notifications
return _config.GetConfiguration<NotificationOptions>("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)
if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate)
{ {
return; return;
} }
@ -145,7 +137,7 @@ namespace Emby.Notifications
} }
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>(); 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)) if (!FilterItem(e.Item))
{ {

View file

@ -9,7 +9,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TagLib; using TagLib;
using TagLib.IFD; using TagLib.IFD;
@ -21,13 +20,11 @@ namespace Emby.Photos
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private IImageProcessor _imageProcessor; private IImageProcessor _imageProcessor;
public PhotoProvider(ILogger logger, IFileSystem fileSystem, IImageProcessor imageProcessor) public PhotoProvider(ILogger logger, IImageProcessor imageProcessor)
{ {
_logger = logger; _logger = logger;
_fileSystem = fileSystem;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
} }

View file

@ -1,3 +1,4 @@
using System;
using System.IO; using System.IO;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -14,50 +15,44 @@ namespace Emby.Server.Implementations.AppBase
/// </summary> /// </summary>
protected BaseApplicationPaths( protected BaseApplicationPaths(
string programDataPath, string programDataPath,
string appFolderPath, string logDirectoryPath,
string logDirectoryPath = null, string configurationDirectoryPath,
string configurationDirectoryPath = null, string cacheDirectoryPath)
string cacheDirectoryPath = null)
{ {
ProgramDataPath = programDataPath; ProgramDataPath = programDataPath;
ProgramSystemPath = appFolderPath;
LogDirectoryPath = logDirectoryPath; LogDirectoryPath = logDirectoryPath;
ConfigurationDirectoryPath = configurationDirectoryPath; ConfigurationDirectoryPath = configurationDirectoryPath;
CachePath = cacheDirectoryPath; 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; } public string ProgramDataPath { get; private set; }
/// <summary> /// <summary>
/// Gets the path to the system folder /// Gets the path to the system folder
/// </summary> /// </summary>
public string ProgramSystemPath { get; private set; } public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
/// <summary>
/// The _data directory
/// </summary>
private string _dataDirectory;
/// <summary> /// <summary>
/// Gets the folder path to the data directory /// Gets the folder path to the data directory
/// </summary> /// </summary>
/// <value>The data directory.</value> /// <value>The data directory.</value>
private string _dataPath;
public string DataPath public string DataPath
{ {
get get => _dataPath;
{ private set => _dataPath = Directory.CreateDirectory(value).FullName;
if (_dataDirectory == null)
{
_dataDirectory = Path.Combine(ProgramDataPath, "data");
Directory.CreateDirectory(_dataDirectory);
}
return _dataDirectory;
}
} }
private const string _virtualDataPath = "%AppDataPath%"; /// <summary>
public string VirtualDataPath => _virtualDataPath; /// Gets the magic strings used for virtual path manipulation.
/// </summary>
public string VirtualDataPath { get; } = "%AppDataPath%";
/// <summary> /// <summary>
/// Gets the image cache path. /// Gets the image cache path.
@ -77,61 +72,17 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The plugin configurations path.</value> /// <value>The plugin configurations path.</value>
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations"); 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> /// <summary>
/// Gets the path to the log directory /// Gets the path to the log directory
/// </summary> /// </summary>
/// <value>The log directory path.</value> /// <value>The log directory path.</value>
public string LogDirectoryPath public string LogDirectoryPath { get; private set; }
{
get
{
if (string.IsNullOrEmpty(_logDirectoryPath))
{
_logDirectoryPath = Path.Combine(ProgramDataPath, "logs");
Directory.CreateDirectory(_logDirectoryPath);
}
return _logDirectoryPath;
}
set => _logDirectoryPath = value;
}
/// <summary>
/// The _config directory
/// </summary>
private string _configurationDirectoryPath;
/// <summary> /// <summary>
/// Gets the path to the application configuration root directory /// Gets the path to the application configuration root directory
/// </summary> /// </summary>
/// <value>The configuration directory path.</value> /// <value>The configuration directory path.</value>
public string ConfigurationDirectoryPath public string ConfigurationDirectoryPath { get; private set; }
{
get
{
if (string.IsNullOrEmpty(_configurationDirectoryPath))
{
_configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
Directory.CreateDirectory(_configurationDirectoryPath);
}
return _configurationDirectoryPath;
}
set => _configurationDirectoryPath = value;
}
/// <summary> /// <summary>
/// Gets the path to the system configuration file /// Gets the path to the system configuration file
@ -139,29 +90,11 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The system configuration file path.</value> /// <value>The system configuration file path.</value>
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml"); public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
/// <summary>
/// The _cache directory
/// </summary>
private string _cachePath;
/// <summary> /// <summary>
/// Gets the folder path to the cache directory /// Gets the folder path to the cache directory
/// </summary> /// </summary>
/// <value>The cache directory.</value> /// <value>The cache directory.</value>
public string CachePath public string CachePath { get; set; }
{
get
{
if (string.IsNullOrEmpty(_cachePath))
{
_cachePath = Path.Combine(ProgramDataPath, "cache");
Directory.CreateDirectory(_cachePath);
}
return _cachePath;
}
set => _cachePath = value;
}
/// <summary> /// <summary>
/// Gets the folder path to the temp directory within the cache folder /// Gets the folder path to the temp directory within the cache folder

View file

@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.AppBase
get get
{ {
// Lazy load // 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; return _configuration;
} }
protected set protected set

View file

@ -1,7 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase namespace Emby.Server.Implementations.AppBase
@ -18,9 +17,8 @@ namespace Emby.Server.Implementations.AppBase
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <param name="xmlSerializer">The XML serializer.</param> /// <param name="xmlSerializer">The XML serializer.</param>
/// <param name="fileSystem">The file system</param>
/// <returns>System.Object.</returns> /// <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; object configuration;

View file

@ -104,9 +104,10 @@ using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Subtitles; using MediaBrowser.Providers.Subtitles;
using MediaBrowser.WebDashboard.Api; using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers; using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using ServiceStack; using ServiceStack;
using ServiceStack.Text.Jsv;
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
namespace Emby.Server.Implementations 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> /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
public abstract bool CanSelfRestart { get; } 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 public virtual bool CanLaunchWebBrowser
{ {
get get
@ -202,7 +197,7 @@ namespace Emby.Server.Implementations
/// Gets all concrete types. /// Gets all concrete types.
/// </summary> /// </summary>
/// <value>All concrete types.</value> /// <value>All concrete types.</value>
public Tuple<Type, string>[] AllConcreteTypes { get; protected set; } public Type[] AllConcreteTypes { get; protected set; }
/// <summary> /// <summary>
/// The disposable parts /// The disposable parts
@ -219,8 +214,6 @@ namespace Emby.Server.Implementations
protected IEnvironmentInfo EnvironmentInfo { get; set; } protected IEnvironmentInfo EnvironmentInfo { get; set; }
private IBlurayExaminer BlurayExaminer { get; set; }
public PackageVersionClass SystemUpdateLevel public PackageVersionClass SystemUpdateLevel
{ {
get get
@ -232,12 +225,7 @@ namespace Emby.Server.Implementations
} }
} }
public virtual string OperatingSystemDisplayName => EnvironmentInfo.OperatingSystemName; protected IServiceProvider _serviceProvider;
/// <summary>
/// The container
/// </summary>
protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
/// <summary> /// <summary>
/// Gets the server configuration manager. /// Gets the server configuration manager.
@ -309,7 +297,6 @@ namespace Emby.Server.Implementations
/// <value>The user data repository.</value> /// <value>The user data repository.</value>
private IUserDataManager UserDataManager { get; set; } private IUserDataManager UserDataManager { get; set; }
private IUserRepository UserRepository { get; set; } private IUserRepository UserRepository { get; set; }
internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
internal SqliteItemRepository ItemRepository { get; set; } internal SqliteItemRepository ItemRepository { get; set; }
private INotificationManager NotificationManager { get; set; } private INotificationManager NotificationManager { get; set; }
@ -325,6 +312,8 @@ namespace Emby.Server.Implementations
private IMediaSourceManager MediaSourceManager { get; set; } private IMediaSourceManager MediaSourceManager { get; set; }
private IPlaylistManager PlaylistManager { get; set; } private IPlaylistManager PlaylistManager { get; set; }
private readonly IConfiguration _configuration;
/// <summary> /// <summary>
/// Gets or sets the installation manager. /// Gets or sets the installation manager.
/// </summary> /// </summary>
@ -363,8 +352,10 @@ namespace Emby.Server.Implementations
IFileSystem fileSystem, IFileSystem fileSystem,
IEnvironmentInfo environmentInfo, IEnvironmentInfo environmentInfo,
IImageEncoder imageEncoder, IImageEncoder imageEncoder,
INetworkManager networkManager) INetworkManager networkManager,
IConfiguration configuration)
{ {
_configuration = configuration;
// hack alert, until common can target .net core // hack alert, until common can target .net core
BaseExtensions.CryptographyProvider = CryptographyProvider; BaseExtensions.CryptographyProvider = CryptographyProvider;
@ -440,7 +431,7 @@ namespace Emby.Server.Implementations
{ {
if (_deviceId == null) if (_deviceId == null)
{ {
_deviceId = new DeviceId(ApplicationPaths, LoggerFactory, FileSystemManager); _deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
} }
return _deviceId.Value; return _deviceId.Value;
@ -453,138 +444,58 @@ namespace Emby.Server.Implementations
/// <value>The name.</value> /// <value>The name.</value>
public string Name => ApplicationProductName; 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> /// <summary>
/// Creates an instance of type and resolves all constructor dependancies /// Creates an instance of type and resolves all constructor dependencies
/// </summary> /// </summary>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
public object CreateInstance(Type type) public object CreateInstance(Type type)
{ => ActivatorUtilities.CreateInstance(_serviceProvider, type);
return Container.GetInstance(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> /// <summary>
/// Creates the instance safe. /// Creates the instance safe.
/// </summary> /// </summary>
/// <param name="typeInfo">The type information.</param> /// <param name="typeInfo">The type information.</param>
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
protected object CreateInstanceSafe(Tuple<Type, string> typeInfo) protected object CreateInstanceSafe(Type type)
{ {
var type = typeInfo.Item1;
try try
{ {
return Container.GetInstance(type); Logger.LogDebug("Creating instance of {Type}", type);
return ActivatorUtilities.CreateInstance(_serviceProvider, type);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogError(ex, "Error creating {type}", type.FullName); Logger.LogError(ex, "Error creating {Type}", type);
// Don't blow up in release mode
return null; 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> /// <summary>
/// Resolves this instance. /// Resolves this instance.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns>``0.</returns> /// <returns>``0.</returns>
public T Resolve<T>() public T Resolve<T>() => _serviceProvider.GetService<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;
}
}
/// <summary> /// <summary>
/// Gets the export types. /// Gets the export types.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns>IEnumerable{Type}.</returns> /// <returns>IEnumerable{Type}.</returns>
public IEnumerable<Tuple<Type, string>> GetExportTypes<T>() public IEnumerable<Type> GetExportTypes<T>()
{ {
var currentType = typeof(T); var currentType = typeof(T);
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i.Item1)); return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
} }
/// <summary> /// <summary>
@ -596,9 +507,10 @@ namespace Emby.Server.Implementations
public IEnumerable<T> GetExports<T>(bool manageLifetime = true) public IEnumerable<T> GetExports<T>(bool manageLifetime = true)
{ {
var parts = GetExportTypes<T>() var parts = GetExportTypes<T>()
.Select(CreateInstanceSafe) .Select(x => CreateInstanceSafe(x))
.Where(i => i != null) .Where(i => i != null)
.Cast<T>(); .Cast<T>()
.ToList(); // Convert to list so this isn't executed for each iteration
if (manageLifetime) if (manageLifetime)
{ {
@ -611,33 +523,6 @@ namespace Emby.Server.Implementations
return parts; 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> /// <summary>
/// Runs the startup tasks. /// Runs the startup tasks.
/// </summary> /// </summary>
@ -691,7 +576,7 @@ namespace Emby.Server.Implementations
} }
} }
public async Task Init() public async Task Init(IServiceCollection serviceCollection)
{ {
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@ -721,7 +606,7 @@ namespace Emby.Server.Implementations
SetHttpLimit(); SetHttpLimit();
await RegisterResources(); await RegisterResources(serviceCollection);
FindParts(); FindParts();
} }
@ -736,104 +621,103 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// Registers resources that classes will depend on /// Registers resources that classes will depend on
/// </summary> /// </summary>
protected async Task RegisterResources() protected async Task RegisterResources(IServiceCollection serviceCollection)
{ {
RegisterSingleInstance(ConfigurationManager); serviceCollection.AddSingleton(ConfigurationManager);
RegisterSingleInstance<IApplicationHost>(this); serviceCollection.AddSingleton<IApplicationHost>(this);
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths); serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
RegisterSingleInstance(JsonSerializer);
RegisterSingleInstance(LoggerFactory, false); serviceCollection.AddSingleton(JsonSerializer);
RegisterSingleInstance(Logger);
RegisterSingleInstance(EnvironmentInfo); serviceCollection.AddSingleton(LoggerFactory);
serviceCollection.AddLogging();
serviceCollection.AddSingleton(Logger);
RegisterSingleInstance(FileSystemManager); serviceCollection.AddSingleton(EnvironmentInfo);
serviceCollection.AddSingleton(FileSystemManager);
HttpClient = CreateHttpClient(); HttpClient = CreateHttpClient();
RegisterSingleInstance(HttpClient); serviceCollection.AddSingleton(HttpClient);
RegisterSingleInstance(NetworkManager); serviceCollection.AddSingleton(NetworkManager);
IsoManager = new IsoManager(); IsoManager = new IsoManager();
RegisterSingleInstance(IsoManager); serviceCollection.AddSingleton(IsoManager);
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager); TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
RegisterSingleInstance(TaskManager); serviceCollection.AddSingleton(TaskManager);
RegisterSingleInstance(XmlSerializer); serviceCollection.AddSingleton(XmlSerializer);
ProcessFactory = new ProcessFactory(); ProcessFactory = new ProcessFactory();
RegisterSingleInstance(ProcessFactory); serviceCollection.AddSingleton(ProcessFactory);
var streamHelper = CreateStreamHelper(); ApplicationHost.StreamHelper = new StreamHelper();
ApplicationHost.StreamHelper = streamHelper; serviceCollection.AddSingleton(StreamHelper);
RegisterSingleInstance(streamHelper);
RegisterSingleInstance(CryptographyProvider); serviceCollection.AddSingleton(CryptographyProvider);
SocketFactory = new SocketFactory(); SocketFactory = new SocketFactory();
RegisterSingleInstance(SocketFactory); serviceCollection.AddSingleton(SocketFactory);
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime); InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, ZipClient, PackageRuntime);
RegisterSingleInstance(InstallationManager); serviceCollection.AddSingleton(InstallationManager);
ZipClient = new ZipClient(FileSystemManager); ZipClient = new ZipClient();
RegisterSingleInstance(ZipClient); serviceCollection.AddSingleton(ZipClient);
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor()); HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
RegisterSingleInstance(HttpResultFactory); serviceCollection.AddSingleton(HttpResultFactory);
RegisterSingleInstance<IServerApplicationHost>(this); serviceCollection.AddSingleton<IServerApplicationHost>(this);
RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths); serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
RegisterSingleInstance(ServerConfigurationManager); serviceCollection.AddSingleton(ServerConfigurationManager);
IAssemblyInfo assemblyInfo = new AssemblyInfo(); var assemblyInfo = new AssemblyInfo();
RegisterSingleInstance(assemblyInfo); serviceCollection.AddSingleton<IAssemblyInfo>(assemblyInfo);
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory); LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory);
await LocalizationManager.LoadAll(); await LocalizationManager.LoadAll();
RegisterSingleInstance<ILocalizationManager>(LocalizationManager); serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
BlurayExaminer = new BdInfoExaminer(FileSystemManager); serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
RegisterSingleInstance(BlurayExaminer);
RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory()); serviceCollection.AddSingleton<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
RegisterSingleInstance(UserDataManager); serviceCollection.AddSingleton(UserDataManager);
UserRepository = GetUserRepository(); UserRepository = GetUserRepository();
// This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it // 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); var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager);
DisplayPreferencesRepository = displayPreferencesRepo; serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo);
RegisterSingleInstance(DisplayPreferencesRepository);
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo); ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo);
RegisterSingleInstance<IItemRepository>(ItemRepository); serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
AuthenticationRepository = GetAuthenticationRepository(); AuthenticationRepository = GetAuthenticationRepository();
RegisterSingleInstance(AuthenticationRepository); serviceCollection.AddSingleton(AuthenticationRepository);
UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager, CryptographyProvider); UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
RegisterSingleInstance(UserManager); serviceCollection.AddSingleton(UserManager);
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager); 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 // TODO wtaylor: investigate use of second music manager
var musicManager = new MusicManager(LibraryManager); 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); LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
RegisterSingleInstance(LibraryMonitor); serviceCollection.AddSingleton(LibraryMonitor);
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager)); serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
CertificateInfo = GetCertificateInfo(true); CertificateInfo = GetCertificateInfo(true);
Certificate = GetCertificate(CertificateInfo); Certificate = GetCertificate(CertificateInfo);
@ -841,88 +725,88 @@ namespace Emby.Server.Implementations
HttpServer = new HttpListenerHost(this, HttpServer = new HttpListenerHost(this,
LoggerFactory, LoggerFactory,
ServerConfigurationManager, ServerConfigurationManager,
"web/index.html", _configuration,
NetworkManager, NetworkManager,
JsonSerializer, JsonSerializer,
XmlSerializer, XmlSerializer);
GetParseFn);
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
RegisterSingleInstance(HttpServer); serviceCollection.AddSingleton(HttpServer);
ImageProcessor = GetImageProcessor(); ImageProcessor = GetImageProcessor();
RegisterSingleInstance(ImageProcessor); serviceCollection.AddSingleton(ImageProcessor);
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
RegisterSingleInstance(TVSeriesManager); serviceCollection.AddSingleton(TVSeriesManager);
var encryptionManager = new EncryptionManager(); var encryptionManager = new EncryptionManager();
RegisterSingleInstance<IEncryptionManager>(encryptionManager); serviceCollection.AddSingleton<IEncryptionManager>(encryptionManager);
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager); DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager);
RegisterSingleInstance(DeviceManager); serviceCollection.AddSingleton(DeviceManager);
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); 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); 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); 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); DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
RegisterSingleInstance(DtoService); serviceCollection.AddSingleton(DtoService);
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); 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); 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); serviceCollection.AddSingleton<IDlnaManager>(
RegisterSingleInstance<IDlnaManager>(dlnaManager); new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo));
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
RegisterSingleInstance(CollectionManager); serviceCollection.AddSingleton(CollectionManager);
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager); 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); 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); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
RegisterSingleInstance(UserViewManager); serviceCollection.AddSingleton(UserViewManager);
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); 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); 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); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
RegisterSingleInstance(EncodingManager); serviceCollection.AddSingleton(EncodingManager);
var activityLogRepo = GetActivityLogRepository(); var activityLogRepo = GetActivityLogRepository();
RegisterSingleInstance(activityLogRepo); serviceCollection.AddSingleton(activityLogRepo);
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
var authContext = new AuthorizationContext(AuthenticationRepository, UserManager); var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
RegisterSingleInstance<IAuthorizationContext>(authContext); serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager)); serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager); 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); 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(); displayPreferencesRepo.Initialize();
@ -935,6 +819,8 @@ namespace Emby.Server.Implementations
((UserDataManager)UserDataManager).Repository = userDataRepo; ((UserDataManager)UserDataManager).Repository = userDataRepo;
ItemRepository.Initialize(userDataRepo, UserManager); ItemRepository.Initialize(userDataRepo, UserManager);
((LibraryManager)LibraryManager).ItemRepository = ItemRepository; ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
_serviceProvider = serviceCollection.BuildServiceProvider();
} }
protected virtual IBrotliCompressor CreateBrotliCompressor() protected virtual IBrotliCompressor CreateBrotliCompressor()
@ -942,11 +828,6 @@ namespace Emby.Server.Implementations
return null; return null;
} }
private static Func<string, object> GetParseFn(Type propertyType)
{
return s => JsvReader.GetParseFn(propertyType)(s);
}
public virtual string PackageRuntime => "netcore"; public virtual string PackageRuntime => "netcore";
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo) public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo)
@ -1058,7 +939,7 @@ namespace Emby.Server.Implementations
protected virtual FFMpegInfo GetFFMpegInfo() protected virtual FFMpegInfo GetFFMpegInfo()
{ {
return new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, GetFfmpegInstallInfo()) return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo())
.GetFFMpegInfo(StartupOptions); .GetFFMpegInfo(StartupOptions);
} }
@ -1066,7 +947,7 @@ namespace Emby.Server.Implementations
/// Registers the media encoder. /// Registers the media encoder.
/// </summary> /// </summary>
/// <returns>Task.</returns> /// <returns>Task.</returns>
private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) private void RegisterMediaEncoder(IServiceCollection serviceCollection)
{ {
string encoderPath = null; string encoderPath = null;
string probePath = null; string probePath = null;
@ -1098,7 +979,7 @@ namespace Emby.Server.Implementations
5000); 5000);
MediaEncoder = mediaEncoder; MediaEncoder = mediaEncoder;
RegisterSingleInstance(MediaEncoder); serviceCollection.AddSingleton(MediaEncoder);
} }
/// <summary> /// <summary>
@ -1174,7 +1055,10 @@ namespace Emby.Server.Implementations
} }
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>()); 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>()); HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>());
@ -1208,19 +1092,15 @@ namespace Emby.Server.Implementations
IsoManager.AddParts(GetExports<IIsoMounter>()); 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 try
{ {
var assemblyPlugin = plugin as IPluginAssembly; if (plugin is IPluginAssembly assemblyPlugin)
if (assemblyPlugin != null)
{ {
var assembly = plugin.GetType().Assembly; var assembly = plugin.GetType().Assembly;
var assemblyName = assembly.GetName(); var assemblyName = assembly.GetName();
var assemblyFilePath = assembly.Location;
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
@ -1264,78 +1144,15 @@ namespace Emby.Server.Implementations
{ {
Logger.LogInformation("Loading assemblies"); Logger.LogInformation("Loading assemblies");
var assemblyInfos = GetComposablePartAssemblies(); AllConcreteTypes = GetComposablePartAssemblies()
.SelectMany(x => x.ExportedTypes)
foreach (var assemblyInfo in assemblyInfos) .Where(type =>
{
var assembly = assemblyInfo.Item1;
var path = assemblyInfo.Item2;
if (path == null)
{ {
Logger.LogInformation("Loading {assemblyName}", assembly.FullName); return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType;
}
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;
}) })
.ToArray(); .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; } private CertificateInfo CertificateInfo { get; set; }
protected X509Certificate Certificate { get; private set; } protected X509Certificate Certificate { get; private set; }
@ -1546,151 +1363,64 @@ namespace Emby.Server.Implementations
/// Gets the composable part assemblies. /// Gets the composable part assemblies.
/// </summary> /// </summary>
/// <returns>IEnumerable{Assembly}.</returns> /// <returns>IEnumerable{Assembly}.</returns>
protected List<Tuple<Assembly, string>> GetComposablePartAssemblies() protected IEnumerable<Assembly> GetComposablePartAssemblies()
{ {
var list = GetPluginAssemblies(ApplicationPaths.PluginsPath); if (Directory.Exists(ApplicationPaths.PluginsPath))
{
// Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly))
// This will prevent the .dll file from getting locked, and allow us to replace it when needed {
Logger.LogInformation("Loading assembly {Path}", file);
yield return Assembly.LoadFrom(file);
}
}
// Include composable parts in the Api assembly // Include composable parts in the Api assembly
list.Add(GetAssembly(typeof(ApiEntryPoint))); yield return typeof(ApiEntryPoint).Assembly;
// Include composable parts in the Dashboard 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 // Include composable parts in the Model assembly
list.Add(GetAssembly(typeof(SystemInfo))); yield return typeof(SystemInfo).Assembly;
// Include composable parts in the Common 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 // Include composable parts in the Controller assembly
list.Add(GetAssembly(typeof(IServerApplicationHost))); yield return typeof(IServerApplicationHost).Assembly;
// Include composable parts in the Providers 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 // Include composable parts in the Photos assembly
list.Add(GetAssembly(typeof(PhotoProvider))); yield return typeof(PhotoProvider).Assembly;
// Emby.Server implementations // Emby.Server implementations
list.Add(GetAssembly(typeof(InstallationManager))); yield return typeof(InstallationManager).Assembly;
// MediaEncoding // MediaEncoding
list.Add(GetAssembly(typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder))); yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
// Dlna // Dlna
list.Add(GetAssembly(typeof(DlnaEntryPoint))); yield return typeof(DlnaEntryPoint).Assembly;
// Local metadata // Local metadata
list.Add(GetAssembly(typeof(BoxSetXmlSaver))); yield return typeof(BoxSetXmlSaver).Assembly;
// Notifications // Notifications
list.Add(GetAssembly(typeof(NotificationManager))); yield return typeof(NotificationManager).Assembly;
// Xbmc // Xbmc
list.Add(GetAssembly(typeof(ArtistNfoProvider))); yield return typeof(ArtistNfoProvider).Assembly;
list.AddRange(GetAssembliesWithPartsInternal().Select(i => new Tuple<Assembly, string>(i, null))); foreach (var i in GetAssembliesWithPartsInternal())
{
return list.ToList(); yield return i;
}
} }
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal(); 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> /// <summary>
/// Gets the system status. /// Gets the system status.
/// </summary> /// </summary>
@ -1718,9 +1448,8 @@ namespace Emby.Server.Implementations
SupportsHttps = SupportsHttps, SupportsHttps = SupportsHttps,
HttpsPortNumber = HttpsPort, HttpsPortNumber = HttpsPort,
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
OperatingSystemDisplayName = OperatingSystemDisplayName, OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName,
CanSelfRestart = CanSelfRestart, CanSelfRestart = CanSelfRestart,
CanSelfUpdate = CanSelfUpdate,
CanLaunchWebBrowser = CanLaunchWebBrowser, CanLaunchWebBrowser = CanLaunchWebBrowser,
WanAddress = wanAddress, WanAddress = wanAddress,
HasUpdateAvailable = HasUpdateAvailable, HasUpdateAvailable = HasUpdateAvailable,
@ -1788,7 +1517,7 @@ namespace Emby.Server.Implementations
public async Task<string> GetWanApiUrl(CancellationToken cancellationToken) public async Task<string> GetWanApiUrl(CancellationToken cancellationToken)
{ {
var url = "http://ipv4.icanhazip.com"; const string url = "http://ipv4.icanhazip.com";
try try
{ {
using (var response = await HttpClient.Get(new HttpRequestOptions using (var response = await HttpClient.Get(new HttpRequestOptions
@ -2019,21 +1748,6 @@ namespace Emby.Server.Implementations
Plugins = list.ToArray(); 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> /// <summary>
/// This returns localhost in the case of no external dns, and the hostname if the /// This returns localhost in the case of no external dns, and the hostname if the
/// dns is prefixed with a valid Uri prefix. /// dns is prefixed with a valid Uri prefix.

View file

@ -14,11 +14,9 @@ namespace Emby.Server.Implementations.Archiving
/// </summary> /// </summary>
public class ZipClient : IZipClient public class ZipClient : IZipClient
{ {
private readonly IFileSystem _fileSystem; public ZipClient()
public ZipClient(IFileSystem fileSystem)
{ {
_fileSystem = fileSystem;
} }
/// <summary> /// <summary>

View file

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

View file

@ -10,14 +10,18 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Collections namespace Emby.Server.Implementations.Collections
{ {
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet> 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)
{ {
} }

View file

@ -342,14 +342,12 @@ namespace Emby.Server.Implementations.Collections
{ {
private readonly CollectionManager _collectionManager; private readonly CollectionManager _collectionManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private ILogger _logger; private ILogger _logger;
public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger) public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger)
{ {
_collectionManager = (CollectionManager)collectionManager; _collectionManager = (CollectionManager)collectionManager;
_config = config; _config = config;
_fileSystem = fileSystem;
_logger = logger; _logger = logger;
} }

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

View file

@ -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()); Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
} }
@ -232,23 +232,6 @@ namespace Emby.Server.Implementations.Data
protected virtual int? CacheSize => null; 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; private bool _disposed;
protected void CheckDisposed() 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) public static IDisposable Read(this ReaderWriterLockSlim obj)
{ {
//if (BaseSqliteRepository.ThreadSafeMode > 0) //if (BaseSqliteRepository.ThreadSafeMode > 0)
@ -390,6 +366,7 @@ namespace Emby.Server.Implementations.Data
//} //}
return new WriteLockToken(obj); return new WriteLockToken(obj);
} }
public static IDisposable Write(this ReaderWriterLockSlim obj) public static IDisposable Write(this ReaderWriterLockSlim obj)
{ {
//if (BaseSqliteRepository.ThreadSafeMode > 0) //if (BaseSqliteRepository.ThreadSafeMode > 0)

View file

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

View file

@ -536,7 +536,7 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(item)); throw new ArgumentNullException(nameof(item));
} }
SaveItems(new List<BaseItem> { item }, cancellationToken); SaveItems(new [] { item }, cancellationToken);
} }
public void SaveImages(BaseItem item) public void SaveImages(BaseItem item)
@ -576,7 +576,7 @@ namespace Emby.Server.Implementations.Data
/// or /// or
/// cancellationToken /// cancellationToken
/// </exception> /// </exception>
public void SaveItems(List<BaseItem> items, CancellationToken cancellationToken) public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
{ {
if (items == null) if (items == null)
{ {
@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.Data
CheckDisposed(); 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) foreach (var item in items)
{ {
var ancestorIds = item.SupportsAncestors ? var ancestorIds = item.SupportsAncestors ?
@ -599,7 +599,7 @@ namespace Emby.Server.Implementations.Data
var userdataKey = item.GetUserDataKeys().FirstOrDefault(); var userdataKey = item.GetUserDataKeys().FirstOrDefault();
var inheritedTags = item.GetInheritedTags(); 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()) 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[] var statements = PrepareAllSafe(db, new string[]
{ {
@ -966,7 +966,7 @@ namespace Emby.Server.Implementations.Data
if (item.ExtraIds.Length > 0) if (item.ExtraIds.Length > 0)
{ {
saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds.ToArray())); saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds));
} }
else else
{ {
@ -1183,9 +1183,9 @@ namespace Emby.Server.Implementations.Data
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
public BaseItem RetrieveItem(Guid id) 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(); CheckDisposed();
@ -2079,14 +2079,14 @@ namespace Emby.Server.Implementations.Data
return false; 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) return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
|| sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.IsPlayed)
|| sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.IsUnplayed)
|| sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.PlayCount)
|| sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.DatePlayed)
|| sortingFields.Contains(ItemSortBy.SeriesDatePlayed, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.SeriesDatePlayed)
|| query.IsFavoriteOrLiked.HasValue || query.IsFavoriteOrLiked.HasValue
|| query.IsFavorite.HasValue || query.IsFavorite.HasValue
|| query.IsResumable.HasValue || query.IsResumable.HasValue
@ -2094,9 +2094,9 @@ namespace Emby.Server.Implementations.Data
|| query.IsLiked.HasValue; || 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)) .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
.ToList(); .ToArray();
private string[] GetColumnNamesFromField(ItemFields field) 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) private bool HasProgramAttributes(InternalItemsQuery query)
{ {
var excludeParentTypes = new string[] if (_programExcludeParentTypes.Contains(query.ParentType))
{
"Series",
"Season",
"MusicAlbum",
"MusicArtist",
"PhotoAlbum"
};
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@ -2172,29 +2180,18 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _programTypes.Contains(x));
{
"Program",
"TvChannel",
"LiveTvProgram",
"LiveTvTvChannel"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"TvChannel",
"LiveTvTvChannel"
};
private bool HasServiceName(InternalItemsQuery query) private bool HasServiceName(InternalItemsQuery query)
{ {
var excludeParentTypes = new string[] if (_programExcludeParentTypes.Contains(query.ParentType))
{
"Series",
"Season",
"MusicAlbum",
"MusicArtist",
"PhotoAlbum"
};
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@ -2204,27 +2201,18 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x));
{
"TvChannel",
"LiveTvTvChannel"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Program",
"LiveTvProgram"
};
private bool HasStartDate(InternalItemsQuery query) private bool HasStartDate(InternalItemsQuery query)
{ {
var excludeParentTypes = new string[] if (_programExcludeParentTypes.Contains(query.ParentType))
{
"Series",
"Season",
"MusicAlbum",
"MusicArtist",
"PhotoAlbum"
};
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@ -2234,13 +2222,7 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x));
{
"Program",
"LiveTvProgram"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private bool HasEpisodeAttributes(InternalItemsQuery query) private bool HasEpisodeAttributes(InternalItemsQuery query)
@ -2263,16 +2245,26 @@ namespace Emby.Server.Implementations.Data
return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase); 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) private bool HasArtistFields(InternalItemsQuery query)
{ {
var excludeParentTypes = new string[] if (_artistExcludeParentTypes.Contains(query.ParentType))
{
"Series",
"Season",
"PhotoAlbum"
};
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@ -2282,18 +2274,18 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x));
{
"Audio",
"MusicAlbum",
"MusicVideo",
"AudioBook",
"AudioPodcast"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Audio",
"MusicAlbum",
"MusicVideo",
"AudioBook",
"AudioPodcast"
};
private bool HasSeriesFields(InternalItemsQuery query) private bool HasSeriesFields(InternalItemsQuery query)
{ {
if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase)) if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase))
@ -2306,26 +2298,18 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x));
{
"Book",
"AudioBook",
"Episode",
"Season"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns) private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable<string> startColumns)
{ {
var list = startColumns.ToList(); var list = startColumns.ToList();
foreach (var field in allFields) foreach (var field in _allFields)
{ {
if (!HasField(query, field)) if (!HasField(query, field))
{ {
foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList()) foreach (var fieldToRemove in GetColumnNamesFromField(field))
{ {
list.Remove(fieldToRemove); list.Remove(fieldToRemove);
} }
@ -2419,11 +2403,14 @@ namespace Emby.Server.Implementations.Data
list.Add(builder.ToString()); list.Add(builder.ToString());
var excludeIds = query.ExcludeItemIds.ToList(); var oldLen = query.ExcludeItemIds.Length;
excludeIds.Add(item.Id); var newLen = oldLen + item.ExtraIds.Length + 1;
excludeIds.AddRange(item.ExtraIds); 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; query.ExcludeProviderIds = item.ProviderIds;
} }
@ -2444,7 +2431,7 @@ namespace Emby.Server.Implementations.Data
list.Add(builder.ToString()); list.Add(builder.ToString());
} }
return list.ToArray(); return list;
} }
private void BindSearchParams(InternalItemsQuery query, IStatement statement) private void BindSearchParams(InternalItemsQuery query, IStatement statement)
@ -2723,18 +2710,17 @@ namespace Emby.Server.Implementations.Data
private void AddItem(List<BaseItem> items, BaseItem newItem) private void AddItem(List<BaseItem> items, BaseItem newItem)
{ {
var providerIds = newItem.ProviderIds.ToList();
for (var i = 0; i < items.Count; i++) for (var i = 0; i < items.Count; i++)
{ {
var item = items[i]; var item = items[i];
foreach (var providerId in providerIds) foreach (var providerId in newItem.ProviderIds)
{ {
if (providerId.Key == MetadataProviders.TmdbCollection.ToString()) if (providerId.Key == MetadataProviders.TmdbCollection.ToString())
{ {
continue; continue;
} }
if (item.GetProviderId(providerId.Key) == providerId.Value) if (item.GetProviderId(providerId.Key) == providerId.Value)
{ {
if (newItem.SourceType == SourceType.Library) if (newItem.SourceType == SourceType.Library)
@ -2753,10 +2739,10 @@ namespace Emby.Server.Implementations.Data
{ {
var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds; var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
int slowThreshold = 1000; int slowThreshold = 100;
#if DEBUG #if DEBUG
slowThreshold = 250; slowThreshold = 10;
#endif #endif
if (elapsed >= slowThreshold) if (elapsed >= slowThreshold)
@ -2806,7 +2792,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ? var whereText = whereClauses.Count == 0 ?
string.Empty : string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray()); " where " + string.Join(" AND ", whereClauses);
commandText += whereText commandText += whereText
+ GetGroupBy(query) + GetGroupBy(query)
@ -2930,25 +2916,31 @@ namespace Emby.Server.Implementations.Data
private string GetOrderByText(InternalItemsQuery query) private string GetOrderByText(InternalItemsQuery query)
{ {
var orderBy = query.OrderBy.ToList(); if (string.IsNullOrEmpty(query.SearchTerm))
var enableOrderInversion = false;
if (query.SimilarTo != null && orderBy.Count == 0)
{ {
orderBy.Add(new ValueTuple<string, SortOrder>("SimilarityScore", SortOrder.Descending)); int oldLen = query.OrderBy.Length;
orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
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)) var orderBy = query.OrderBy;
{
orderBy = new List<(string, SortOrder)>();
orderBy.Add(new ValueTuple<string, SortOrder>("SearchScore", SortOrder.Descending));
orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
}
query.OrderBy = orderBy.ToArray(); if (orderBy.Length == 0)
if (orderBy.Count == 0)
{ {
return string.Empty; return string.Empty;
} }
@ -2957,6 +2949,7 @@ namespace Emby.Server.Implementations.Data
{ {
var columnMap = MapOrderByField(i.Item1, query); var columnMap = MapOrderByField(i.Item1, query);
var columnAscending = i.Item2 == SortOrder.Ascending; var columnAscending = i.Item2 == SortOrder.Ascending;
const bool enableOrderInversion = false;
if (columnMap.Item2 && enableOrderInversion) if (columnMap.Item2 && enableOrderInversion)
{ {
columnAscending = !columnAscending; 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)) if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
{ {
@ -3218,7 +3211,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ? var whereText = whereClauses.Count == 0 ?
string.Empty : string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray()); " where " + string.Join(" AND ", whereClauses);
commandText += whereText commandText += whereText
+ GetGroupBy(query) + GetGroupBy(query)
@ -4378,7 +4371,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (query.Years.Length > 1) else if (query.Years.Length > 1)
{ {
var val = string.Join(",", query.Years.ToArray()); var val = string.Join(",", query.Years);
whereClauses.Add("ProductionYear in (" + val + ")"); whereClauses.Add("ProductionYear in (" + val + ")");
} }
@ -4952,7 +4945,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return result; return result;
} }
return new[] { value }.Where(IsValidType); if (IsValidType(value))
{
return new[] { value };
}
return Array.Empty<string>();
} }
public void DeleteItem(Guid id, CancellationToken cancellationToken) 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); 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); 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); 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); 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); 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); 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) if (query == null)
{ {
@ -5335,7 +5333,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
var typeClause = itemValueTypes.Length == 1 ? var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : ("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; 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 + ")"); whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
var typeWhereText = whereClauses.Count == 0 ? itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses);
string.Empty :
" where " + string.Join(" AND ", whereClauses);
itemCountColumnQuery += typeWhereText;
itemCountColumns = new Dictionary<string, string>() itemCountColumns = new Dictionary<string, string>()
{ {
@ -5400,7 +5394,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
IsSeries = query.IsSeries IsSeries = query.IsSeries
}; };
columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList(); columns = GetFinalColumnsToSelect(query, columns);
var commandText = "select " var commandText = "select "
+ string.Join(",", columns) + string.Join(",", columns)
@ -5492,8 +5486,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{ {
return connection.RunInTransaction(db => return connection.RunInTransaction(db =>
{ {
var list = new List<Tuple<BaseItem, ItemCounts>>(); var list = new List<(BaseItem, ItemCounts)>();
var result = new QueryResult<Tuple<BaseItem, ItemCounts>>(); var result = new QueryResult<(BaseItem, ItemCounts)>();
var statements = PrepareAllSafe(db, statementTexts); 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; 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; return item;
} }
} }
} }

View file

@ -11,7 +11,6 @@ namespace Emby.Server.Implementations.Devices
{ {
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly object _syncLock = new object(); private readonly object _syncLock = new object();
@ -86,19 +85,10 @@ namespace Emby.Server.Implementations.Devices
private string _id; private string _id;
public DeviceId( public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
IApplicationPaths appPaths,
ILoggerFactory loggerFactory,
IFileSystem fileSystem)
{ {
if (fileSystem == null)
{
throw new ArgumentNullException(nameof(fileSystem));
}
_appPaths = appPaths; _appPaths = appPaths;
_logger = loggerFactory.CreateLogger("SystemId"); _logger = loggerFactory.CreateLogger("SystemId");
_fileSystem = fileSystem;
} }
public string Value => _id ?? (_id = GetDeviceId()); public string Value => _id ?? (_id = GetDeviceId());

View file

@ -34,8 +34,6 @@ namespace Emby.Server.Implementations.Devices
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryMonitor _libraryMonitor;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly INetworkManager _network;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager; private readonly ILocalizationManager _localizationManager;
@ -55,17 +53,13 @@ namespace Emby.Server.Implementations.Devices
IUserManager userManager, IUserManager userManager,
IFileSystem fileSystem, IFileSystem fileSystem,
ILibraryMonitor libraryMonitor, ILibraryMonitor libraryMonitor,
IServerConfigurationManager config, IServerConfigurationManager config)
ILoggerFactory loggerFactory,
INetworkManager network)
{ {
_json = json; _json = json;
_userManager = userManager; _userManager = userManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_libraryMonitor = libraryMonitor; _libraryMonitor = libraryMonitor;
_config = config; _config = config;
_logger = loggerFactory.CreateLogger(nameof(DeviceManager));
_network = network;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_localizationManager = localizationManager; _localizationManager = localizationManager;
_authRepo = authRepo; _authRepo = authRepo;
@ -414,14 +408,12 @@ namespace Emby.Server.Implementations.Devices
{ {
private readonly DeviceManager _deviceManager; private readonly DeviceManager _deviceManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private ILogger _logger; private ILogger _logger;
public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger) public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger)
{ {
_deviceManager = (DeviceManager)deviceManager; _deviceManager = (DeviceManager)deviceManager;
_config = config; _config = config;
_fileSystem = fileSystem;
_logger = logger; _logger = logger;
} }

View file

@ -36,13 +36,9 @@ namespace Emby.Server.Implementations.Dto
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly Func<IChannelManager> _channelManagerFactory;
private readonly IApplicationHost _appHost; private readonly IApplicationHost _appHost;
private readonly Func<IDeviceManager> _deviceManager;
private readonly Func<IMediaSourceManager> _mediaSourceManager; private readonly Func<IMediaSourceManager> _mediaSourceManager;
private readonly Func<ILiveTvManager> _livetvManager; private readonly Func<ILiveTvManager> _livetvManager;
@ -52,12 +48,8 @@ namespace Emby.Server.Implementations.Dto
IUserDataManager userDataRepository, IUserDataManager userDataRepository,
IItemRepository itemRepo, IItemRepository itemRepo,
IImageProcessor imageProcessor, IImageProcessor imageProcessor,
IServerConfigurationManager config,
IFileSystem fileSystem,
IProviderManager providerManager, IProviderManager providerManager,
Func<IChannelManager> channelManagerFactory,
IApplicationHost appHost, IApplicationHost appHost,
Func<IDeviceManager> deviceManager,
Func<IMediaSourceManager> mediaSourceManager, Func<IMediaSourceManager> mediaSourceManager,
Func<ILiveTvManager> livetvManager) Func<ILiveTvManager> livetvManager)
{ {
@ -66,12 +58,8 @@ namespace Emby.Server.Implementations.Dto
_userDataRepository = userDataRepository; _userDataRepository = userDataRepository;
_itemRepo = itemRepo; _itemRepo = itemRepo;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_config = config;
_fileSystem = fileSystem;
_providerManager = providerManager; _providerManager = providerManager;
_channelManagerFactory = channelManagerFactory;
_appHost = appHost; _appHost = appHost;
_deviceManager = deviceManager;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_livetvManager = livetvManager; _livetvManager = livetvManager;
} }

View file

@ -22,9 +22,11 @@
</ItemGroup> </ItemGroup>
<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="ServiceStack.Text.Core" Version="5.4.0" />
<PackageReference Include="sharpcompress" Version="0.22.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="SQLitePCL.pretty.netstandard" Version="1.0.0" />
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" /> <PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
</ItemGroup> </ItemGroup>

View file

@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints namespace Emby.Server.Implementations.EntryPoints
{ {
class UserDataChangeNotifier : IServerEntryPoint public class UserDataChangeNotifier : IServerEntryPoint
{ {
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly ILogger _logger; private readonly ILogger _logger;

View file

@ -3,27 +3,19 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.FFMpeg namespace Emby.Server.Implementations.FFMpeg
{ {
public class FFMpegLoader public class FFMpegLoader
{ {
private readonly IHttpClient _httpClient;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger;
private readonly IZipClient _zipClient;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly FFMpegInstallInfo _ffmpegInstallInfo; 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; _appPaths = appPaths;
_httpClient = httpClient;
_zipClient = zipClient;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_ffmpegInstallInfo = ffmpegInstallInfo; _ffmpegInstallInfo = ffmpegInstallInfo;
} }
@ -115,8 +107,7 @@ namespace Emby.Server.Implementations.FFMpeg
var encoderFilename = Path.GetFileName(info.EncoderPath); var encoderFilename = Path.GetFileName(info.EncoderPath);
var probeFilename = Path.GetFileName(info.ProbePath); var probeFilename = Path.GetFileName(info.ProbePath);
foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath) foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath))
.ToList())
{ {
var allFiles = _fileSystem.GetFilePaths(directory, true).ToList(); var allFiles = _fileSystem.GetFilePaths(directory, true).ToList();

View file

@ -66,11 +66,6 @@ namespace Emby.Server.Implementations.HttpClientManager
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
ServicePointManager.Expect100Continue = false; ServicePointManager.Expect100Continue = false;
#if NET46
// Trakt requests sometimes fail without this
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
#endif
} }
/// <summary> /// <summary>
@ -106,23 +101,6 @@ namespace Emby.Server.Implementations.HttpClientManager
return client; 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) private WebRequest GetRequest(HttpRequestOptions options, string method)
{ {
string url = options.Url; string url = options.Url;
@ -135,7 +113,7 @@ namespace Emby.Server.Implementations.HttpClientManager
url = url.Replace(userInfo + "@", string.Empty); url = url.Replace(userInfo + "@", string.Empty);
} }
var request = CreateWebRequest(url); var request = WebRequest.Create(url);
if (request is HttpWebRequest httpWebRequest) if (request is HttpWebRequest httpWebRequest)
{ {
@ -627,14 +605,16 @@ namespace Emby.Server.Implementations.HttpClientManager
var exception = new HttpException(webException.Message, webException); var exception = new HttpException(webException.Message, webException);
var response = webException.Response as HttpWebResponse; using (var response = webException.Response as HttpWebResponse)
if (response != null)
{ {
exception.StatusCode = response.StatusCode; if (response != null)
if ((int)response.StatusCode == 429)
{ {
client.LastTimeout = DateTime.UtcNow; exception.StatusCode = response.StatusCode;
if ((int)response.StatusCode == 429)
{
client.LastTimeout = DateTime.UtcNow;
}
} }
} }

View file

@ -19,7 +19,9 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ServiceStack.Text.Jsv;
namespace Emby.Server.Implementations.HttpServer namespace Emby.Server.Implementations.HttpServer
{ {
@ -53,20 +55,20 @@ namespace Emby.Server.Implementations.HttpServer
IServerApplicationHost applicationHost, IServerApplicationHost applicationHost,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IServerConfigurationManager config, IServerConfigurationManager config,
string defaultRedirectPath, IConfiguration configuration,
INetworkManager networkManager, INetworkManager networkManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IXmlSerializer xmlSerializer, IXmlSerializer xmlSerializer)
Func<Type, Func<string, object>> funcParseFn)
{ {
_appHost = applicationHost; _appHost = applicationHost;
_logger = loggerFactory.CreateLogger("HttpServer"); _logger = loggerFactory.CreateLogger("HttpServer");
_config = config; _config = config;
DefaultRedirectPath = defaultRedirectPath; DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
_networkManager = networkManager; _networkManager = networkManager;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer; _xmlSerializer = xmlSerializer;
_funcParseFn = funcParseFn;
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
Instance = this; Instance = this;
ResponseFilters = Array.Empty<Action<IRequest, IResponse, object>>(); ResponseFilters = Array.Empty<Action<IRequest, IResponse, object>>();

View file

@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary> /// </summary>
private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null) 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) if (responseHeaders == null)
{ {
@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer
content = Array.Empty<byte>(); content = Array.Empty<byte>();
} }
result = new StreamWriter(content, contentType, contentLength, _logger); result = new StreamWriter(content, contentType, contentLength);
} }
else else
{ {
@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>(); responseHeaders = new Dictionary<string, string>();
} }
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
{ {
responseHeaders["Expires"] = "-1"; responseHeaders["Expires"] = "-1";
} }
@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer
bytes = Array.Empty<byte>(); bytes = Array.Empty<byte>();
} }
result = new StreamWriter(bytes, contentType, contentLength, _logger); result = new StreamWriter(bytes, contentType, contentLength);
} }
else else
{ {
@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>(); responseHeaders = new Dictionary<string, string>();
} }
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
{ {
responseHeaders["Expires"] = "-1"; 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) 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 "application/xml":
case "text/xml": case "text/xml":
@ -333,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer
if (isHeadRequest) if (isHeadRequest)
{ {
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger); var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength);
AddResponseHeaders(result, responseHeaders); AddResponseHeaders(result, responseHeaders);
return result; return result;
} }
else else
{ {
var result = new StreamWriter(content, contentType, contentLength, _logger); var result = new StreamWriter(content, contentType, contentLength);
AddResponseHeaders(result, responseHeaders); AddResponseHeaders(result, responseHeaders);
return result; return result;
} }
@ -348,13 +349,19 @@ namespace Emby.Server.Implementations.HttpServer
private byte[] Compress(byte[] bytes, string compressionType) private byte[] Compress(byte[] bytes, string compressionType)
{ {
if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase)) if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase))
{
return CompressBrotli(bytes); return CompressBrotli(bytes);
}
if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase)) if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase))
{
return Deflate(bytes); return Deflate(bytes);
}
if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase)) if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase))
{
return GZip(bytes); return GZip(bytes);
}
throw new NotSupportedException(compressionType); 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) private static string SerializeToXmlString(object from)
{ {
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
@ -422,18 +422,20 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary> /// <summary>
/// Pres the process optimized result. /// Pres the process optimized result.
/// </summary> /// </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; 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 (!noCache)
{ {
if (IsNotModified(requestContext, cacheKey)) DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader);
{
AddAgeHeader(responseHeaders, lastDateModified);
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
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); AddResponseHeaders(result, responseHeaders);
@ -441,8 +443,6 @@ namespace Emby.Server.Implementations.HttpServer
} }
} }
AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration);
return null; return null;
} }
@ -487,9 +487,6 @@ namespace Emby.Server.Implementations.HttpServer
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path); options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
} }
var cacheKey = path + options.DateLastModified.Value.Ticks;
options.CacheKey = cacheKey.GetMD5();
options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare)); options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare));
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@ -520,7 +517,6 @@ namespace Emby.Server.Implementations.HttpServer
return GetStaticResult(requestContext, new StaticResultOptions return GetStaticResult(requestContext, new StaticResultOptions
{ {
CacheDuration = cacheDuration, CacheDuration = cacheDuration,
CacheKey = cacheKey,
ContentFactory = factoryFn, ContentFactory = factoryFn,
ContentType = contentType, ContentType = contentType,
DateLastModified = lastDateModified, DateLastModified = lastDateModified,
@ -534,14 +530,10 @@ namespace Emby.Server.Implementations.HttpServer
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var contentType = options.ContentType; var contentType = options.ContentType;
var etag = requestContext.Headers.Get("If-None-Match"); if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since")))
var cacheKey = etag != null ? new Guid(etag.Trim('\"')) : Guid.Empty;
if (!cacheKey.Equals(Guid.Empty))
{ {
var key = cacheKey.ToString("N");
// See if the result is already cached in the browser // 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) if (result != null)
{ {
@ -553,6 +545,8 @@ namespace Emby.Server.Implementations.HttpServer
var isHeadRequest = options.IsHeadRequest || string.Equals(requestContext.Verb, "HEAD", StringComparison.OrdinalIgnoreCase); var isHeadRequest = options.IsHeadRequest || string.Equals(requestContext.Verb, "HEAD", StringComparison.OrdinalIgnoreCase);
var factoryFn = options.ContentFactory; var factoryFn = options.ContentFactory;
var responseHeaders = options.ResponseHeaders; var responseHeaders = options.ResponseHeaders;
AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified);
AddAgeHeader(responseHeaders, options.DateLastModified);
var rangeHeader = requestContext.Headers.Get("Range"); var rangeHeader = requestContext.Headers.Get("Range");
@ -566,21 +560,10 @@ namespace Emby.Server.Implementations.HttpServer
}; };
AddResponseHeaders(hasHeaders, options.ResponseHeaders); 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; return hasHeaders;
} }
var stream = await factoryFn().ConfigureAwait(false); 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; var totalContentLength = options.ContentLength;
if (!totalContentLength.HasValue) if (!totalContentLength.HasValue)
@ -603,7 +586,6 @@ namespace Emby.Server.Implementations.HttpServer
}; };
AddResponseHeaders(hasHeaders, options.ResponseHeaders); AddResponseHeaders(hasHeaders, options.ResponseHeaders);
hasHeaders.Headers["ETag"] = newEtag;
return hasHeaders; return hasHeaders;
} }
else 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, OnComplete = options.OnComplete,
OnError = options.OnError OnError = options.OnError
}; };
AddResponseHeaders(hasHeaders, options.ResponseHeaders); AddResponseHeaders(hasHeaders, options.ResponseHeaders);
hasHeaders.Headers["ETag"] = newEtag;
return hasHeaders; return hasHeaders;
} }
} }
@ -641,37 +622,28 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary> /// <summary>
/// Adds the caching responseHeaders. /// Adds the caching responseHeaders.
/// </summary> /// </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) if (noCache)
{
responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
}
else if (!string.IsNullOrEmpty(cacheKey))
{
responseHeaders["Cache-Control"] = "public";
}
else
{ {
responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate"; responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate";
responseHeaders["pragma"] = "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) 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); 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> /// <summary>
/// Determines whether [is not modified] [the specified if modified since]. /// Determines whether [is not modified] [the specified if modified since].

View file

@ -14,8 +14,6 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary> /// </summary>
public class StreamWriter : IAsyncStreamWriter, IHasHeaders public class StreamWriter : IAsyncStreamWriter, IHasHeaders
{ {
private ILogger Logger { get; set; }
private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary> /// <summary>
@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param> /// <param name="contentType">Type of the content.</param>
/// <param name="logger">The logger.</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)) if (string.IsNullOrEmpty(contentType))
{ {
@ -53,7 +51,6 @@ namespace Emby.Server.Implementations.HttpServer
} }
SourceStream = source; SourceStream = source;
Logger = logger;
Headers["Content-Type"] = contentType; Headers["Content-Type"] = contentType;
@ -69,7 +66,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param> /// <param name="contentType">Type of the content.</param>
/// <param name="logger">The logger.</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)) if (string.IsNullOrEmpty(contentType))
{ {
@ -77,7 +74,6 @@ namespace Emby.Server.Implementations.HttpServer
} }
SourceBytes = source; SourceBytes = source;
Logger = logger;
Headers["Content-Type"] = contentType; Headers["Content-Type"] = contentType;

View file

@ -17,31 +17,23 @@ namespace Emby.Server.Implementations.IO
public class FileRefresher : IDisposable public class FileRefresher : IDisposable
{ {
private ILogger Logger { get; set; } private ILogger Logger { get; set; }
private ITaskManager TaskManager { get; set; }
private ILibraryManager LibraryManager { get; set; } private ILibraryManager LibraryManager { get; set; }
private IServerConfigurationManager ConfigurationManager { get; set; } private IServerConfigurationManager ConfigurationManager { get; set; }
private readonly IFileSystem _fileSystem;
private readonly List<string> _affectedPaths = new List<string>(); private readonly List<string> _affectedPaths = new List<string>();
private Timer _timer; private Timer _timer;
private readonly object _timerLock = new object(); private readonly object _timerLock = new object();
public string Path { get; private set; } public string Path { get; private set; }
public event EventHandler<EventArgs> Completed; 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); logger.LogDebug("New file refresher created for {0}", path);
Path = path; Path = path;
_fileSystem = fileSystem;
ConfigurationManager = configurationManager; ConfigurationManager = configurationManager;
LibraryManager = libraryManager; LibraryManager = libraryManager;
TaskManager = taskManager;
Logger = logger; Logger = logger;
_environmentInfo = environmentInfo;
_libraryManager = libraryManager1;
AddPath(path); AddPath(path);
} }

View file

@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.IO
/// <summary> /// <summary>
/// Any file name ending in any of these will be ignored by the watchers /// Any file name ending in any of these will be ignored by the watchers
/// </summary> /// </summary>
private readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
"small.jpg", "small.jpg",
"albumart.jpg", "albumart.jpg",
@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.IO
"TempSBE" "TempSBE"
}; };
private readonly string[] _alwaysIgnoreSubstrings = new string[] private static readonly string[] _alwaysIgnoreSubstrings = new string[]
{ {
// Synology // Synology
"eaDir", "eaDir",
@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.IO
".actors" ".actors"
}; };
private readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
// thumbs.db // thumbs.db
".db", ".db",
@ -123,12 +123,6 @@ namespace Emby.Server.Implementations.IO
/// <value>The logger.</value> /// <value>The logger.</value>
private ILogger Logger { get; set; } 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 ILibraryManager LibraryManager { get; set; }
private IServerConfigurationManager ConfigurationManager { get; set; } private IServerConfigurationManager ConfigurationManager { get; set; }
@ -140,19 +134,12 @@ namespace Emby.Server.Implementations.IO
/// </summary> /// </summary>
public LibraryMonitor( public LibraryMonitor(
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
ITaskManager taskManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IServerConfigurationManager configurationManager, IServerConfigurationManager configurationManager,
IFileSystem fileSystem, IFileSystem fileSystem,
IEnvironmentInfo environmentInfo) IEnvironmentInfo environmentInfo)
{ {
if (taskManager == null)
{
throw new ArgumentNullException(nameof(taskManager));
}
LibraryManager = libraryManager; LibraryManager = libraryManager;
TaskManager = taskManager;
Logger = loggerFactory.CreateLogger(GetType().Name); Logger = loggerFactory.CreateLogger(GetType().Name);
ConfigurationManager = configurationManager; ConfigurationManager = configurationManager;
_fileSystem = fileSystem; _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; newRefresher.Completed += NewRefresher_Completed;
_activeRefreshers.Add(newRefresher); _activeRefreshers.Add(newRefresher);
} }

View file

@ -4,8 +4,10 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO namespace Emby.Server.Implementations.IO
@ -20,61 +22,27 @@ namespace Emby.Server.Implementations.IO
private readonly bool _supportsAsyncFileStreams; private readonly bool _supportsAsyncFileStreams;
private char[] _invalidFileNameChars; private char[] _invalidFileNameChars;
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>(); private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
private bool EnableSeparateFileAndDirectoryQueries;
private string _tempPath; private readonly string _tempPath;
private IEnvironmentInfo _environmentInfo; private readonly IEnvironmentInfo _environmentInfo;
private bool _isEnvironmentCaseInsensitive; private readonly bool _isEnvironmentCaseInsensitive;
private string _defaultDirectory;
public ManagedFileSystem( public ManagedFileSystem(
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IEnvironmentInfo environmentInfo, IEnvironmentInfo environmentInfo,
string defaultDirectory, IApplicationPaths applicationPaths)
string tempPath,
bool enableSeparateFileAndDirectoryQueries)
{ {
Logger = loggerFactory.CreateLogger("FileSystem"); Logger = loggerFactory.CreateLogger("FileSystem");
_supportsAsyncFileStreams = true; _supportsAsyncFileStreams = true;
_tempPath = tempPath; _tempPath = applicationPaths.TempDirectory;
_environmentInfo = environmentInfo; _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); SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
_isEnvironmentCaseInsensitive = 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) public virtual void AddShortcutHandler(IShortcutHandler handler)
{ {
_shortcutHandlers.Add(handler); _shortcutHandlers.Add(handler);
@ -718,7 +686,7 @@ namespace Emby.Server.Implementations.IO
SetAttributes(path, false, false); SetAttributes(path, false, false);
File.Delete(path); File.Delete(path);
} }
public virtual List<FileSystemMetadata> GetDrives() 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 // 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 directoryInfo = new DirectoryInfo(path);
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
if (EnableSeparateFileAndDirectoryQueries) return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
{ .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
.Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
}
return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", searchOption));
} }
private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos) private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
{ {
return infos.Select(GetFileSystemMetadata); return infos.Select(GetFileSystemMetadata);
} }
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false) public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -7,7 +6,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
{ {
@ -16,16 +14,14 @@ namespace Emby.Server.Implementations.Library
/// </summary> /// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{ {
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private bool _ignoreDotPrefix; private bool _ignoreDotPrefix;
/// <summary> /// <summary>
/// Any folder named in this list will be ignored - can be added to at runtime for extensibility /// Any folder named in this list will be ignored - can be added to at runtime for extensibility
/// </summary> /// </summary>
public static readonly Dictionary<string, string> IgnoreFolders = new List<string> public static readonly string[] IgnoreFolders =
{ {
"metadata", "metadata",
"ps3_update", "ps3_update",
@ -50,13 +46,11 @@ namespace Emby.Server.Implementations.Library
// macos // macos
".AppleDouble" ".AppleDouble"
}.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); };
public CoreResolutionIgnoreRule(IFileSystem fileSystem, ILibraryManager libraryManager, ILogger logger) public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
{ {
_fileSystem = fileSystem;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = logger;
_ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT; _ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT;
} }
@ -117,7 +111,7 @@ namespace Emby.Server.Implementations.Library
if (fileInfo.IsDirectory) if (fileInfo.IsDirectory)
{ {
// Ignore any folders in our list // Ignore any folders in our list
if (IgnoreFolders.ContainsKey(filename)) if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
{ {
return true; return true;
} }

View file

@ -986,7 +986,7 @@ namespace Emby.Server.Implementations.Library
// Ensure the location is available. // Ensure the location is available.
Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath); Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress); return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
} }
/// <summary> /// <summary>
@ -1225,9 +1225,9 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="ArgumentNullException">id</exception> /// <exception cref="ArgumentNullException">id</exception>
public BaseItem GetItemById(Guid id) 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)) if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
@ -1237,8 +1237,6 @@ namespace Emby.Server.Implementations.Library
item = RetrieveItem(id); item = RetrieveItem(id);
//_logger.LogDebug("GetitemById {0}", id);
if (item != null) if (item != null)
{ {
RegisterItem(item); RegisterItem(item);
@ -1333,7 +1331,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetItemIdsList(query); return ItemRepository.GetItemIdsList(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1344,7 +1342,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetStudios(query); return ItemRepository.GetStudios(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1355,7 +1353,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetGenres(query); return ItemRepository.GetGenres(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1366,7 +1364,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetMusicGenres(query); return ItemRepository.GetMusicGenres(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1377,7 +1375,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetAllArtists(query); return ItemRepository.GetAllArtists(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
{ {
if (query.User != null) 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) if (query.User != null)
{ {
@ -1808,18 +1806,16 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns> /// <returns>Task.</returns>
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken) 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 items)
foreach (var item in list)
{ {
RegisterItem(item); RegisterItem(item);
} }
if (ItemAdded != null) if (ItemAdded != null)
{ {
foreach (var item in list) foreach (var item in items)
{ {
// With the live tv guide this just creates too much noise // With the live tv guide this just creates too much noise
if (item.SourceType != SourceType.Library) if (item.SourceType != SourceType.Library)
@ -1853,7 +1849,7 @@ namespace Emby.Server.Implementations.Library
/// <summary> /// <summary>
/// Updates the item. /// Updates the item.
/// </summary> /// </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) foreach (var item in items)
{ {
@ -1908,7 +1904,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns> /// <returns>Task.</returns>
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) 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> /// <summary>
@ -2005,9 +2001,7 @@ namespace Emby.Server.Implementations.Library
.FirstOrDefault(); .FirstOrDefault();
} }
var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
return options;
} }
public string GetContentType(BaseItem item) public string GetContentType(BaseItem item)
@ -2017,11 +2011,13 @@ namespace Emby.Server.Implementations.Library
{ {
return configuredContentType; return configuredContentType;
} }
configuredContentType = GetConfiguredContentType(item, true); configuredContentType = GetConfiguredContentType(item, true);
if (!string.IsNullOrEmpty(configuredContentType)) if (!string.IsNullOrEmpty(configuredContentType))
{ {
return configuredContentType; return configuredContentType;
} }
return GetInheritedContentType(item); return GetInheritedContentType(item);
} }
@ -2056,6 +2052,7 @@ namespace Emby.Server.Implementations.Library
{ {
return collectionFolder.CollectionType; return collectionFolder.CollectionType;
} }
return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath); return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath);
} }
@ -2066,6 +2063,7 @@ namespace Emby.Server.Implementations.Library
{ {
return nameValuePair.Value; return nameValuePair.Value;
} }
return null; return null;
} }
@ -2108,9 +2106,9 @@ namespace Emby.Server.Implementations.Library
string viewType, string viewType,
string sortName) string sortName)
{ {
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views"); var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath,
"views",
path = Path.Combine(path, _fileSystem.GetValidFilename(viewType)); _fileSystem.GetValidFilename(viewType));
var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView)); var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
@ -2543,7 +2541,7 @@ namespace Emby.Server.Implementations.Library
var resolvers = new IItemResolver[] var resolvers = new IItemResolver[]
{ {
new GenericVideoResolver<Trailer>(this, _fileSystem) new GenericVideoResolver<Trailer>(this)
}; };
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers) return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)

View file

@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers namespace Emby.Server.Implementations.Library.Resolvers
{ {
@ -18,11 +17,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
where T : Video, new() where T : Video, new()
{ {
protected readonly ILibraryManager LibraryManager; protected readonly ILibraryManager LibraryManager;
protected readonly IFileSystem FileSystem;
protected BaseVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) protected BaseVideoResolver(ILibraryManager libraryManager)
{ {
FileSystem = fileSystem;
LibraryManager = libraryManager; LibraryManager = libraryManager;
} }

View file

@ -548,7 +548,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private IImageProcessor _imageProcessor; private IImageProcessor _imageProcessor;
public MovieResolver(ILibraryManager libraryManager, IFileSystem fileSystem, IImageProcessor imageProcessor) : base(libraryManager, fileSystem) public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
: base(libraryManager)
{ {
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
} }

View file

@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers namespace Emby.Server.Implementations.Library.Resolvers
{ {
@ -15,13 +14,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
{ {
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem;
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager, IFileSystem fileSystem) public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{ {
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_fileSystem = fileSystem;
} }
/// <summary> /// <summary>
@ -113,8 +110,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false; return false;
} }
return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.')); return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal);
} }
} }
} }

View file

@ -9,7 +9,7 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers namespace Emby.Server.Implementations.Library.Resolvers
{ {
class SpecialFolderResolver : FolderResolver<Folder> public class SpecialFolderResolver : FolderResolver<Folder>
{ {
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;

View file

@ -3,7 +3,6 @@ using System.Linq;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers.TV namespace Emby.Server.Implementations.Library.Resolvers.TV
{ {
@ -74,7 +73,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null; return null;
} }
public EpisodeResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem) public EpisodeResolver(ILibraryManager libraryManager)
: base(libraryManager)
{ {
} }
} }

View file

@ -1,13 +1,13 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers namespace Emby.Server.Implementations.Library.Resolvers
{ {
public class GenericVideoResolver<T> : BaseVideoResolver<T> public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new() 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

View file

@ -24,7 +24,6 @@ namespace Emby.Server.Implementations.Library.Validators
/// </summary> /// </summary>
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
/// <summary> /// <summary>
@ -32,11 +31,10 @@ namespace Emby.Server.Implementations.Library.Validators
/// </summary> /// </summary>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</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; _libraryManager = libraryManager;
_logger = logger; _logger = logger;
_config = config;
_fileSystem = fileSystem; _fileSystem = fileSystem;
} }

View file

@ -105,8 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_streamHelper = streamHelper; _streamHelper = streamHelper;
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger); _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
_timerProvider.TimerFired += _timerProvider_TimerFired; _timerProvider.TimerFired += _timerProvider_TimerFired;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; _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)) 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); return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);

View file

@ -7,7 +7,6 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -17,7 +16,6 @@ using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Reflection;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -27,7 +25,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;
private bool _hasExited; private bool _hasExited;
@ -38,19 +35,23 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IJsonSerializer _json; private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(); private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
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; _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_appPaths = appPaths; _appPaths = appPaths;
_json = json; _json = json;
_httpClient = httpClient;
_processFactory = processFactory; _processFactory = processFactory;
_config = config; _config = config;
_assemblyInfo = assemblyInfo;
} }
private static bool CopySubtitles => false; private static bool CopySubtitles => false;

View file

@ -17,15 +17,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
protected readonly ILogger Logger; protected readonly ILogger Logger;
private readonly string _dataPath; private readonly string _dataPath;
protected readonly Func<T, T, bool> EqualityComparer; 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; Logger = logger;
_dataPath = dataPath; _dataPath = dataPath;
EqualityComparer = equalityComparer; EqualityComparer = equalityComparer;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
} }
public IReadOnlyList<T> GetAll() public IReadOnlyList<T> GetAll()

View file

@ -1,6 +1,5 @@
using System; using System;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -8,8 +7,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo> public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo>
{ {
public SeriesTimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath) public SeriesTimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{ {
} }

View file

@ -5,7 +5,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -19,8 +18,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired; public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1) public TimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{ {
_logger = logger1; _logger = logger1;
} }

View file

@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.LiveTv
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) 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); var topFolder = GetInternalLiveTvFolder(cancellationToken);

View file

@ -9,7 +9,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
@ -23,18 +22,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected readonly IServerConfigurationManager Config; protected readonly IServerConfigurationManager Config;
protected readonly ILogger Logger; protected readonly ILogger Logger;
protected IJsonSerializer JsonSerializer; protected IJsonSerializer JsonSerializer;
protected readonly IMediaEncoder MediaEncoder;
protected readonly IFileSystem FileSystem; protected readonly IFileSystem FileSystem;
private readonly ConcurrentDictionary<string, ChannelCache> _channelCache = private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase); 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; Config = config;
Logger = logger; Logger = logger;
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
MediaEncoder = mediaEncoder;
FileSystem = fileSystem; FileSystem = fileSystem;
} }

View file

@ -31,15 +31,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager; 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; _httpClient = httpClient;
_appHost = appHost; _appHost = appHost;
_socketFactory = socketFactory; _socketFactory = socketFactory;
_networkManager = networkManager; _networkManager = networkManager;
_environment = environment;
} }
public string Name => "HD Homerun"; public string Name => "HD Homerun";

View file

@ -26,15 +26,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IEnvironmentInfo _environment;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly IMediaSourceManager _mediaSourceManager; 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; _httpClient = httpClient;
_appHost = appHost; _appHost = appHost;
_environment = environment;
_networkManager = networkManager; _networkManager = networkManager;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
} }
@ -52,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
var channelIdPrefix = GetFullChannelIdPrefix(info); 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(); return result.Cast<ChannelInfo>().ToList();
} }
@ -115,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task Validate(TunerHostInfo info) 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))
{ {
} }

View file

@ -19,14 +19,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class M3uParser public class M3uParser
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
public M3uParser(ILogger logger, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost) public M3uParser(ILogger logger, IHttpClient httpClient, IServerApplicationHost appHost)
{ {
_logger = logger; _logger = logger;
_fileSystem = fileSystem;
_httpClient = httpClient; _httpClient = httpClient;
_appHost = appHost; _appHost = appHost;
} }
@ -157,56 +155,56 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null; var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null;
string numberString = null; string numberString = null;
string attributeValue;
double doubleValue;
// Check for channel number with the format from SatIp if (attributes.TryGetValue("tvg-chno", out attributeValue))
// #EXTINF:0,84. VOX Schweiz
// #EXTINF:0,84.0 - VOX Schweiz
if (!string.IsNullOrWhiteSpace(nameInExtInf))
{ {
var numberIndex = nameInExtInf.IndexOf(' '); if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
if (numberIndex > 0)
{ {
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); numberString = attributeValue;
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
{
numberString = numberPart;
}
} }
} }
if (!string.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
if (!IsValidChannelNumber(numberString)) 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)) if (String.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
if (!IsValidChannelNumber(numberString))
{
if (attributes.TryGetValue("channel-id", out string value))
{ {
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)) if (!IsValidChannelNumber(numberString))
@ -214,7 +212,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
numberString = null; numberString = null;
} }
if (string.IsNullOrWhiteSpace(numberString)) if (!string.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
else
{ {
if (string.IsNullOrWhiteSpace(mediaUrl)) if (string.IsNullOrWhiteSpace(mediaUrl))
{ {

View file

@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
//OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile; //OpenedMediaSource.Path = tempFile;

View file

@ -2,10 +2,10 @@
"Albums": "Album", "Albums": "Album",
"AppDeviceValues": "App: {0}, Enhed: {1}", "AppDeviceValues": "App: {0}, Enhed: {1}",
"Application": "Applikation", "Application": "Applikation",
"Artists": "Kunstner", "Artists": "Kunstnere",
"AuthenticationSucceededWithUserName": "{0} bekræftet med succes", "AuthenticationSucceededWithUserName": "{0} bekræftet med succes",
"Books": "Bøger", "Books": "Bøger",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", "CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}",
"Channels": "Kanaler", "Channels": "Kanaler",
"ChapterNameValue": "Kapitel {0}", "ChapterNameValue": "Kapitel {0}",
"Collections": "Samlinger", "Collections": "Samlinger",
@ -14,41 +14,41 @@
"FailedLoginAttemptWithUserName": "Fejlet loginforsøg fra {0}", "FailedLoginAttemptWithUserName": "Fejlet loginforsøg fra {0}",
"Favorites": "Favoritter", "Favorites": "Favoritter",
"Folders": "Mapper", "Folders": "Mapper",
"Genres": "Genre", "Genres": "Genrer",
"HeaderAlbumArtists": "Albumkunstnere", "HeaderAlbumArtists": "Albumkunstnere",
"HeaderCameraUploads": "Camera Uploads", "HeaderCameraUploads": "Kamera Uploads",
"HeaderContinueWatching": "Fortsæt Afspilning", "HeaderContinueWatching": "Fortsæt Afspilning",
"HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritkunstnere", "HeaderFavoriteArtists": "Favoritkunstnere",
"HeaderFavoriteEpisodes": "Favoritepisoder", "HeaderFavoriteEpisodes": "Favorit-afsnit",
"HeaderFavoriteShows": "Favorit serier", "HeaderFavoriteShows": "Favorit-serier",
"HeaderFavoriteSongs": "Favoritsange", "HeaderFavoriteSongs": "Favorit-sange",
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Live TV",
"HeaderNextUp": "Næste", "HeaderNextUp": "Næste",
"HeaderRecordingGroups": "Optagegrupper", "HeaderRecordingGroups": "Optagelsesgrupper",
"HomeVideos": "Hjemmevideoer", "HomeVideos": "Hjemmevideoer",
"Inherit": "Arv", "Inherit": "Nedarv",
"ItemAddedWithName": "{0} blev tilføjet til biblioteket", "ItemAddedWithName": "{0} blev tilføjet til biblioteket",
"ItemRemovedWithName": "{0} blev fjernet fra biblioteket", "ItemRemovedWithName": "{0} blev fjernet fra biblioteket",
"LabelIpAddressValue": "IP-adresse: {0}", "LabelIpAddressValue": "IP-adresse: {0}",
"LabelRunningTimeValue": "Spilletid: {0}", "LabelRunningTimeValue": "Spilletid: {0}",
"Latest": "Seneste", "Latest": "Seneste",
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret", "MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", "MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Server konfigurationssektion {0} er blevet opdateret", "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret",
"MessageServerConfigurationUpdated": "Serverkonfiguration er blevet opdateret", "MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
"MixedContent": "Blandet indhold", "MixedContent": "Blandet indhold",
"Movies": "Film", "Movies": "Film",
"Music": "Musik", "Music": "Musik",
"MusicVideos": "Musikvideoer", "MusicVideos": "Musikvideoer",
"NameInstallFailed": "{0} installation failed", "NameInstallFailed": "{0} installationen mislykkedes",
"NameSeasonNumber": "Sæson {0}", "NameSeasonNumber": "Sæson {0}",
"NameSeasonUnknown": "Season Unknown", "NameSeasonUnknown": "Ukendt Sæson",
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", "NewVersionIsAvailable": "En ny version af Jellyfin Server er tilgængelig til download.",
"NotificationOptionApplicationUpdateAvailable": "Opdatering til applikation tilgængelig", "NotificationOptionApplicationUpdateAvailable": "Opdatering til applikation tilgængelig",
"NotificationOptionApplicationUpdateInstalled": "Opdatering til applikation installeret", "NotificationOptionApplicationUpdateInstalled": "Opdatering til applikation installeret",
"NotificationOptionAudioPlayback": "Audioafspilning påbegyndt", "NotificationOptionAudioPlayback": "Lydafspilning påbegyndt",
"NotificationOptionAudioPlaybackStopped": "Audioafspilning stoppet", "NotificationOptionAudioPlaybackStopped": "Lydafspilning stoppet",
"NotificationOptionCameraImageUploaded": "Kamerabillede uploadet", "NotificationOptionCameraImageUploaded": "Kamerabillede uploadet",
"NotificationOptionInstallationFailed": "Installationsfejl", "NotificationOptionInstallationFailed": "Installationsfejl",
"NotificationOptionNewLibraryContent": "Nyt indhold tilføjet", "NotificationOptionNewLibraryContent": "Nyt indhold tilføjet",
@ -70,16 +70,16 @@
"ProviderValue": "Udbyder: {0}", "ProviderValue": "Udbyder: {0}",
"ScheduledTaskFailedWithName": "{0} fejlet", "ScheduledTaskFailedWithName": "{0} fejlet",
"ScheduledTaskStartedWithName": "{0} påbegyndt", "ScheduledTaskStartedWithName": "{0} påbegyndt",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "ServerNameNeedsToBeRestarted": "{0} skal genstartes",
"Shows": "Shows", "Shows": "Serier",
"Songs": "Sange", "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}", "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}", "SubtitlesDownloadedForItem": "Undertekster downloadet for {0}",
"Sync": "Synk", "Sync": "Synk",
"System": "System", "System": "System",
"TvShows": "TV Shows", "TvShows": "TV serier",
"User": "Bruger", "User": "Bruger",
"UserCreatedWithName": "Bruger {0} er blevet oprettet", "UserCreatedWithName": "Bruger {0} er blevet oprettet",
"UserDeletedWithName": "Brugeren {0} er blevet slettet", "UserDeletedWithName": "Brugeren {0} er blevet slettet",
@ -88,10 +88,10 @@
"UserOfflineFromDevice": "{0} har afbrudt fra {1}", "UserOfflineFromDevice": "{0} har afbrudt fra {1}",
"UserOnlineFromDevice": "{0} er online fra {1}", "UserOnlineFromDevice": "{0} er online fra {1}",
"UserPasswordChangedWithName": "Adgangskode er ændret for bruger {0}", "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}", "UserStartedPlayingItemWithValues": "{0} har påbegyndt afspilning af {1}",
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1}", "UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
"ValueSpecialEpisodeName": "Special - {0}", "ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}" "VersionNumber": "Version {0}"
} }

View file

@ -3,61 +3,61 @@
"AppDeviceValues": "App: {0}, Gerät: {1}", "AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung", "Application": "Anwendung",
"Artists": "Interpreten", "Artists": "Interpreten",
"AuthenticationSucceededWithUserName": "{0} erfolgreich authentifiziert", "AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
"Books": "Bücher", "Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Bild wurde hochgeladen von {0}", "CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
"Channels": "Kanäle", "Channels": "Kanäle",
"ChapterNameValue": "Kapitel {0}", "ChapterNameValue": "Kapitel {0}",
"Collections": "Sammlungen", "Collections": "Sammlungen",
"DeviceOfflineWithName": "{0} wurde getrennt", "DeviceOfflineWithName": "{0} wurde getrennt",
"DeviceOnlineWithName": "{0} ist verbunden", "DeviceOnlineWithName": "{0} hat sich verbunden",
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}", "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
"Favorites": "Favoriten", "Favorites": "Favoriten",
"Folders": "Verzeichnisse", "Folders": "Verzeichnisse",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album-Künstler", "HeaderAlbumArtists": "Album-Interpreten",
"HeaderCameraUploads": "Kamera Uploads", "HeaderCameraUploads": "Kamera-Uploads",
"HeaderContinueWatching": "Weiterschauen", "HeaderContinueWatching": "Weiterschauen",
"HeaderFavoriteAlbums": "Lieblingsalben", "HeaderFavoriteAlbums": "Lieblingsalben",
"HeaderFavoriteArtists": "Interpreten Favoriten", "HeaderFavoriteArtists": "Lieblings-Interpreten",
"HeaderFavoriteEpisodes": "Lieblingsepisoden", "HeaderFavoriteEpisodes": "Lieblingsepisoden",
"HeaderFavoriteShows": "Lieblingsserien", "HeaderFavoriteShows": "Lieblingsserien",
"HeaderFavoriteSongs": "Lieder Favoriten", "HeaderFavoriteSongs": "Lieblingslieder",
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Live-TV",
"HeaderNextUp": "Als Nächstes", "HeaderNextUp": "Als Nächstes",
"HeaderRecordingGroups": "Aufnahme-Gruppen", "HeaderRecordingGroups": "Aufnahme-Gruppen",
"HomeVideos": "Heimvideos", "HomeVideos": "Heimvideos",
"Inherit": "Übernehmen", "Inherit": "Übernehmen",
"ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt", "ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt",
"ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt", "ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt",
"LabelIpAddressValue": "IP Adresse: {0}", "LabelIpAddressValue": "IP-Adresse: {0}",
"LabelRunningTimeValue": "Laufzeit: {0}", "LabelRunningTimeValue": "Laufzeit: {0}",
"Latest": "Neueste", "Latest": "Neueste",
"MessageApplicationUpdated": "Jellyfin Server wurde auf den neusten Stand gebracht.", "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
"MessageApplicationUpdatedTo": "Jellyfin Server wurde auf Version {0} aktualisiert", "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert", "MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert",
"MessageServerConfigurationUpdated": "Server Einstellungen wurden aktualisiert", "MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
"MixedContent": "Gemischte Inhalte", "MixedContent": "Gemischte Inhalte",
"Movies": "Filme", "Movies": "Filme",
"Music": "Musik", "Music": "Musik",
"MusicVideos": "Musikvideos", "MusicVideos": "Musikvideos",
"NameInstallFailed": "{0} Installation fehlgeschlagen", "NameInstallFailed": "Installation von {0} fehlgeschlagen",
"NameSeasonNumber": "Staffel {0}", "NameSeasonNumber": "Staffel {0}",
"NameSeasonUnknown": "Staffel unbekannt", "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", "NotificationOptionApplicationUpdateAvailable": "Anwendungsaktualisierung verfügbar",
"NotificationOptionApplicationUpdateInstalled": "Anwendungsaktualisierung installiert", "NotificationOptionApplicationUpdateInstalled": "Anwendungsaktualisierung installiert",
"NotificationOptionAudioPlayback": "Audiowiedergabe gestartet", "NotificationOptionAudioPlayback": "Audiowiedergabe gestartet",
"NotificationOptionAudioPlaybackStopped": "Audiowiedergabe gestoppt", "NotificationOptionAudioPlaybackStopped": "Audiowiedergabe gestoppt",
"NotificationOptionCameraImageUploaded": "Kamera Bild hochgeladen", "NotificationOptionCameraImageUploaded": "Foto hochgeladen",
"NotificationOptionInstallationFailed": "Installationsfehler", "NotificationOptionInstallationFailed": "Installationsfehler",
"NotificationOptionNewLibraryContent": "Neuer Inhalt hinzugefügt", "NotificationOptionNewLibraryContent": "Neuer Inhalt hinzugefügt",
"NotificationOptionPluginError": "Plugin Fehler", "NotificationOptionPluginError": "Plugin-Fehler",
"NotificationOptionPluginInstalled": "Plugin installiert", "NotificationOptionPluginInstalled": "Plugin installiert",
"NotificationOptionPluginUninstalled": "Plugin deinstalliert", "NotificationOptionPluginUninstalled": "Plugin deinstalliert",
"NotificationOptionPluginUpdateInstalled": "Pluginaktualisierung installiert", "NotificationOptionPluginUpdateInstalled": "Pluginaktualisierung installiert",
"NotificationOptionServerRestartRequired": "Serverneustart notwendig", "NotificationOptionServerRestartRequired": "Serverneustart notwendig",
"NotificationOptionTaskFailed": "Geplante Aufgaben fehlgeschlagen", "NotificationOptionTaskFailed": "Geplante Aufgabe fehlgeschlagen",
"NotificationOptionUserLockedOut": "Benutzer ausgeschlossen", "NotificationOptionUserLockedOut": "Benutzer ausgeschlossen",
"NotificationOptionVideoPlayback": "Videowiedergabe gestartet", "NotificationOptionVideoPlayback": "Videowiedergabe gestartet",
"NotificationOptionVideoPlaybackStopped": "Videowiedergabe gestoppt", "NotificationOptionVideoPlaybackStopped": "Videowiedergabe gestoppt",
@ -68,18 +68,18 @@
"PluginUninstalledWithName": "{0} wurde deinstalliert", "PluginUninstalledWithName": "{0} wurde deinstalliert",
"PluginUpdatedWithName": "{0} wurde aktualisiert", "PluginUpdatedWithName": "{0} wurde aktualisiert",
"ProviderValue": "Anbieter: {0}", "ProviderValue": "Anbieter: {0}",
"ScheduledTaskFailedWithName": "{0} fehlgeschlagen", "ScheduledTaskFailedWithName": "{0} ist fehlgeschlagen",
"ScheduledTaskStartedWithName": "{0} gestartet", "ScheduledTaskStartedWithName": "{0} wurde gestartet",
"ServerNameNeedsToBeRestarted": "{0} muss neu gestartet werden", "ServerNameNeedsToBeRestarted": "{0} muss neu gestartet werden",
"Shows": "Serien", "Shows": "Serien",
"Songs": "Songs", "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}", "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}", "SubtitlesDownloadedForItem": "Untertitel heruntergeladen für {0}",
"Sync": "Synchronisation", "Sync": "Synchronisation",
"System": "System", "System": "System",
"TvShows": "TV Sendungen", "TvShows": "TV-Sendungen",
"User": "Benutzer", "User": "Benutzer",
"UserCreatedWithName": "Benutzer {0} wurde erstellt", "UserCreatedWithName": "Benutzer {0} wurde erstellt",
"UserDeletedWithName": "Benutzer {0} wurde gelöscht", "UserDeletedWithName": "Benutzer {0} wurde gelöscht",
@ -88,10 +88,10 @@
"UserOfflineFromDevice": "{0} wurde getrennt von {1}", "UserOfflineFromDevice": "{0} wurde getrennt von {1}",
"UserOnlineFromDevice": "{0} ist online von {1}", "UserOnlineFromDevice": "{0} ist online von {1}",
"UserPasswordChangedWithName": "Das Passwort für Benutzer {0} wurde geändert", "UserPasswordChangedWithName": "Das Passwort für Benutzer {0} wurde geändert",
"UserPolicyUpdatedWithName": "Benutzerrichtlinie wurde für {0} aktualisiert", "UserPolicyUpdatedWithName": "Benutzerrichtlinie von {0} wurde aktualisiert",
"UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} gestartet", "UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet",
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} beendet", "UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet",
"ValueHasBeenAddedToLibrary": "{0} wurde ihrer Bibliothek hinzugefügt", "ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
"ValueSpecialEpisodeName": "Special - {0}", "ValueSpecialEpisodeName": "Extra - {0}",
"VersionNumber": "Version {0}" "VersionNumber": "Version {0}"
} }

View file

@ -90,7 +90,7 @@
"UserPasswordChangedWithName": "Password has been changed for user {0}", "UserPasswordChangedWithName": "Password has been changed for user {0}",
"UserPolicyUpdatedWithName": "User policy has been updated for {0}", "UserPolicyUpdatedWithName": "User policy has been updated for {0}",
"UserStartedPlayingItemWithValues": "{0} has started playing {1}", "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", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueSpecialEpisodeName": "Special - {0}", "ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}" "VersionNumber": "Version {0}"

View file

@ -30,7 +30,7 @@
"Inherit": "Inherit", "Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library", "ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library", "ItemRemovedWithName": "{0} was removed from the library",
"LabelIpAddressValue": "Ip address: {0}", "LabelIpAddressValue": "IP address: {0}",
"LabelRunningTimeValue": "Running time: {0}", "LabelRunningTimeValue": "Running time: {0}",
"Latest": "Latest", "Latest": "Latest",
"MessageApplicationUpdated": "Jellyfin Server has been updated", "MessageApplicationUpdated": "Jellyfin Server has been updated",

View file

@ -5,46 +5,46 @@
"Artists": "Artistas", "Artists": "Artistas",
"AuthenticationSucceededWithUserName": "{0} autenticado correctamente", "AuthenticationSucceededWithUserName": "{0} autenticado correctamente",
"Books": "Libros", "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", "Channels": "Canales",
"ChapterNameValue": "Capítulo {0}", "ChapterNameValue": "Capítulo {0}",
"Collections": "Colecciones", "Collections": "Colecciones",
"DeviceOfflineWithName": "{0} se ha desconectado", "DeviceOfflineWithName": "{0} se ha desconectado",
"DeviceOnlineWithName": "{0} está conectado", "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", "Favorites": "Favoritos",
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artistas del Álbum", "HeaderAlbumArtists": "Artistas del álbum",
"HeaderCameraUploads": "Camera Uploads", "HeaderCameraUploads": "Subidas desde cámara",
"HeaderContinueWatching": "Continuar viendo", "HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos",
"HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteShows": "Programas favoritos",
"HeaderFavoriteSongs": "Canciones favoritas", "HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en vivo", "HeaderLiveTV": "TV en directo",
"HeaderNextUp": "Siguiendo", "HeaderNextUp": "Siguiendo",
"HeaderRecordingGroups": "Grupos de grabación", "HeaderRecordingGroups": "Grupos de grabación",
"HomeVideos": "Vídeos de inicio", "HomeVideos": "Vídeos caseros",
"Inherit": "Heredar", "Inherit": "Heredar",
"ItemAddedWithName": "{0} se ha añadido a la biblioteca", "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}", "LabelIpAddressValue": "Dirección IP: {0}",
"LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}",
"Latest": "Últimos", "Latest": "Últimos",
"MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin", "MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin",
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La sección de configuración del servidor {0} ha sido actualizado", "MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada",
"MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
"MixedContent": "Contenido mixto", "MixedContent": "Contenido mixto",
"Movies": "Peliculas", "Movies": "Películas",
"Music": "Música", "Music": "Música",
"MusicVideos": "Videos musicales", "MusicVideos": "Vídeos musicales",
"NameInstallFailed": "{0} installation failed", "NameInstallFailed": "{0} error de instalación",
"NameSeasonNumber": "Temporada {0}", "NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Season Unknown", "NameSeasonUnknown": "Temporada desconocida",
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", "NewVersionIsAvailable": "Disponible una nueva versión de Jellyfin para descargar.",
"NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible",
"NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada",
"NotificationOptionAudioPlayback": "Se inició la reproducción de audio", "NotificationOptionAudioPlayback": "Se inició la reproducción de audio",
@ -56,13 +56,13 @@
"NotificationOptionPluginInstalled": "Plugin instalado", "NotificationOptionPluginInstalled": "Plugin instalado",
"NotificationOptionPluginUninstalled": "Plugin desinstalado", "NotificationOptionPluginUninstalled": "Plugin desinstalado",
"NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada", "NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada",
"NotificationOptionServerRestartRequired": "Requiere reinicio del servidor", "NotificationOptionServerRestartRequired": "Se requiere reinicio del servidor",
"NotificationOptionTaskFailed": "Error de tarea programada", "NotificationOptionTaskFailed": "Error de tarea programada",
"NotificationOptionUserLockedOut": "Usuario bloqueado", "NotificationOptionUserLockedOut": "Usuario bloqueado",
"NotificationOptionVideoPlayback": "Se inició la reproducción de vídeo", "NotificationOptionVideoPlayback": "Se inició la reproducción de vídeo",
"NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo detenida", "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo detenida",
"Photos": "Fotos", "Photos": "Fotos",
"Playlists": "Listas reproducción", "Playlists": "Listas de reproducción",
"Plugin": "Plugin", "Plugin": "Plugin",
"PluginInstalledWithName": "{0} se ha instalado", "PluginInstalledWithName": "{0} se ha instalado",
"PluginUninstalledWithName": "{0} se ha desinstalado", "PluginUninstalledWithName": "{0} se ha desinstalado",
@ -70,16 +70,16 @@
"ProviderValue": "Proveedor: {0}", "ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciada", "ScheduledTaskStartedWithName": "{0} iniciada",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Series", "Shows": "Series",
"Songs": "Canciones", "Songs": "Canciones",
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.", "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
"SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}", "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}", "SubtitlesDownloadedForItem": "Descargar subtítulos para {0}",
"Sync": "Sincronizar", "Sync": "Sincronizar",
"System": "Sistema", "System": "Sistema",
"TvShows": "Series TV", "TvShows": "Series de TV",
"User": "Usuario", "User": "Usuario",
"UserCreatedWithName": "El usuario {0} ha sido creado", "UserCreatedWithName": "El usuario {0} ha sido creado",
"UserDeletedWithName": "El usuario {0} ha sido borrado", "UserDeletedWithName": "El usuario {0} ha sido borrado",
@ -88,10 +88,10 @@
"UserOfflineFromDevice": "{0} se ha desconectado de {1}", "UserOfflineFromDevice": "{0} se ha desconectado de {1}",
"UserOnlineFromDevice": "{0} está en línea desde {1}", "UserOnlineFromDevice": "{0} está en línea desde {1}",
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
"UserPolicyUpdatedWithName": "User policy has been updated for {0}", "UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}",
"UserStartedPlayingItemWithValues": "{0} ha comenzado reproducir {1}", "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
"UserStoppedPlayingItemWithValues": "{0} ha parado de reproducir {1}", "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia",
"ValueSpecialEpisodeName": "Especial - {0}", "ValueSpecialEpisodeName": "Especial - {0}",
"VersionNumber": "Versión {0}" "VersionNumber": "Versión {0}"
} }

View file

@ -36,7 +36,7 @@
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour", "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
"MessageApplicationUpdatedTo": "Jellyfin Serveur a été mis à jour en version {0}", "MessageApplicationUpdatedTo": "Jellyfin Serveur a été mis à jour en version {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour", "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", "MixedContent": "Contenu mixte",
"Movies": "Films", "Movies": "Films",
"Music": "Musique", "Music": "Musique",

View file

@ -5,48 +5,48 @@
"Artists": "Előadók", "Artists": "Előadók",
"AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva", "AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
"Books": "Könyvek", "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", "Channels": "Csatornák",
"ChapterNameValue": "Jelenet {0}", "ChapterNameValue": "Jelenet {0}",
"Collections": "Gyűjtemények", "Collections": "Gyűjtemények",
"DeviceOfflineWithName": "{0} kijelentkezett", "DeviceOfflineWithName": "{0} kijelentkezett",
"DeviceOnlineWithName": "{0} belépett", "DeviceOnlineWithName": "{0} belépett",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet {0}",
"Favorites": "Kedvencek", "Favorites": "Kedvencek",
"Folders": "Könyvtárak", "Folders": "Könyvtárak",
"Genres": "Műfajok", "Genres": "Műfajok",
"HeaderAlbumArtists": "Album Előadók", "HeaderAlbumArtists": "Album Előadók",
"HeaderCameraUploads": "Camera Uploads", "HeaderCameraUploads": "Kamera feltöltések",
"HeaderContinueWatching": "Vetítés(ek) folytatása", "HeaderContinueWatching": "Folyamatban lévő filmek",
"HeaderFavoriteAlbums": "Kedvenc Albumok", "HeaderFavoriteAlbums": "Kedvenc Albumok",
"HeaderFavoriteArtists": "Kedvenc Művészek", "HeaderFavoriteArtists": "Kedvenc Művészek",
"HeaderFavoriteEpisodes": "Kedvenc Epizódok", "HeaderFavoriteEpisodes": "Kedvenc Epizódok",
"HeaderFavoriteShows": "Kedvenc Műsorok", "HeaderFavoriteShows": "Kedvenc Műsorok",
"HeaderFavoriteSongs": "Kedvenc Dalok", "HeaderFavoriteSongs": "Kedvenc Dalok",
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Élő TV",
"HeaderNextUp": "Következik", "HeaderNextUp": "Következik",
"HeaderRecordingGroups": "Recording Groups", "HeaderRecordingGroups": "Felvételi csoportok",
"HomeVideos": "Házi videók", "HomeVideos": "Házi videók",
"Inherit": "Inherit", "Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library", "ItemAddedWithName": "{0} hozzáadva a könyvtárhoz",
"ItemRemovedWithName": "{0} was removed from the library", "ItemRemovedWithName": "{0} eltávolítva a könyvtárból",
"LabelIpAddressValue": "Ip cím: {0}", "LabelIpAddressValue": "IP cím: {0}",
"LabelRunningTimeValue": "Running time: {0}", "LabelRunningTimeValue": "Futási idő: {0}",
"Latest": "Legújabb", "Latest": "Legújabb",
"MessageApplicationUpdated": "Jellyfin Szerver frissítve", "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", "MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész {0} frissítve",
"MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve", "MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve",
"MixedContent": "Vegyes tartalom", "MixedContent": "Vegyes tartalom",
"Movies": "Filmek", "Movies": "Filmek",
"Music": "Zene", "Music": "Zene",
"MusicVideos": "Zenei Videók", "MusicVideos": "Zenei Videók",
"NameInstallFailed": "{0} installation failed", "NameInstallFailed": "{0} sikertelen telepítés",
"NameSeasonNumber": "Season {0}", "NameSeasonNumber": "Évad {0}",
"NameSeasonUnknown": "Season Unknown", "NameSeasonUnknown": "Ismeretlen évad",
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", "NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.",
"NotificationOptionApplicationUpdateAvailable": "Program frissítés elérhető", "NotificationOptionApplicationUpdateAvailable": "Új programfrissítés érhető el",
"NotificationOptionApplicationUpdateInstalled": "Program frissítés telepítve", "NotificationOptionApplicationUpdateInstalled": "Programfrissítés telepítve",
"NotificationOptionAudioPlayback": "Audió lejátszás elkezdve", "NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve", "NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve",
"NotificationOptionCameraImageUploaded": "Kamera kép feltöltve", "NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
@ -57,7 +57,7 @@
"NotificationOptionPluginUninstalled": "Bővítmény eltávolítva", "NotificationOptionPluginUninstalled": "Bővítmény eltávolítva",
"NotificationOptionPluginUpdateInstalled": "Bővítmény frissítés telepítve", "NotificationOptionPluginUpdateInstalled": "Bővítmény frissítés telepítve",
"NotificationOptionServerRestartRequired": "Szerver újraindítás szükséges", "NotificationOptionServerRestartRequired": "Szerver újraindítás szükséges",
"NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionTaskFailed": "Ütemezett feladat hiba",
"NotificationOptionUserLockedOut": "Felhasználó tiltva", "NotificationOptionUserLockedOut": "Felhasználó tiltva",
"NotificationOptionVideoPlayback": "Videó lejátszás elkezdve", "NotificationOptionVideoPlayback": "Videó lejátszás elkezdve",
"NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve", "NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve",
@ -68,30 +68,30 @@
"PluginUninstalledWithName": "{0} eltávolítva", "PluginUninstalledWithName": "{0} eltávolítva",
"PluginUpdatedWithName": "{0} frissítve", "PluginUpdatedWithName": "{0} frissítve",
"ProviderValue": "Provider: {0}", "ProviderValue": "Provider: {0}",
"ScheduledTaskFailedWithName": "{0} failed", "ScheduledTaskFailedWithName": "{0} hiba",
"ScheduledTaskStartedWithName": "{0} started", "ScheduledTaskStartedWithName": "{0} elkezdve",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
"Shows": "Műsorok", "Shows": "Műsorok",
"Songs": "Dalok", "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}", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}",
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", "SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz {0}",
"Sync": "Szinkronizál", "Sync": "Szinkronizál",
"System": "Rendszer", "System": "Rendszer",
"TvShows": "TV Műsorok", "TvShows": "TV Műsorok",
"User": "Felhasználó", "User": "Felhasználó",
"UserCreatedWithName": "User {0} has been created", "UserCreatedWithName": "{0} felhasználó létrehozva",
"UserDeletedWithName": "User {0} has been deleted", "UserDeletedWithName": "{0} felhasználó törölve",
"UserDownloadingItemWithValues": "{0} letölti {1}", "UserDownloadingItemWithValues": "{0} letölti {1}",
"UserLockedOutWithName": "User {0} has been locked out", "UserLockedOutWithName": "{0} felhasználó zárolva van",
"UserOfflineFromDevice": "{0} kijelentkezett innen {1}", "UserOfflineFromDevice": "{0} kijelentkezett innen: {1}",
"UserOnlineFromDevice": "{0} is online from {1}", "UserOnlineFromDevice": "{0} online itt: {1}",
"UserPasswordChangedWithName": "Password has been changed for user {0}", "UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
"UserPolicyUpdatedWithName": "User policy has been updated for {0}", "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett {0}",
"UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt {1}", "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}",
"UserStoppedPlayingItemWithValues": "{0} befejezte a következőt {1}", "UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt: {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
"ValueSpecialEpisodeName": "Special - {0}", "ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Verzió {0}" "VersionNumber": "Verzió: {0}"
} }

View file

@ -5,13 +5,13 @@
"Artists": "Artisti", "Artists": "Artisti",
"AuthenticationSucceededWithUserName": "{0} autenticato con successo", "AuthenticationSucceededWithUserName": "{0} autenticato con successo",
"Books": "Libri", "Books": "Libri",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", "CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera {0}",
"Channels": "Canali", "Channels": "Canali",
"ChapterNameValue": "Capitolo {0}", "ChapterNameValue": "Capitolo {0}",
"Collections": "Collezioni", "Collections": "Collezioni",
"DeviceOfflineWithName": "{0} è stato disconnesso", "DeviceOfflineWithName": "{0} è stato disconnesso",
"DeviceOnlineWithName": "{0} è connesso", "DeviceOnlineWithName": "{0} è connesso",
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}", "FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
"Favorites": "Preferiti", "Favorites": "Preferiti",
"Folders": "Cartelle", "Folders": "Cartelle",
"Genres": "Generi", "Genres": "Generi",
@ -19,9 +19,9 @@
"HeaderCameraUploads": "Caricamenti Fotocamera", "HeaderCameraUploads": "Caricamenti Fotocamera",
"HeaderContinueWatching": "Continua a guardare", "HeaderContinueWatching": "Continua a guardare",
"HeaderFavoriteAlbums": "Album preferiti", "HeaderFavoriteAlbums": "Album preferiti",
"HeaderFavoriteArtists": "Artisti preferiti", "HeaderFavoriteArtists": "Artisti Preferiti",
"HeaderFavoriteEpisodes": "Episodi Preferiti", "HeaderFavoriteEpisodes": "Episodi Preferiti",
"HeaderFavoriteShows": "Show preferiti", "HeaderFavoriteShows": "Serie TV Preferite",
"HeaderFavoriteSongs": "Brani Preferiti", "HeaderFavoriteSongs": "Brani Preferiti",
"HeaderLiveTV": "Diretta TV", "HeaderLiveTV": "Diretta TV",
"HeaderNextUp": "Prossimo", "HeaderNextUp": "Prossimo",

View file

@ -1,97 +1,97 @@
{ {
"Albums": "Альбомдар", "Albums": "Álbomdar",
"AppDeviceValues": "Қолданба: {0}, Құрылғы: {1}", "AppDeviceValues": "Qoldanba: {0}, Qurylǵy: {1}",
"Application": "Қолданба", "Application": "Qoldanba",
"Artists": "Орындаушылар", "Artists": "Oryndaýshylar",
"AuthenticationSucceededWithUserName": "{0} түпнұсқалығын расталуы сәтті", "AuthenticationSucceededWithUserName": "{0} túpnusqalyǵyn rastalýy sátti",
"Books": "Кітаптар", "Books": "Kitaptar",
"CameraImageUploadedFrom": "Жаңа сурет {0} камерасынан жүктеп алынды", "CameraImageUploadedFrom": "Jańa sýret {0} kamerasynan júktep alyndy",
"Channels": "Арналар", "Channels": "Arnalar",
"ChapterNameValue": "{0}-сахна", "ChapterNameValue": "{0}-sahna",
"Collections": "Жиынтықтар", "Collections": "Jıyntyqtar",
"DeviceOfflineWithName": "{0} ажыратылған", "DeviceOfflineWithName": "{0} ajyratylǵan",
"DeviceOnlineWithName": "{0} қосылған", "DeviceOnlineWithName": "{0} qosylǵan",
"FailedLoginAttemptWithUserName": "{0} тарапынан кіру әрекеті сәтсіз", "FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz",
"Favorites": "Таңдаулылар", "Favorites": "Tańdaýlylar",
"Folders": "Қалталар", "Folders": "Qaltalar",
"Genres": "Жанрлар", "Genres": "Janrlar",
"HeaderAlbumArtists": "Альбом орындаушылары", "HeaderAlbumArtists": "Álbom oryndaýshylary",
"HeaderCameraUploads": "Камерадан жүктелгендер", "HeaderCameraUploads": "Kameradan júktelgender",
"HeaderContinueWatching": "Қарауды жалғастыру", "HeaderContinueWatching": "Qaraýdy jalǵastyrý",
"HeaderFavoriteAlbums": "Таңдаулы альбомдар", "HeaderFavoriteAlbums": "Tańdaýly álbomdar",
"HeaderFavoriteArtists": "Таңдаулы орындаушылар", "HeaderFavoriteArtists": "Tańdaýly oryndaýshylar",
"HeaderFavoriteEpisodes": "Таңдаулы бөлімдер", "HeaderFavoriteEpisodes": "Tańdaýly bólimder",
"HeaderFavoriteShows": "Таңдаулы көрсетімдер", "HeaderFavoriteShows": "Tańdaýly kórsetimder",
"HeaderFavoriteSongs": "Таңдаулы әуендер", "HeaderFavoriteSongs": "Tańdaýly áýender",
"HeaderLiveTV": "Эфир", "HeaderLiveTV": "Efır",
"HeaderNextUp": "Кезекті", "HeaderNextUp": "Kezekti",
"HeaderRecordingGroups": "Жазба топтары", "HeaderRecordingGroups": "Jazba toptary",
"HomeVideos": "Үйлік бейнелер", "HomeVideos": "Úılik beıneler",
"Inherit": "Мұраға иелену", "Inherit": "Muraǵa ıelený",
"ItemAddedWithName": "{0} тасығышханаға үстелінді", "ItemAddedWithName": "{0} tasyǵyshhanaǵa ústelindi",
"ItemRemovedWithName": "{0} тасығышханадан аласталды", "ItemRemovedWithName": "{0} tasyǵyshhanadan alastaldy",
"LabelIpAddressValue": "IP-мекенжайы: {0}", "LabelIpAddressValue": "IP-mekenjaıy: {0}",
"LabelRunningTimeValue": "Іске қосылу уақыты: {0}", "LabelRunningTimeValue": "Oınatý ýaqyty: {0}",
"Latest": "Ең кейінгі", "Latest": "Eń keıingi",
"MessageApplicationUpdated": "Jellyfin Server жаңартылды.", "MessageApplicationUpdated": "Jellyfin Serveri jańartyldy",
"MessageApplicationUpdatedTo": "Jellyfin Server {0} үшін жаңартылды", "MessageApplicationUpdatedTo": "Jellyfin Serveri {0} deńgeıge jańartyldy",
"MessageNamedServerConfigurationUpdatedWithValue": "Сервер теңшелімі ({0} бөлімі) жаңартылды", "MessageNamedServerConfigurationUpdatedWithValue": "Server teńsheliminiń {0} bólimi jańartyldy",
"MessageServerConfigurationUpdated": "Сервер теңшелімі жаңартылды", "MessageServerConfigurationUpdated": "Server teńshelimi jańartyldy",
"MixedContent": "Аралас мазмұн", "MixedContent": "Aralas mazmun",
"Movies": "Фильмдер", "Movies": "Fılmder",
"Music": "Музыка", "Music": "Mýzyka",
"MusicVideos": "Музыкалық бейнелер", "MusicVideos": "Mýzykalyq beıneler",
"NameInstallFailed": "{0} орнатылуы сәтсіз", "NameInstallFailed": "{0} ornatylýy sátsiz",
"NameSeasonNumber": "{0}-маусым", "NameSeasonNumber": "{0}-maýsym",
"NameSeasonUnknown": "Белгісіз маусым", "NameSeasonUnknown": "Belgisiz maýsym",
"NewVersionIsAvailable": "Жаңа Jellyfin Server нұсқасы жүктеп алуға қолжетімді.", "NewVersionIsAvailable": "Jańa Jellyfin Server nusqasy júktep alýǵa qoljetimdi.",
"NotificationOptionApplicationUpdateAvailable": "Қолданба жаңартуы қолжетімді", "NotificationOptionApplicationUpdateAvailable": "Qoldanba jańartýy qoljetimdi",
"NotificationOptionApplicationUpdateInstalled": "Қолданба жаңартуы орнатылды", "NotificationOptionApplicationUpdateInstalled": "Qoldanba jańartýy ornatyldy",
"NotificationOptionAudioPlayback": "Дыбыс ойнатуы басталды", "NotificationOptionAudioPlayback": "Dybys oınatýy bastaldy",
"NotificationOptionAudioPlaybackStopped": "Дыбыс ойнатуы тоқтатылды", "NotificationOptionAudioPlaybackStopped": "Dybys oınatýy toqtatyldy",
"NotificationOptionCameraImageUploaded": "Камерадан фотосурет кері қотарылған", "NotificationOptionCameraImageUploaded": "Kameradan fotosýret keri qotarylǵan",
"NotificationOptionInstallationFailed": "Орнату сәтсіздігі", "NotificationOptionInstallationFailed": "Ornatý sátsizdigi",
"NotificationOptionNewLibraryContent": "Жаңа мазмұн үстелген", "NotificationOptionNewLibraryContent": "Jańa mazmun ústelgen",
"NotificationOptionPluginError": "Плагин сәтсіздігі", "NotificationOptionPluginError": "Plagın sátsizdigi",
"NotificationOptionPluginInstalled": "Плагин орнатылды", "NotificationOptionPluginInstalled": "Plagın ornatyldy",
"NotificationOptionPluginUninstalled": "Плагин орнатуы болдырылмады", "NotificationOptionPluginUninstalled": "Plagın ornatýy boldyrylmady",
"NotificationOptionPluginUpdateInstalled": "Плагин жаңартуы орнатылды", "NotificationOptionPluginUpdateInstalled": "Plagın jańartýy ornatyldy",
"NotificationOptionServerRestartRequired": "Серверді қайта іске қосу қажет", "NotificationOptionServerRestartRequired": "Serverdi qaıta iske qosý qajet",
"NotificationOptionTaskFailed": "Жоспарлаған тапсырма сәтсіздігі", "NotificationOptionTaskFailed": "Josparlaǵan tapsyrma sátsizdigi",
"NotificationOptionUserLockedOut": "Пайдаланушы құрсаулы", "NotificationOptionUserLockedOut": "Paıdalanýshy qursaýly",
"NotificationOptionVideoPlayback": "Бейне ойнатуы басталды", "NotificationOptionVideoPlayback": "Beıne oınatýy bastaldy",
"NotificationOptionVideoPlaybackStopped": "Бейне ойнатуы тоқтатылды", "NotificationOptionVideoPlaybackStopped": "Beıne oınatýy toqtatyldy",
"Photos": "Фотосуреттер", "Photos": "Fotosýretter",
"Playlists": "Ойнату тізімдері", "Playlists": "Oınatý tizimderi",
"Plugin": "Плагин", "Plugin": "Plagın",
"PluginInstalledWithName": "{0} орнатылды", "PluginInstalledWithName": "{0} ornatyldy",
"PluginUninstalledWithName": "{0} жойылды", "PluginUninstalledWithName": "{0} joıyldy",
"PluginUpdatedWithName": "{0} жаңартылды", "PluginUpdatedWithName": "{0} jańartyldy",
"ProviderValue": "Жеткізуші: {0}", "ProviderValue": "Jetkizýshi: {0}",
"ScheduledTaskFailedWithName": "{0} сәтсіз", "ScheduledTaskFailedWithName": "{0} sátsiz",
"ScheduledTaskStartedWithName": "{0} іске қосылды", "ScheduledTaskStartedWithName": "{0} iske qosyldy",
"ServerNameNeedsToBeRestarted": "{0} қайта іске қосу қажет", "ServerNameNeedsToBeRestarted": "{0} qaıta iske qosý qajet",
"Shows": "Көрсетімдер", "Shows": "Kórsetimder",
"Songs": "Әуендер", "Songs": "Áýender",
"StartupEmbyServerIsLoading": "Jellyfin Server жүктелуде. Әрекетті көп ұзамай қайталаңыз.", "StartupEmbyServerIsLoading": "Jellyfin Server júktelýde. Áreketti kóp uzamaı qaıtalańyz.",
"SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз", "SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", "SubtitleDownloadFailureFromForItem": "{1} úshin sýbtıtrlerdi {0} kózinen júktep alý sátsiz",
"SubtitlesDownloadedForItem": "{0} үшін субтитрлер жүктеліп алынды", "SubtitlesDownloadedForItem": "{0} úshin sýbtıtrler júktelip alyndy",
"Sync": "Үндестіру", "Sync": "Úndestirý",
"System": "Жүйе", "System": "ıe",
"TvShows": "ТД-көрсетімдер", "TvShows": "TD-kórsetimder",
"User": "Пайдаланушы", "User": "Paıdalanýshy",
"UserCreatedWithName": "Пайдаланушы {0} жасалған", "UserCreatedWithName": "Paıdalanýshy {0} jasalǵan",
"UserDeletedWithName": "Пайдаланушы {0} жойылған", "UserDeletedWithName": "Paıdalanýshy {0} joıylǵan",
"UserDownloadingItemWithValues": "{0} мынаны жүктеп алуда: {1}", "UserDownloadingItemWithValues": "{0} mynany júktep alýda: {1}",
"UserLockedOutWithName": "Пайдаланушы {0} құрсаулы", "UserLockedOutWithName": "Paıdalanýshy {0} qursaýly",
"UserOfflineFromDevice": "{0} - {1} тарапынан ажыратылған", "UserOfflineFromDevice": "{0} - {1} tarapynan ajyratylǵan",
"UserOnlineFromDevice": "{0} - {1} арқылы қосылған", "UserOnlineFromDevice": "{0} - {1} arqyly qosylǵan",
"UserPasswordChangedWithName": "Пайдаланушы {0} үшін құпия сөз өзгертілді", "UserPasswordChangedWithName": "Paıdalanýshy {0} úshin paról ózgertildi",
"UserPolicyUpdatedWithName": "Пайдаланушы {0} үшін саясаттары жаңартылды", "UserPolicyUpdatedWithName": "Paıdalanýshy {0} úshin saıasattary jańartyldy",
"UserStartedPlayingItemWithValues": "{0} - {1} ойнатуын {2} бастады", "UserStartedPlayingItemWithValues": "{0} - {1} oınatýyn {2} bastady",
"UserStoppedPlayingItemWithValues": "{0} - {1} ойнатуын {2} тоқтатты", "UserStoppedPlayingItemWithValues": "{0} - {1} oınatýyn {2} toqtatty",
"ValueHasBeenAddedToLibrary": "{0} (тасығышханаға үстелінді)", "ValueHasBeenAddedToLibrary": "{0} (tasyǵyshhanaǵa ústelindi)",
"ValueSpecialEpisodeName": "Арнайы - {0}", "ValueSpecialEpisodeName": "Arnaıy - {0}",
"VersionNumber": "Нұсқасы: {0}" "VersionNumber": "Nusqasy {0}"
} }

View file

@ -1,10 +1,10 @@
{ {
"Albums": "Albums", "Albums": "Album-album",
"AppDeviceValues": "App: {0}, Device: {1}", "AppDeviceValues": "App: {0}, Device: {1}",
"Application": "Application", "Application": "Application",
"Artists": "Artists", "Artists": "Artis-artis",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated", "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
"Books": "Books", "Books": "Buku-buku",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
"Channels": "Channels", "Channels": "Channels",
"ChapterNameValue": "Chapter {0}", "ChapterNameValue": "Chapter {0}",

View file

@ -5,28 +5,28 @@
"Artists": "Artiesten", "Artists": "Artiesten",
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
"Books": "Boeken", "Books": "Boeken",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", "CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd via {0}",
"Channels": "Kanalen", "Channels": "Kanalen",
"ChapterNameValue": "Hoofdstuk {0}", "ChapterNameValue": "Hoofdstuk {0}",
"Collections": "Collecties", "Collections": "Collecties",
"DeviceOfflineWithName": "{0} is losgekoppeld", "DeviceOfflineWithName": "{0} heeft de verbinding verbroken",
"DeviceOnlineWithName": "{0} is verbonden", "DeviceOnlineWithName": "{0} is verbonden",
"FailedLoginAttemptWithUserName": "Mislukte aanmeld poging van {0}", "FailedLoginAttemptWithUserName": "Mislukte aanmeld poging van {0}",
"Favorites": "Favorieten", "Favorites": "Favorieten",
"Folders": "Mappen", "Folders": "Mappen",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album artiesten", "HeaderAlbumArtists": "Albumartiesten",
"HeaderCameraUploads": "Camera uploads", "HeaderCameraUploads": "Camera-uploads",
"HeaderContinueWatching": "Kijken hervatten", "HeaderContinueWatching": "Kijken hervatten",
"HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteAlbums": "Favoriete albums",
"HeaderFavoriteArtists": "Favoriete artiesten", "HeaderFavoriteArtists": "Favoriete artiesten",
"HeaderFavoriteEpisodes": "Favoriete afleveringen", "HeaderFavoriteEpisodes": "Favoriete afleveringen",
"HeaderFavoriteShows": "Favoriete shows", "HeaderFavoriteShows": "Favoriete shows",
"HeaderFavoriteSongs": "Favoriete titels", "HeaderFavoriteSongs": "Favoriete nummers",
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Live TV",
"HeaderNextUp": "Volgende", "HeaderNextUp": "Volgende",
"HeaderRecordingGroups": "Opnamegroepen", "HeaderRecordingGroups": "Opnamegroepen",
"HomeVideos": "Thuis video's", "HomeVideos": "Start video's",
"Inherit": "Overerven", "Inherit": "Overerven",
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek", "ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek", "ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
@ -34,22 +34,22 @@
"LabelRunningTimeValue": "Looptijd: {0}", "LabelRunningTimeValue": "Looptijd: {0}",
"Latest": "Nieuwste", "Latest": "Nieuwste",
"MessageApplicationUpdated": "Jellyfin Server is bijgewerkt", "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", "MessageNamedServerConfigurationUpdatedWithValue": "Sectie {0} van de server configuratie is bijgewerkt",
"MessageServerConfigurationUpdated": "Server configuratie is bijgewerkt", "MessageServerConfigurationUpdated": "Server configuratie is bijgewerkt",
"MixedContent": "Gemengde inhoud", "MixedContent": "Gemengde inhoud",
"Movies": "Films", "Movies": "Films",
"Music": "Muziek", "Music": "Muziek",
"MusicVideos": "Muziekvideo's", "MusicVideos": "Muziekvideo's",
"NameInstallFailed": "{0} installation failed", "NameInstallFailed": "{0} installatie mislukt",
"NameSeasonNumber": "Seizoen {0}", "NameSeasonNumber": "Seizoen {0}",
"NameSeasonUnknown": "Seizoen onbekend", "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", "NotificationOptionApplicationUpdateAvailable": "Programma-update beschikbaar",
"NotificationOptionApplicationUpdateInstalled": "Programma-update geïnstalleerd", "NotificationOptionApplicationUpdateInstalled": "Programma-update geïnstalleerd",
"NotificationOptionAudioPlayback": "Geluid gestart", "NotificationOptionAudioPlayback": "Muziek gestart",
"NotificationOptionAudioPlaybackStopped": "Geluid gestopt", "NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
"NotificationOptionCameraImageUploaded": "Camera afbeelding geüpload", "NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
"NotificationOptionInstallationFailed": "Installatie mislukt", "NotificationOptionInstallationFailed": "Installatie mislukt",
"NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd", "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
"NotificationOptionPluginError": "Plug-in fout", "NotificationOptionPluginError": "Plug-in fout",
@ -70,12 +70,12 @@
"ProviderValue": "Aanbieder: {0}", "ProviderValue": "Aanbieder: {0}",
"ScheduledTaskFailedWithName": "{0} is mislukt", "ScheduledTaskFailedWithName": "{0} is mislukt",
"ScheduledTaskStartedWithName": "{0} is gestart", "ScheduledTaskStartedWithName": "{0} is gestart",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "ServerNameNeedsToBeRestarted": "{0} moet herstart worden",
"Shows": "Series", "Shows": "Series",
"Songs": "Titels", "Songs": "Nummers",
"StartupEmbyServerIsLoading": "Jellyfin Server is aan het laden, probeer het later opnieuw.", "StartupEmbyServerIsLoading": "Jellyfin Server is aan het laden, probeer het later opnieuw.",
"SubtitleDownloadFailureForItem": "Downloaden van ondertiteling voor {0} is mislukt", "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", "SubtitlesDownloadedForItem": "Ondertiteling voor {0} is gedownload",
"Sync": "Synchronisatie", "Sync": "Synchronisatie",
"System": "Systeem", "System": "Systeem",
@ -89,9 +89,9 @@
"UserOnlineFromDevice": "{0} heeft verbinding met {1}", "UserOnlineFromDevice": "{0} heeft verbinding met {1}",
"UserPasswordChangedWithName": "Wachtwoord voor {0} is gewijzigd", "UserPasswordChangedWithName": "Wachtwoord voor {0} is gewijzigd",
"UserPolicyUpdatedWithName": "Gebruikersbeleid gewijzigd voor {0}", "UserPolicyUpdatedWithName": "Gebruikersbeleid gewijzigd voor {0}",
"UserStartedPlayingItemWithValues": "{0} heeft afspelen van {1} gestart", "UserStartedPlayingItemWithValues": "{0} heeft afspelen van {1} gestart op {2}",
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt", "UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
"ValueSpecialEpisodeName": "Speciaal - {0}", "ValueSpecialEpisodeName": "Speciaal - {0}",
"VersionNumber": "Versie {0}" "VersionNumber": "Versie {0}"
} }

View file

@ -5,7 +5,7 @@
"Artists": "Исполнители", "Artists": "Исполнители",
"AuthenticationSucceededWithUserName": "{0} - авторизация успешна", "AuthenticationSucceededWithUserName": "{0} - авторизация успешна",
"Books": "Литература", "Books": "Литература",
"CameraImageUploadedFrom": "Новое фото было выложено с {0}", "CameraImageUploadedFrom": "Новое фото было выложено с камеры {0}",
"Channels": "Каналы", "Channels": "Каналы",
"ChapterNameValue": "Сцена {0}", "ChapterNameValue": "Сцена {0}",
"Collections": "Коллекции", "Collections": "Коллекции",
@ -31,20 +31,20 @@
"ItemAddedWithName": "{0} - добавлено в медиатеку", "ItemAddedWithName": "{0} - добавлено в медиатеку",
"ItemRemovedWithName": "{0} - изъято из медиатеки", "ItemRemovedWithName": "{0} - изъято из медиатеки",
"LabelIpAddressValue": "IP-адрес: {0}", "LabelIpAddressValue": "IP-адрес: {0}",
"LabelRunningTimeValue": "Время выполнения: {0}", "LabelRunningTimeValue": "Длительность: {0}",
"Latest": "Новейшее", "Latest": "Новейшее",
"MessageApplicationUpdated": "Jellyfin Server был обновлён", "MessageApplicationUpdated": "Jellyfin Server был обновлён",
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}", "MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена", "MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена",
"MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена", "MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена",
"MixedContent": "Смешанное содержание", "MixedContent": "Смешанное содержимое",
"Movies": "Кино", "Movies": "Кино",
"Music": "Музыка", "Music": "Музыка",
"MusicVideos": "Муз. видео", "MusicVideos": "Муз. видео",
"NameInstallFailed": "Установка {0} неудачна", "NameInstallFailed": "Установка {0} неудачна",
"NameSeasonNumber": "Сезон {0}", "NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Сезон неопознан", "NameSeasonUnknown": "Сезон неопознан",
"NewVersionIsAvailable": "Имеется новая версия Jellyfin Server", "NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения", "NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено", "NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
"NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но", "NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но",
@ -75,7 +75,7 @@
"Songs": "Композиции", "Songs": "Композиции",
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.", "StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить", "SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", "SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
"SubtitlesDownloadedForItem": "Субтитры к {0} загружены", "SubtitlesDownloadedForItem": "Субтитры к {0} загружены",
"Sync": "Синхро", "Sync": "Синхро",
"System": "Система", "System": "Система",

View file

@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <summary> /// <summary>
/// Class ChapterImagesTask /// Class ChapterImagesTask
/// </summary> /// </summary>
class ChapterImagesTask : IScheduledTask public class ChapterImagesTask : IScheduledTask
{ {
/// <summary> /// <summary>
/// The _logger /// The _logger

View file

@ -41,6 +41,27 @@ namespace Emby.Server.Implementations.Serialization
ServiceStack.Text.JsonSerializer.SerializeToStream(obj, obj.GetType(), stream); 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> /// <summary>
/// Serializes to file. /// Serializes to file.
/// </summary> /// </summary>

View file

@ -15,21 +15,17 @@ namespace Emby.Server.Implementations
/// </summary> /// </summary>
public ServerApplicationPaths( public ServerApplicationPaths(
string programDataPath, string programDataPath,
string appFolderPath, string logDirectoryPath,
string applicationResourcesPath, string configurationDirectoryPath,
string logDirectoryPath = null, string cacheDirectoryPath)
string configurationDirectoryPath = null,
string cacheDirectoryPath = null)
: base(programDataPath, : base(programDataPath,
appFolderPath,
logDirectoryPath, logDirectoryPath,
configurationDirectoryPath, configurationDirectoryPath,
cacheDirectoryPath) cacheDirectoryPath)
{ {
ApplicationResourcesPath = applicationResourcesPath;
} }
public string ApplicationResourcesPath { get; private set; } public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
/// <summary> /// <summary>
/// Gets the path to the base root media directory /// Gets the path to the base root media directory
@ -148,7 +144,6 @@ namespace Emby.Server.Implementations
set => _internalMetadataPath = value; set => _internalMetadataPath = value;
} }
private const string _virtualInternalMetadataPath = "%MetadataPath%"; public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
public string VirtualInternalMetadataPath => _virtualInternalMetadataPath;
} }
} }

View file

@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting namespace Emby.Server.Implementations.Sorting
{ {
class AiredEpisodeOrderComparer : IBaseItemComparer public class AiredEpisodeOrderComparer : IBaseItemComparer
{ {
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

View file

@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting namespace Emby.Server.Implementations.Sorting
{ {
class SeriesSortNameComparer : IBaseItemComparer public class SeriesSortNameComparer : IBaseItemComparer
{ {
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

View file

@ -116,6 +116,7 @@ namespace Emby.Server.Implementations.Updates
private readonly IApplicationHost _applicationHost; private readonly IApplicationHost _applicationHost;
private readonly ICryptoProvider _cryptographyProvider; private readonly ICryptoProvider _cryptographyProvider;
private readonly IZipClient _zipClient;
// netframework or netcore // netframework or netcore
private readonly string _packageRuntime; private readonly string _packageRuntime;
@ -129,6 +130,7 @@ namespace Emby.Server.Implementations.Updates
IServerConfigurationManager config, IServerConfigurationManager config,
IFileSystem fileSystem, IFileSystem fileSystem,
ICryptoProvider cryptographyProvider, ICryptoProvider cryptographyProvider,
IZipClient zipClient,
string packageRuntime) string packageRuntime)
{ {
if (loggerFactory == null) if (loggerFactory == null)
@ -146,6 +148,7 @@ namespace Emby.Server.Implementations.Updates
_config = config; _config = config;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_cryptographyProvider = cryptographyProvider; _cryptographyProvider = cryptographyProvider;
_zipClient = zipClient;
_packageRuntime = packageRuntime; _packageRuntime = packageRuntime;
_logger = loggerFactory.CreateLogger(nameof(InstallationManager)); _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) 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 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) 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 // 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(); cancellationToken.ThrowIfCancellationRequested();
// Validate with a checksum // TODO: Validate with a checksum, *properly*
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();
// Success - move it to the real target // Success - move it to the real target
try try
{ {
Directory.CreateDirectory(Path.GetDirectoryName(target)); using (var stream = File.OpenRead(tempFile))
File.Copy(tempFile, target, true);
//If it is an archive - write out a version file so we know what it is
if (isArchive)
{ {
File.WriteAllText(target + ".ver", package.versionStr); _zipClient.ExtractAllFromZip(stream, target, true);
} }
} }
catch (IOException ex) 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; throw;
} }

View file

@ -282,7 +282,7 @@ namespace Jellyfin.Drawing.Skia
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack); var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
// decode // decode
var _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels()); _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
origin = codec.EncodedOrigin; origin = codec.EncodedOrigin;

View file

@ -5,28 +5,47 @@ using Emby.Server.Implementations.HttpServer;
using Jellyfin.Server.SocketSharp; using Jellyfin.Server.SocketSharp;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Jellyfin.Server namespace Jellyfin.Server
{ {
public class CoreAppHost : ApplicationHost 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) public CoreAppHost(
: base(applicationPaths, loggerFactory, options, fileSystem, environmentInfo, imageEncoder, networkManager) 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; public override bool CanSelfRestart => StartupOptions.RestartPath != null;
protected override bool SupportsDualModeSockets => true;
protected override void RestartInternal() => Program.Restart(); protected override void RestartInternal() => Program.Restart();
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal() protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
=> new[] { typeof(CoreAppHost).Assembly }; {
yield return typeof(CoreAppHost).Assembly;
}
protected override void ShutdownInternal() => Program.Shutdown(); protected override void ShutdownInternal() => Program.Shutdown();
protected override bool SupportsDualModeSockets => true;
protected override IHttpListener CreateHttpListener() protected override IHttpListener CreateHttpListener()
=> new WebSocketSharpListener( => new WebSocketSharpListener(
Logger, Logger,
@ -37,7 +56,6 @@ namespace Jellyfin.Server
CryptographyProvider, CryptographyProvider,
SupportsDualModeSockets, SupportsDualModeSockets,
FileSystemManager, FileSystemManager,
EnvironmentInfo EnvironmentInfo);
);
} }
} }

View file

@ -5,11 +5,14 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- We need C# 7.1 for async main--> <!-- We need C# 7.1 for async main-->
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<!-- Disable documentation warnings (for now) -->
<NoWarn>SA1600;CS1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -20,6 +23,10 @@
<EmbeddedResource Include="Resources/Configuration/*" /> <EmbeddedResource Include="Resources/Configuration/*" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code analysers--> <!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" /> <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />

View file

@ -21,6 +21,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Serilog; using Serilog;
using Serilog.AspNetCore; using Serilog.AspNetCore;
@ -34,6 +35,7 @@ namespace Jellyfin.Server
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory();
private static ILogger _logger; private static ILogger _logger;
private static bool _restartOnShutdown; private static bool _restartOnShutdown;
private static IConfiguration appConfig;
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
@ -56,13 +58,32 @@ namespace Jellyfin.Server
errs => Task.FromResult(0)).ConfigureAwait(false); 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) private static async Task StartApp(StartupOptions options)
{ {
ServerApplicationPaths appPaths = CreateApplicationPaths(options); ServerApplicationPaths appPaths = CreateApplicationPaths(options);
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath); Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
await CreateLogger(appPaths);
appConfig = await CreateConfiguration(appPaths).ConfigureAwait(false);
CreateLogger(appConfig, appPaths);
_logger = _loggerFactory.CreateLogger("Main"); _logger = _loggerFactory.CreateLogger("Main");
AppDomain.CurrentDomain.UnhandledException += (sender, e) AppDomain.CurrentDomain.UnhandledException += (sender, e)
@ -75,6 +96,7 @@ namespace Jellyfin.Server
{ {
return; // Already shutting down return; // Already shutting down
} }
e.Cancel = true; e.Cancel = true;
_logger.LogInformation("Ctrl+C, shutting down"); _logger.LogInformation("Ctrl+C, shutting down");
Environment.ExitCode = 128 + 2; Environment.ExitCode = 128 + 2;
@ -88,6 +110,7 @@ namespace Jellyfin.Server
{ {
return; // Already shutting down return; // Already shutting down
} }
_logger.LogInformation("Received a SIGTERM signal, shutting down"); _logger.LogInformation("Received a SIGTERM signal, shutting down");
Environment.ExitCode = 128 + 15; Environment.ExitCode = 128 + 15;
Shutdown(); Shutdown();
@ -101,9 +124,9 @@ namespace Jellyfin.Server
SQLitePCL.Batteries_V2.Init(); SQLitePCL.Batteries_V2.Init();
// Allow all https requests // 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( using (var appHost = new CoreAppHost(
appPaths, appPaths,
@ -112,20 +135,21 @@ namespace Jellyfin.Server
fileSystem, fileSystem,
environmentInfo, environmentInfo,
new NullImageEncoder(), 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); appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
await appHost.RunStartupTasks(); await appHost.RunStartupTasks().ConfigureAwait(false);
// TODO: read input for a stop command // TODO: read input for a stop command
try try
{ {
// Block main thread until shutdown // Block main thread until shutdown
await Task.Delay(-1, _tokenSource.Token); await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false);
} }
catch (TaskCanceledException) 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) private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
{ {
string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH"); // dataDir
if (string.IsNullOrEmpty(programDataPath)) // 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) dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
{
programDataPath = options.DataDir; if (string.IsNullOrEmpty(dataDir))
}
else
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
} }
else else
{ {
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored. // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); dataDir = 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)) // 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)) // configDir
{ // IF --configdir
Console.WriteLine("Cannot continue without path to program data folder (try -programdata)"); // ELSE IF $JELLYFIN_CONFIG_DIR
Environment.Exit(1); // ELSE IF --datadir, use <datadir>/config (assume portable run)
} // ELSE IF <datadir>/config exists, use that
else // ELSE IF windows, use <datadir>/config
{ // ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
Directory.CreateDirectory(programDataPath); // ELSE $HOME/.config/jellyfin
} var configDir = options.ConfigDir;
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir)) if (string.IsNullOrEmpty(configDir))
{ {
if (options.ConfigDir != null) configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{ {
configDir = options.ConfigDir; if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
} {
else // Hang config folder off already set dataDir
{ configDir = Path.Combine(dataDir, "config");
// Let BaseApplicationPaths set up the default value }
configDir = null; 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) // cacheDir
{ // IF --cachedir
Directory.CreateDirectory(configDir); // 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 (string.IsNullOrEmpty(cacheDir))
{ {
if (options.CacheDir != null) cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
if (string.IsNullOrEmpty(cacheDir))
{ {
cacheDir = options.CacheDir; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
}
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))
{ {
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) // logDir
{ // IF --logdir
Directory.CreateDirectory(cacheDir); // 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 (string.IsNullOrEmpty(logDir))
{ {
if (options.LogDir != null) logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
if (string.IsNullOrEmpty(logDir))
{ {
logDir = options.LogDir; // Hang log folder off already set dataDir
} logDir = Path.Combine(dataDir, "log");
else
{
// Let BaseApplicationPaths set up the default value
logDir = null;
} }
} }
if (logDir != null) // Ensure the main folders exist before we continue
try
{ {
Directory.CreateDirectory(dataDir);
Directory.CreateDirectory(logDir); 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(dataDir, logDir, configDir, cacheDir);
return new ServerApplicationPaths(programDataPath, appPath, appPath, 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 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 is used by SerilogLoggerFactory when no logger is specified
Serilog.Log.Logger = new LoggerConfiguration() Serilog.Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration) .ReadFrom.Configuration(configuration)
@ -290,7 +363,7 @@ namespace Jellyfin.Server
} }
} }
public static IImageEncoder GetImageEncoder( private static IImageEncoder GetImageEncoder(
IFileSystem fileSystem, IFileSystem fileSystem,
IApplicationPaths appPaths, IApplicationPaths appPaths,
ILocalizationManager localizationManager) ILocalizationManager localizationManager)
@ -331,26 +404,12 @@ namespace Jellyfin.Server
{ {
return MediaBrowser.Model.System.OperatingSystem.BSD; return MediaBrowser.Model.System.OperatingSystem.BSD;
} }
throw new Exception($"Can't resolve OS with description: '{osDescription}'"); 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) private static void StartNewInstance(StartupOptions options)
{ {
_logger.LogInformation("Starting new instance"); _logger.LogInformation("Starting new instance");

View file

@ -13,7 +13,7 @@ namespace Jellyfin.Server.SocketSharp
{ {
internal static string GetParameter(string header, string attr) internal static string GetParameter(string header, string attr)
{ {
int ap = header.IndexOf(attr); int ap = header.IndexOf(attr, StringComparison.Ordinal);
if (ap == -1) if (ap == -1)
{ {
return null; return null;
@ -82,9 +82,7 @@ namespace Jellyfin.Server.SocketSharp
} }
else else
{ {
//
// We use a substream, as in 2.x we will support large uploads streamed to disk, // 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); var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
files[e.Name] = sub; files[e.Name] = sub;
} }
@ -127,8 +125,12 @@ namespace Jellyfin.Server.SocketSharp
public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"]; public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
protected bool validate_cookies, validate_query_string, validate_form; protected bool validate_cookies { get; set; }
protected bool checked_cookies, checked_query_string, checked_form; 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) private static void ThrowValidationException(string name, string key, string value)
{ {
@ -138,8 +140,12 @@ namespace Jellyfin.Server.SocketSharp
v = v.Substring(0, 16) + "...\""; v = v.Substring(0, 16) + "...\"";
} }
string msg = string.Format("A potentially dangerous Request.{0} value was " + string msg = string.Format(
"detected from the client ({1}={2}).", name, key, v); CultureInfo.InvariantCulture,
"A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
name,
key,
v);
throw new Exception(msg); throw new Exception(msg);
} }
@ -179,6 +185,7 @@ namespace Jellyfin.Server.SocketSharp
for (int idx = 1; idx < len; idx++) for (int idx = 1; idx < len; idx++)
{ {
char next = val[idx]; char next = val[idx];
// See http://secunia.com/advisories/14325 // See http://secunia.com/advisories/14325
if (current == '<' || current == '\xff1c') if (current == '<' || current == '\xff1c')
{ {
@ -256,6 +263,7 @@ namespace Jellyfin.Server.SocketSharp
value.Append((char)c); value.Append((char)c);
} }
} }
if (c == -1) if (c == -1)
{ {
AddRawKeyValue(form, key, value); AddRawKeyValue(form, key, value);
@ -271,6 +279,7 @@ namespace Jellyfin.Server.SocketSharp
key.Append((char)c); key.Append((char)c);
} }
} }
if (c == -1) if (c == -1)
{ {
AddRawKeyValue(form, key, value); AddRawKeyValue(form, key, value);
@ -308,6 +317,7 @@ namespace Jellyfin.Server.SocketSharp
result.Append(key); result.Append(key);
result.Append('='); result.Append('=');
} }
result.Append(pair.Value); result.Append(pair.Value);
} }
@ -429,13 +439,13 @@ namespace Jellyfin.Server.SocketSharp
real = position + d; real = position + d;
break; break;
default: default:
throw new ArgumentException(nameof(origin)); throw new ArgumentException("Unknown SeekOrigin value", nameof(origin));
} }
long virt = real - offset; long virt = real - offset;
if (virt < 0 || virt > Length) if (virt < 0 || virt > Length)
{ {
throw new ArgumentException(); throw new ArgumentException("Invalid position", nameof(d));
} }
position = s.Seek(real, SeekOrigin.Begin); position = s.Seek(real, SeekOrigin.Begin);
@ -491,11 +501,6 @@ namespace Jellyfin.Server.SocketSharp
public Stream InputStream => stream; public Stream InputStream => stream;
} }
private class Helpers
{
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
}
internal static class StrUtils internal static class StrUtils
{ {
public static bool StartsWith(string str1, string str2, bool ignore_case) public static bool StartsWith(string str1, string str2, bool ignore_case)
@ -533,12 +538,17 @@ namespace Jellyfin.Server.SocketSharp
public class Element public class Element
{ {
public string ContentType; public string ContentType { get; set; }
public string Name;
public string Filename; public string Name { get; set; }
public Encoding Encoding;
public long Start; public string Filename { get; set; }
public long Length;
public Encoding Encoding { get; set; }
public long Start { get; set; }
public long Length { get; set; }
public override string ToString() public override string ToString()
{ {
@ -547,15 +557,23 @@ namespace Jellyfin.Server.SocketSharp
} }
} }
private Stream data; private const byte LF = (byte)'\n';
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', 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 // See RFC 2046
// In the case of multipart entities, in which one or more different // 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) public HttpMultipart(Stream data, string b, Encoding encoding)
{ {
this.data = data; 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 = b;
boundary_bytes = encoding.GetBytes(b); boundaryBytes = encoding.GetBytes(b);
buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--' buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
this.encoding = encoding; this.encoding = encoding;
sb = new StringBuilder(); 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() private string ReadLine()
{ {
// CRLF or LF are ok as line endings. // CRLF or LF are ok as line endings.
@ -600,6 +648,7 @@ namespace Jellyfin.Server.SocketSharp
{ {
break; break;
} }
got_cr = b == CR; got_cr = b == CR;
sb.Append((char)b); sb.Append((char)b);
} }
@ -769,7 +818,7 @@ namespace Jellyfin.Server.SocketSharp
return -1; return -1;
} }
if (!CompareBytes(boundary_bytes, buffer)) if (!CompareBytes(boundaryBytes, buffer))
{ {
state = 0; state = 0;
data.Position = retval + 2; data.Position = retval + 2;
@ -785,7 +834,7 @@ namespace Jellyfin.Server.SocketSharp
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-') if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
{ {
at_eof = true; atEof = true;
} }
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF) else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
{ {
@ -800,6 +849,7 @@ namespace Jellyfin.Server.SocketSharp
c = data.ReadByte(); c = data.ReadByte();
continue; continue;
} }
data.Position = retval + 2; data.Position = retval + 2;
if (got_cr) if (got_cr)
{ {
@ -818,42 +868,6 @@ namespace Jellyfin.Server.SocketSharp
return retval; 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) private static string StripPath(string path)
{ {
if (path == null || path.Length == 0) if (path == null || path.Length == 0)

View file

@ -24,6 +24,7 @@ namespace Jellyfin.Server.SocketSharp
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(); private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed = false;
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
{ {
@ -40,9 +41,9 @@ namespace Jellyfin.Server.SocketSharp
_logger = logger; _logger = logger;
WebSocket = socket; WebSocket = socket;
socket.OnMessage += socket_OnMessage; socket.OnMessage += OnSocketMessage;
socket.OnClose += socket_OnClose; socket.OnClose += OnSocketClose;
socket.OnError += socket_OnError; socket.OnError += OnSocketError;
WebSocket.ConnectAsServer(); WebSocket.ConnectAsServer();
} }
@ -52,29 +53,22 @@ namespace Jellyfin.Server.SocketSharp
return _taskCompletionSource.Task; 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); _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); _taskCompletionSource.TrySetResult(true);
Closed?.Invoke(this, EventArgs.Empty); 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) if (OnReceiveBytes != null)
{ {
OnReceiveBytes(e.RawData); OnReceiveBytes(e.RawData);
@ -117,6 +111,7 @@ namespace Jellyfin.Server.SocketSharp
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this);
} }
/// <summary> /// <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> /// <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) protected virtual void Dispose(bool dispose)
{ {
if (_disposed)
{
return;
}
if (dispose) if (dispose)
{ {
WebSocket.OnMessage -= socket_OnMessage; WebSocket.OnMessage -= OnSocketMessage;
WebSocket.OnClose -= socket_OnClose; WebSocket.OnClose -= OnSocketClose;
WebSocket.OnError -= socket_OnError; WebSocket.OnError -= OnSocketError;
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
WebSocket.Close(); WebSocket.Close();
} }
_disposed = true;
} }
/// <summary> /// <summary>
@ -142,11 +144,5 @@ namespace Jellyfin.Server.SocketSharp
/// </summary> /// </summary>
/// <value>The receive action.</value> /// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; } 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; }
} }
} }

View file

@ -34,9 +34,16 @@ namespace Jellyfin.Server.SocketSharp
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
private CancellationToken _disposeCancellationToken; private CancellationToken _disposeCancellationToken;
public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IStreamHelper streamHelper, public WebSocketSharpListener(
INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, ILogger logger,
bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment) X509Certificate certificate,
IStreamHelper streamHelper,
INetworkManager networkManager,
ISocketFactory socketFactory,
ICryptoProvider cryptoProvider,
bool enableDualMode,
IFileSystem fileSystem,
IEnvironmentInfo environment)
{ {
_logger = logger; _logger = logger;
_certificate = certificate; _certificate = certificate;
@ -61,7 +68,9 @@ namespace Jellyfin.Server.SocketSharp
public void Start(IEnumerable<string> urlPrefixes) public void Start(IEnumerable<string> urlPrefixes)
{ {
if (_listener == null) if (_listener == null)
{
_listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment); _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment);
}
_listener.EnableDualMode = _enableDualMode; _listener.EnableDualMode = _enableDualMode;
@ -83,15 +92,18 @@ namespace Jellyfin.Server.SocketSharp
private void ProcessContext(HttpListenerContext context) 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) private static void LogRequest(ILogger logger, HttpListenerRequest request)
{ {
var url = request.Url.ToString(); var url = request.Url.ToString();
logger.LogInformation("{0} {1}. UserAgent: {2}", logger.LogInformation(
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty); "{0} {1}. UserAgent: {2}",
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod,
url,
request.UserAgent ?? string.Empty);
} }
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken) private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
@ -201,7 +213,7 @@ namespace Jellyfin.Server.SocketSharp
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
//TODO Investigate and properly fix. // TODO: Investigate and properly fix.
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -223,38 +235,39 @@ namespace Jellyfin.Server.SocketSharp
public Task Stop() public Task Stop()
{ {
_disposeCancellationTokenSource.Cancel(); _disposeCancellationTokenSource.Cancel();
_listener?.Close();
if (_listener != null)
{
_listener.Close();
}
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary>
/// Releases the unmanaged resources and disposes of the managed resources used.
/// </summary>
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this);
} }
private bool _disposed; 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) protected virtual void Dispose(bool disposing)
{ {
if (_disposed) return; if (_disposed)
lock (_disposeLock)
{ {
if (_disposed) return; return;
if (disposing)
{
Stop();
}
//release unmanaged resources here...
_disposed = true;
} }
if (disposing)
{
Stop().GetAwaiter().GetResult();
}
_disposed = true;
} }
} }
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer;
@ -24,31 +25,7 @@ namespace Jellyfin.Server.SocketSharp
this.request = httpContext.Request; this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
//HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); // 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('/');
} }
public HttpListenerRequest HttpRequest => request; public HttpListenerRequest HttpRequest => request;
@ -69,9 +46,11 @@ namespace Jellyfin.Server.SocketSharp
public string UserHostAddress => request.UserHostAddress; 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"]; 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); name = name.Trim(HttpTrimCharacters);
// First, check for correctly formed multi-line value // 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; int crlf = 0;
for (int i = 0; i < name.Length; ++i) for (int i = 0; i < name.Length; ++i)
{ {
@ -107,6 +86,7 @@ namespace Jellyfin.Server.SocketSharp
switch (crlf) switch (crlf)
{ {
case 0: case 0:
{
if (c == '\r') if (c == '\r')
{ {
crlf = 1; crlf = 1;
@ -121,29 +101,39 @@ namespace Jellyfin.Server.SocketSharp
{ {
throw new ArgumentException("net_WebHeaderInvalidControlChars"); throw new ArgumentException("net_WebHeaderInvalidControlChars");
} }
break; break;
}
case 1: case 1:
{
if (c == '\n') if (c == '\n')
{ {
crlf = 2; crlf = 2;
break; break;
} }
throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
case 2: case 2:
{
if (c == ' ' || c == '\t') if (c == ' ' || c == '\t')
{ {
crlf = 0; crlf = 0;
break; break;
} }
throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
} }
} }
if (crlf != 0) if (crlf != 0)
{ {
throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
} }
return name; return name;
} }
@ -156,6 +146,7 @@ namespace Jellyfin.Server.SocketSharp
return true; return true;
} }
} }
return false; return false;
} }
@ -216,8 +207,15 @@ namespace Jellyfin.Server.SocketSharp
{ {
foreach (var acceptsType in acceptContentTypes) foreach (var acceptsType in acceptContentTypes)
{ {
var contentType = HttpResultFactory.GetRealContentType(acceptsType); // TODO: @bond move to Span when Span.Split lands
acceptsAnything = acceptsAnything || contentType == "*/*"; // https://github.com/dotnet/corefx/issues/26528
var contentType = acceptsType?.Split(';')[0].Trim();
acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase);
if (acceptsAnything)
{
break;
}
} }
if (acceptsAnything) if (acceptsAnything)
@ -226,7 +224,7 @@ namespace Jellyfin.Server.SocketSharp
{ {
return defaultContentType; return defaultContentType;
} }
else if (serverDefaultContentType != null) else
{ {
return serverDefaultContentType; return serverDefaultContentType;
} }
@ -269,11 +267,11 @@ namespace Jellyfin.Server.SocketSharp
private static string GetQueryStringContentType(IRequest httpReq) private static string GetQueryStringContentType(IRequest httpReq)
{ {
var format = httpReq.QueryString["format"]; ReadOnlySpan<char> format = httpReq.QueryString["format"];
if (format == null) if (format == null)
{ {
const int formatMaxLength = 4; const int formatMaxLength = 4;
var pi = httpReq.PathInfo; ReadOnlySpan<char> pi = httpReq.PathInfo;
if (pi == null || pi.Length <= formatMaxLength) if (pi == null || pi.Length <= formatMaxLength)
{ {
return null; return null;
@ -281,7 +279,7 @@ namespace Jellyfin.Server.SocketSharp
if (pi[0] == '/') if (pi[0] == '/')
{ {
pi = pi.Substring(1); pi = pi.Slice(1);
} }
format = LeftPart(pi, '/'); format = LeftPart(pi, '/');
@ -315,6 +313,17 @@ namespace Jellyfin.Server.SocketSharp
return pos == -1 ? strVal : strVal.Substring(0, pos); 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; public static string HandlerFactoryPath;
private string pathInfo; private string pathInfo;
@ -326,7 +335,7 @@ namespace Jellyfin.Server.SocketSharp
{ {
var mode = HandlerFactoryPath; var mode = HandlerFactoryPath;
var pos = request.RawUrl.IndexOf("?", StringComparison.Ordinal); var pos = request.RawUrl.IndexOf('?', StringComparison.Ordinal);
if (pos != -1) if (pos != -1)
{ {
var path = request.RawUrl.Substring(0, pos); var path = request.RawUrl.Substring(0, pos);
@ -343,6 +352,7 @@ namespace Jellyfin.Server.SocketSharp
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
this.pathInfo = NormalizePathInfo(pathInfo, mode); this.pathInfo = NormalizePathInfo(pathInfo, mode);
} }
return this.pathInfo; return this.pathInfo;
} }
} }
@ -444,7 +454,7 @@ namespace Jellyfin.Server.SocketSharp
public string ContentType => request.ContentType; public string ContentType => request.ContentType;
public Encoding contentEncoding; private Encoding contentEncoding;
public Encoding ContentEncoding public Encoding ContentEncoding
{ {
get => contentEncoding ?? request.ContentEncoding; get => contentEncoding ?? request.ContentEncoding;
@ -502,16 +512,20 @@ namespace Jellyfin.Server.SocketSharp
i++; i++;
} }
} }
return httpFiles; return httpFiles;
} }
} }
public static string NormalizePathInfo(string pathInfo, string handlerPath) public static string NormalizePathInfo(string pathInfo, string handlerPath)
{ {
var trimmed = pathInfo.TrimStart('/'); if (handlerPath != null)
if (handlerPath != null && trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase))
{ {
return trimmed.Substring(handlerPath.Length); var trimmed = pathInfo.TrimStart('/');
if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase))
{
return trimmed.Substring(handlerPath.Length);
}
} }
return pathInfo; return pathInfo;

View file

@ -13,12 +13,12 @@ using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
using IRequest = MediaBrowser.Model.Services.IRequest; using IRequest = MediaBrowser.Model.Services.IRequest;
namespace Jellyfin.Server.SocketSharp namespace Jellyfin.Server.SocketSharp
{ {
public class WebSocketSharpResponse : IHttpResponse public class WebSocketSharpResponse : IHttpResponse
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly HttpListenerResponse _response; private readonly HttpListenerResponse _response;
public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request) public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request)
@ -30,7 +30,9 @@ namespace Jellyfin.Server.SocketSharp
} }
public IRequest Request { get; private set; } public IRequest Request { get; private set; }
public Dictionary<string, object> Items { get; private set; } public Dictionary<string, object> Items { get; private set; }
public object OriginalResponse => _response; public object OriginalResponse => _response;
public int StatusCode public int StatusCode
@ -51,7 +53,7 @@ namespace Jellyfin.Server.SocketSharp
set => _response.ContentType = value; set => _response.ContentType = value;
} }
//public ICookies Cookies { get; set; } public QueryParamCollection Headers => _response.Headers;
public void AddHeader(string name, string value) public void AddHeader(string name, string value)
{ {
@ -64,8 +66,6 @@ namespace Jellyfin.Server.SocketSharp
_response.AddHeader(name, value); _response.AddHeader(name, value);
} }
public QueryParamCollection Headers => _response.Headers;
public string GetHeader(string name) public string GetHeader(string name)
{ {
return _response.Headers[name]; return _response.Headers[name];
@ -114,9 +114,9 @@ namespace Jellyfin.Server.SocketSharp
public void SetContentLength(long contentLength) public void SetContentLength(long contentLength)
{ {
//you can happily set the Content-Length header in Asp.Net // 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. // 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 // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
_response.ContentLength64 = contentLength; _response.ContentLength64 = contentLength;
} }
@ -147,15 +147,12 @@ namespace Jellyfin.Server.SocketSharp
{ {
sb.Append($";domain={cookie.Domain}"); sb.Append($";domain={cookie.Domain}");
} }
//else if (restrictAllCookiesToDomain != null)
//{
// sb.Append($";domain={restrictAllCookiesToDomain}");
//}
if (cookie.Secure) if (cookie.Secure)
{ {
sb.Append(";Secure"); sb.Append(";Secure");
} }
if (cookie.HttpOnly) if (cookie.HttpOnly)
{ {
sb.Append(";HttpOnly"); sb.Append(";HttpOnly");
@ -164,7 +161,6 @@ namespace Jellyfin.Server.SocketSharp
return sb.ToString(); return sb.ToString();
} }
public bool SendChunked public bool SendChunked
{ {
get => _response.SendChunked; get => _response.SendChunked;

View file

@ -9,6 +9,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api namespace MediaBrowser.Api
@ -118,8 +119,7 @@ namespace MediaBrowser.Api
{ {
var options = new DtoOptions(); var options = new DtoOptions();
var hasFields = request as IHasItemFields; if (request is IHasItemFields hasFields)
if (hasFields != null)
{ {
options.Fields = hasFields.GetItemFields(); options.Fields = hasFields.GetItemFields();
} }
@ -133,9 +133,11 @@ namespace MediaBrowser.Api
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 || client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1) client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
{ {
var list = options.Fields.ToList(); int oldLen = options.Fields.Length;
list.Add(Model.Querying.ItemFields.RecursiveItemCount); var arr = new ItemFields[oldLen + 1];
options.Fields = list.ToArray(); options.Fields.CopyTo(arr, 0);
arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
options.Fields = arr;
} }
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@ -146,9 +148,12 @@ namespace MediaBrowser.Api
client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 || client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1) client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
{ {
var list = options.Fields.ToList();
list.Add(Model.Querying.ItemFields.ChildCount); int oldLen = options.Fields.Length;
options.Fields = list.ToArray(); 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)) 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();
}
} }
} }

View file

@ -173,14 +173,8 @@ namespace MediaBrowser.Api
_fileSystem.DeleteFile(file); _fileSystem.DeleteFile(file);
} }
public object Get(GetDefaultDirectoryBrowser request) public object Get(GetDefaultDirectoryBrowser request) =>
{ ToOptimizedResult(new DefaultDirectoryBrowserInfo {Path = null});
var result = new DefaultDirectoryBrowserInfo();
result.Path = _fileSystem.DefaultDirectory;
return ToOptimizedResult(result);
}
/// <summary> /// <summary>
/// Gets the specified request. /// Gets the specified request.

View file

@ -8,7 +8,6 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
@ -71,7 +70,6 @@ namespace MediaBrowser.Api.Playback
protected IMediaSourceManager MediaSourceManager { get; private set; } protected IMediaSourceManager MediaSourceManager { get; private set; }
protected IJsonSerializer JsonSerializer { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; }
public static IHttpClient HttpClient;
protected IAuthorizationContext AuthorizationContext { get; private set; } protected IAuthorizationContext AuthorizationContext { get; private set; }
protected EncodingHelper EncodingHelper { get; set; } protected EncodingHelper EncodingHelper { get; set; }

View file

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -33,6 +34,7 @@ namespace MediaBrowser.Api.Playback.Progressive
public class AudioService : BaseProgressiveStreamingService public class AudioService : BaseProgressiveStreamingService
{ {
public AudioService( public AudioService(
IHttpClient httpClient,
IServerConfigurationManager serverConfig, IServerConfigurationManager serverConfig,
IUserManager userManager, IUserManager userManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
@ -46,7 +48,8 @@ namespace MediaBrowser.Api.Playback.Progressive
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuthorizationContext authorizationContext, IAuthorizationContext authorizationContext,
IEnvironmentInfo environmentInfo) IEnvironmentInfo environmentInfo)
: base(serverConfig, : base(httpClient,
serverConfig,
userManager, userManager,
libraryManager, libraryManager,
isoManager, isoManager,

View file

@ -26,8 +26,10 @@ namespace MediaBrowser.Api.Playback.Progressive
public abstract class BaseProgressiveStreamingService : BaseStreamingService public abstract class BaseProgressiveStreamingService : BaseStreamingService
{ {
protected readonly IEnvironmentInfo EnvironmentInfo; protected readonly IEnvironmentInfo EnvironmentInfo;
protected IHttpClient HttpClient { get; private set; }
public BaseProgressiveStreamingService( public BaseProgressiveStreamingService(
IHttpClient httpClient,
IServerConfigurationManager serverConfig, IServerConfigurationManager serverConfig,
IUserManager userManager, IUserManager userManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
@ -55,6 +57,7 @@ namespace MediaBrowser.Api.Playback.Progressive
authorizationContext) authorizationContext)
{ {
EnvironmentInfo = environmentInfo; EnvironmentInfo = environmentInfo;
HttpClient = httpClient;
} }
/// <summary> /// <summary>

View file

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -69,6 +70,7 @@ namespace MediaBrowser.Api.Playback.Progressive
public class VideoService : BaseProgressiveStreamingService public class VideoService : BaseProgressiveStreamingService
{ {
public VideoService( public VideoService(
IHttpClient httpClient,
IServerConfigurationManager serverConfig, IServerConfigurationManager serverConfig,
IUserManager userManager, IUserManager userManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
@ -82,7 +84,8 @@ namespace MediaBrowser.Api.Playback.Progressive
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuthorizationContext authorizationContext, IAuthorizationContext authorizationContext,
IEnvironmentInfo environmentInfo) IEnvironmentInfo environmentInfo)
: base(serverConfig, : base(httpClient,
serverConfig,
userManager, userManager,
libraryManager, libraryManager,
isoManager, isoManager,

View file

@ -77,6 +77,7 @@ namespace MediaBrowser.Api.Playback
public class UniversalAudioService : BaseApiService public class UniversalAudioService : BaseApiService
{ {
public UniversalAudioService( public UniversalAudioService(
IHttpClient httpClient,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IUserManager userManager, IUserManager userManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
@ -95,6 +96,7 @@ namespace MediaBrowser.Api.Playback
IEnvironmentInfo environmentInfo, IEnvironmentInfo environmentInfo,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory)
{ {
HttpClient = httpClient;
ServerConfigurationManager = serverConfigurationManager; ServerConfigurationManager = serverConfigurationManager;
UserManager = userManager; UserManager = userManager;
LibraryManager = libraryManager; LibraryManager = libraryManager;
@ -115,6 +117,7 @@ namespace MediaBrowser.Api.Playback
_logger = loggerFactory.CreateLogger(nameof(UniversalAudioService)); _logger = loggerFactory.CreateLogger(nameof(UniversalAudioService));
} }
protected IHttpClient HttpClient { get; private set; }
protected IServerConfigurationManager ServerConfigurationManager { get; private set; } protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
protected IUserManager UserManager { get; private set; } protected IUserManager UserManager { get; private set; }
protected ILibraryManager LibraryManager { get; private set; } protected ILibraryManager LibraryManager { get; private set; }
@ -323,7 +326,8 @@ namespace MediaBrowser.Api.Playback
} }
else else
{ {
var service = new AudioService(ServerConfigurationManager, var service = new AudioService(HttpClient,
ServerConfigurationManager,
UserManager, UserManager,
LibraryManager, LibraryManager,
IsoManager, IsoManager,

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