merging with master to clear merge conflict

pull/870/head
Phallacy 5 years ago
commit a0d31a49a0

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

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

@ -1,12 +1,111 @@
---
kind: pipeline
name: build
name: build-debug
steps:
- name: submodules
image: docker:git
commands:
- git submodule update --init --recursive
- name: build
image: microsoft/dotnet:2-sdk
commands:
- dotnet publish "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 --configuration release --output /release Jellyfin.Server
- dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
- name: clone-dotnet-compat
image: docker:git
commands:
- git clone --depth 1 https://github.com/EraYaN/dotnet-compatibility ci/dotnet-compatibility
- name: build-dotnet-compat
image: microsoft/dotnet:2-sdk
commands:
- dotnet publish "ci/dotnet-compatibility/CompatibilityCheckerCoreCLI" --configuration Release --output "../../ci-tools"
- name: download-last-nuget-release-common
image: plugins/download
settings:
source: https://www.nuget.org/api/v2/package/Jellyfin.Common
destination: ci/Jellyfin.Common.nupkg
- name: download-last-nuget-release-model
image: plugins/download
settings:
source: https://www.nuget.org/api/v2/package/Jellyfin.Model
destination: ci/Jellyfin.Model.nupkg
- name: download-last-nuget-release-controller
image: plugins/download
settings:
source: https://www.nuget.org/api/v2/package/Jellyfin.Controller
destination: ci/Jellyfin.Controller.nupkg
- name: download-last-nuget-release-naming
image: plugins/download
settings:
source: https://www.nuget.org/api/v2/package/Jellyfin.Naming
destination: ci/Jellyfin.Naming.nupkg
- name: extract-downloaded-nuget-packages
image: garthk/unzip
commands:
- unzip -j ci/Jellyfin.Common.nupkg "*.dll" -d ci/nuget-packages
- unzip -j ci/Jellyfin.Model.nupkg "*.dll" -d ci/nuget-packages
- unzip -j ci/Jellyfin.Controller.nupkg "*.dll" -d ci/nuget-packages
- unzip -j ci/Jellyfin.Naming.nupkg "*.dll" -d ci/nuget-packages
- name: run-dotnet-compat-common
image: microsoft/dotnet:2-runtime
err_ignore: true
commands:
- dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Common.dll ci/ci-release/MediaBrowser.Common.dll
- name: run-dotnet-compat-model
image: microsoft/dotnet:2-runtime
err_ignore: true
commands:
- dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Model.dll ci/ci-release/MediaBrowser.Model.dll
- name: run-dotnet-compat-controller
image: microsoft/dotnet:2-runtime
err_ignore: true
commands:
- dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Controller.dll ci/ci-release/MediaBrowser.Controller.dll
- name: run-dotnet-compat-naming
image: microsoft/dotnet:2-runtime
err_ignore: true
commands:
- dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Emby.Naming.dll ci/ci-release/Emby.Naming.dll

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

4
.gitignore vendored

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

@ -165,7 +165,7 @@ namespace BDInfo
foreach (var file in files)
{
PlaylistFiles.Add(
file.Name.ToUpper(), new TSPlaylistFile(this, file, _fileSystem));
file.Name.ToUpper(), new TSPlaylistFile(this, file));
}
}
@ -185,7 +185,7 @@ namespace BDInfo
foreach (var file in files)
{
StreamClipFiles.Add(
file.Name.ToUpper(), new TSStreamClipFile(file, _fileSystem));
file.Name.ToUpper(), new TSStreamClipFile(file));
}
}

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

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

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

@ -3,9 +3,8 @@ ARG DOTNET_VERSION=2
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
WORKDIR /repo
COPY . .
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
&& dotnet clean \
&& dotnet publish \
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
RUN dotnet publish \
--configuration release \
--output /jellyfin \
Jellyfin.Server
@ -18,9 +17,11 @@ RUN apt-get update \
libfontconfig1 \
&& apt-get clean autoclean \
&& apt-get autoremove \
&& rm -rf /var/lib/{apt,dpkg,cache,log}
&& rm -rf /var/lib/{apt,dpkg,cache,log} \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
COPY --from=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin
EXPOSE 8096
VOLUME /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config
VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache

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

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

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

@ -36,7 +36,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
};
}
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
: base(config, logger, xmlReaderSettingsFactory)
{
}
}

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

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

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

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

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

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

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

@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.AppBase
get
{
// Lazy load
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem));
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
return _configuration;
}
protected set

@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase
@ -18,9 +17,8 @@ namespace Emby.Server.Implementations.AppBase
/// <param name="type">The type.</param>
/// <param name="path">The path.</param>
/// <param name="xmlSerializer">The XML serializer.</param>
/// <param name="fileSystem">The file system</param>
/// <returns>System.Object.</returns>
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer)
{
object configuration;

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

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

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

@ -10,14 +10,18 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Collections
{
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
{
public CollectionImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
public CollectionImageProvider(
IFileSystem fileSystem,
IProviderManager providerManager,
IApplicationPaths applicationPaths,
IImageProcessor imageProcessor)
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
}

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

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

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

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

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

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

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

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

@ -22,9 +22,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
<PackageReference Include="sharpcompress" Version="0.22.0" />
<PackageReference Include="SimpleInjector" Version="4.4.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
</ItemGroup>

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

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

@ -66,11 +66,6 @@ namespace Emby.Server.Implementations.HttpClientManager
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
ServicePointManager.Expect100Continue = false;
#if NET46
// Trakt requests sometimes fail without this
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
#endif
}
/// <summary>
@ -106,23 +101,6 @@ namespace Emby.Server.Implementations.HttpClientManager
return client;
}
private static WebRequest CreateWebRequest(string url)
{
try
{
return WebRequest.Create(url);
}
catch (NotSupportedException)
{
//Webrequest creation does fail on MONO randomly when using WebRequest.Create
//the issue occurs in the GetCreator method here: http://www.oschina.net/code/explore/mono-2.8.1/mcs/class/System/System.Net/WebRequest.cs
var type = Type.GetType("System.Net.HttpRequestCreator, System, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089");
var creator = Activator.CreateInstance(type, nonPublic: true) as IWebRequestCreate;
return creator.Create(new Uri(url)) as HttpWebRequest;
}
}
private WebRequest GetRequest(HttpRequestOptions options, string method)
{
string url = options.Url;
@ -135,7 +113,7 @@ namespace Emby.Server.Implementations.HttpClientManager
url = url.Replace(userInfo + "@", string.Empty);
}
var request = CreateWebRequest(url);
var request = WebRequest.Create(url);
if (request is HttpWebRequest httpWebRequest)
{
@ -627,14 +605,16 @@ namespace Emby.Server.Implementations.HttpClientManager
var exception = new HttpException(webException.Message, webException);
var response = webException.Response as HttpWebResponse;
if (response != null)
using (var response = webException.Response as HttpWebResponse)
{
exception.StatusCode = response.StatusCode;
if ((int)response.StatusCode == 429)
if (response != null)
{
client.LastTimeout = DateTime.UtcNow;
exception.StatusCode = response.StatusCode;
if ((int)response.StatusCode == 429)
{
client.LastTimeout = DateTime.UtcNow;
}
}
}

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

@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
{
var result = new StreamWriter(content, contentType, _logger);
var result = new StreamWriter(content, contentType);
if (responseHeaders == null)
{
@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer
content = Array.Empty<byte>();
}
result = new StreamWriter(content, contentType, contentLength, _logger);
result = new StreamWriter(content, contentType, contentLength);
}
else
{
@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
{
responseHeaders["Expires"] = "-1";
}
@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer
bytes = Array.Empty<byte>();
}
result = new StreamWriter(bytes, contentType, contentLength, _logger);
result = new StreamWriter(bytes, contentType, contentLength);
}
else
{
@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
{
responseHeaders["Expires"] = "-1";
}
@ -277,9 +277,10 @@ namespace Emby.Server.Implementations.HttpServer
private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null)
{
var contentType = request.ResponseContentType;
// TODO: @bond use Span and .Equals
var contentType = request.ResponseContentType?.Split(';')[0].Trim().ToLowerInvariant();
switch (GetRealContentType(contentType))
switch (contentType)
{
case "application/xml":
case "text/xml":
@ -333,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer
if (isHeadRequest)
{
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger);
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength);
AddResponseHeaders(result, responseHeaders);
return result;
}
else
{
var result = new StreamWriter(content, contentType, contentLength, _logger);
var result = new StreamWriter(content, contentType, contentLength);
AddResponseHeaders(result, responseHeaders);
return result;
}
@ -348,13 +349,19 @@ namespace Emby.Server.Implementations.HttpServer
private byte[] Compress(byte[] bytes, string compressionType)
{
if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase))
{
return CompressBrotli(bytes);
}
if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase))
{
return Deflate(bytes);
}
if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase))
{
return GZip(bytes);
}
throw new NotSupportedException(compressionType);
}
@ -390,13 +397,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
public static string GetRealContentType(string contentType)
{
return contentType == null
? null
: contentType.Split(';')[0].ToLowerInvariant().Trim();
}
private static string SerializeToXmlString(object from)
{
using (var ms = new MemoryStream())
@ -422,18 +422,20 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Pres the process optimized result.
/// </summary>
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
{
bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
if (!noCache)
{
if (IsNotModified(requestContext, cacheKey))
DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader);
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
{
AddAgeHeader(responseHeaders, lastDateModified);
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
AddAgeHeader(responseHeaders, options.DateLastModified);
var result = new HttpResult(Array.Empty<byte>(), contentType ?? "text/html", HttpStatusCode.NotModified);
var result = new HttpResult(Array.Empty<byte>(), options.ContentType ?? "text/html", HttpStatusCode.NotModified);
AddResponseHeaders(result, responseHeaders);
@ -441,8 +443,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration);
return null;
}
@ -487,9 +487,6 @@ namespace Emby.Server.Implementations.HttpServer
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
}
var cacheKey = path + options.DateLastModified.Value.Ticks;
options.CacheKey = cacheKey.GetMD5();
options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare));
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@ -520,7 +517,6 @@ namespace Emby.Server.Implementations.HttpServer
return GetStaticResult(requestContext, new StaticResultOptions
{
CacheDuration = cacheDuration,
CacheKey = cacheKey,
ContentFactory = factoryFn,
ContentType = contentType,
DateLastModified = lastDateModified,
@ -534,14 +530,10 @@ namespace Emby.Server.Implementations.HttpServer
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var contentType = options.ContentType;
var etag = requestContext.Headers.Get("If-None-Match");
var cacheKey = etag != null ? new Guid(etag.Trim('\"')) : Guid.Empty;
if (!cacheKey.Equals(Guid.Empty))
if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since")))
{
var key = cacheKey.ToString("N");
// See if the result is already cached in the browser
var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType);
var result = GetCachedResult(requestContext, options.ResponseHeaders, options);
if (result != null)
{
@ -553,6 +545,8 @@ namespace Emby.Server.Implementations.HttpServer
var isHeadRequest = options.IsHeadRequest || string.Equals(requestContext.Verb, "HEAD", StringComparison.OrdinalIgnoreCase);
var factoryFn = options.ContentFactory;
var responseHeaders = options.ResponseHeaders;
AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified);
AddAgeHeader(responseHeaders, options.DateLastModified);
var rangeHeader = requestContext.Headers.Get("Range");
@ -566,21 +560,10 @@ namespace Emby.Server.Implementations.HttpServer
};
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
// Generate an ETag based on identifying information - TODO read contents from filesystem instead?
var responseId = $"{hasHeaders.ContentType}{options.Path}{hasHeaders.TotalContentLength}";
var hashedId = MD5.Create().ComputeHash(Encoding.Default.GetBytes(responseId));
hasHeaders.Headers["ETag"] = new Guid(hashedId).ToString("N");
return hasHeaders;
}
var stream = await factoryFn().ConfigureAwait(false);
// Generate an etag based on stream content
var streamHash = MD5.Create().ComputeHash(stream);
var newEtag = new Guid(streamHash).ToString("N");
// reset position so the response can re-use it -- TODO is this ok?
stream.Position = 0;
var totalContentLength = options.ContentLength;
if (!totalContentLength.HasValue)
@ -603,7 +586,6 @@ namespace Emby.Server.Implementations.HttpServer
};
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
hasHeaders.Headers["ETag"] = newEtag;
return hasHeaders;
}
else
@ -621,14 +603,13 @@ namespace Emby.Server.Implementations.HttpServer
}
}
var hasHeaders = new StreamWriter(stream, contentType, _logger)
var hasHeaders = new StreamWriter(stream, contentType)
{
OnComplete = options.OnComplete,
OnError = options.OnError
};
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
hasHeaders.Headers["ETag"] = newEtag;
return hasHeaders;
}
}
@ -641,37 +622,28 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Adds the caching responseHeaders.
/// </summary>
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration)
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration,
bool noCache, DateTime? lastModifiedDate)
{
if (cacheDuration.HasValue)
{
responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
}
else if (!string.IsNullOrEmpty(cacheKey))
{
responseHeaders["Cache-Control"] = "public";
}
else
if (noCache)
{
responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate";
responseHeaders["pragma"] = "no-cache, no-store, must-revalidate";
return;
}
AddExpiresHeader(responseHeaders, cacheKey, cacheDuration);
}
/// <summary>
/// Adds the expires header.
/// </summary>
private static void AddExpiresHeader(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration)
{
if (cacheDuration.HasValue)
{
responseHeaders["Expires"] = DateTime.UtcNow.Add(cacheDuration.Value).ToString("r");
responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds;
}
else if (string.IsNullOrEmpty(cacheKey))
else
{
responseHeaders["Expires"] = "-1";
responseHeaders["Cache-Control"] = "public";
}
if (lastModifiedDate.HasValue)
{
responseHeaders["Last-Modified"] = lastModifiedDate.ToString();
}
}
@ -687,32 +659,6 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
}
}
/// <summary>
/// Determines whether [is not modified] [the specified cache key].
/// </summary>
/// <param name="requestContext">The request context.</param>
/// <param name="cacheKey">The cache key.</param>
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
private bool IsNotModified(IRequest requestContext, Guid cacheKey)
{
var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match");
bool hasCacheKey = !cacheKey.Equals(Guid.Empty);
// Validate If-None-Match
if (hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader))
{
if (Guid.TryParse(ifNoneMatchHeader, out var ifNoneMatch)
&& cacheKey.Equals(ifNoneMatch))
{
return true;
}
}
return false;
}
/// <summary>
/// Determines whether [is not modified] [the specified if modified since].

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

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

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

@ -4,8 +4,10 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO
@ -20,61 +22,27 @@ namespace Emby.Server.Implementations.IO
private readonly bool _supportsAsyncFileStreams;
private char[] _invalidFileNameChars;
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
private bool EnableSeparateFileAndDirectoryQueries;
private string _tempPath;
private readonly string _tempPath;
private IEnvironmentInfo _environmentInfo;
private bool _isEnvironmentCaseInsensitive;
private string _defaultDirectory;
private readonly IEnvironmentInfo _environmentInfo;
private readonly bool _isEnvironmentCaseInsensitive;
public ManagedFileSystem(
ILoggerFactory loggerFactory,
IEnvironmentInfo environmentInfo,
string defaultDirectory,
string tempPath,
bool enableSeparateFileAndDirectoryQueries)
IApplicationPaths applicationPaths)
{
Logger = loggerFactory.CreateLogger("FileSystem");
_supportsAsyncFileStreams = true;
_tempPath = tempPath;
_tempPath = applicationPaths.TempDirectory;
_environmentInfo = environmentInfo;
_defaultDirectory = defaultDirectory;
// On Linux with mono, this needs to be true or symbolic links are ignored
EnableSeparateFileAndDirectoryQueries = enableSeparateFileAndDirectoryQueries;
SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
_isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
}
public virtual string DefaultDirectory
{
get
{
var value = _defaultDirectory;
if (!string.IsNullOrEmpty(value))
{
try
{
if (Directory.Exists(value))
{
return value;
}
}
catch
{
}
}
return null;
}
}
public virtual void AddShortcutHandler(IShortcutHandler handler)
{
_shortcutHandlers.Add(handler);
@ -718,7 +686,7 @@ namespace Emby.Server.Implementations.IO
SetAttributes(path, false, false);
File.Delete(path);
}
public virtual List<FileSystemMetadata> GetDrives()
{
// Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
@ -777,20 +745,15 @@ namespace Emby.Server.Implementations.IO
var directoryInfo = new DirectoryInfo(path);
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
if (EnableSeparateFileAndDirectoryQueries)
{
return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
.Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
}
return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", searchOption));
return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
.Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
}
private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
{
return infos.Select(GetFileSystemMetadata);
}
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
{
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;

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

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

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

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

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

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

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

@ -1,13 +1,13 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new()
{
public GenericVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
public GenericVideoResolver(ILibraryManager libraryManager)
: base(libraryManager)
{
}
}

File diff suppressed because it is too large Load Diff

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

@ -105,8 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_mediaSourceManager = mediaSourceManager;
_streamHelper = streamHelper;
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
_seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
_timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
_timerProvider.TimerFired += _timerProvider_TimerFired;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
@ -1708,7 +1708,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
{
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _httpClient, _processFactory, _config, _assemblyInfo);
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
}
return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);

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

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

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

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

@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.LiveTv
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{
var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId);
var user = query.UserId == Guid.Empty ? null : _userManager.GetUserById(query.UserId);
var topFolder = GetInternalLiveTvFolder(cancellationToken);

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

@ -31,15 +31,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
private readonly IEnvironmentInfo _environment;
public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
public HdHomerunHost(
IServerConfigurationManager config,
ILogger logger,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IHttpClient httpClient,
IServerApplicationHost appHost,
ISocketFactory socketFactory,
INetworkManager networkManager)
: base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
_environment = environment;
}
public string Name => "HD Homerun";

@ -26,15 +26,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
private readonly IEnvironmentInfo _environment;
private readonly INetworkManager _networkManager;
private readonly IMediaSourceManager _mediaSourceManager;
public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
: base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_environment = environment;
_networkManager = networkManager;
_mediaSourceManager = mediaSourceManager;
}
@ -52,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
var channelIdPrefix = GetFullChannelIdPrefix(info);
var result = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
var result = await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
return result.Cast<ChannelInfo>().ToList();
}
@ -115,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task Validate(TunerHostInfo info)
{
using (var stream = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
{
}

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

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

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

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

@ -90,7 +90,7 @@
"UserPasswordChangedWithName": "Password has been changed for user {0}",
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
"UserStartedPlayingItemWithValues": "{0} has started playing {1}",
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}"

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

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

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

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

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

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

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

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

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

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

@ -41,6 +41,27 @@ namespace Emby.Server.Implementations.Serialization
ServiceStack.Text.JsonSerializer.SerializeToStream(obj, obj.GetType(), stream);
}
/// <summary>
/// Serializes to stream.
/// </summary>
/// <param name="obj">The obj.</param>
/// <param name="stream">The stream.</param>
/// <exception cref="ArgumentNullException">obj</exception>
public void SerializeToStream<T>(T obj, Stream stream)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
ServiceStack.Text.JsonSerializer.SerializeToStream<T>(obj, stream);
}
/// <summary>
/// Serializes to file.
/// </summary>

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

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

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

@ -116,6 +116,7 @@ namespace Emby.Server.Implementations.Updates
private readonly IApplicationHost _applicationHost;
private readonly ICryptoProvider _cryptographyProvider;
private readonly IZipClient _zipClient;
// netframework or netcore
private readonly string _packageRuntime;
@ -129,6 +130,7 @@ namespace Emby.Server.Implementations.Updates
IServerConfigurationManager config,
IFileSystem fileSystem,
ICryptoProvider cryptographyProvider,
IZipClient zipClient,
string packageRuntime)
{
if (loggerFactory == null)
@ -146,6 +148,7 @@ namespace Emby.Server.Implementations.Updates
_config = config;
_fileSystem = fileSystem;
_cryptographyProvider = cryptographyProvider;
_zipClient = zipClient;
_packageRuntime = packageRuntime;
_logger = loggerFactory.CreateLogger(nameof(InstallationManager));
}
@ -526,14 +529,18 @@ namespace Emby.Server.Implementations.Updates
private async Task PerformPackageInstallation(IProgress<double> progress, string target, PackageVersionInfo package, CancellationToken cancellationToken)
{
// Target based on if it is an archive or single assembly
// zip archives are assumed to contain directory structures relative to our ProgramDataPath
var extension = Path.GetExtension(package.targetFilename);
var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".rar", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".7z", StringComparison.OrdinalIgnoreCase);
var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase);
if (!isArchive)
{
_logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename);
return;
}
if (target == null)
{
target = Path.Combine(isArchive ? _appPaths.TempUpdatePath : _appPaths.PluginsPath, package.targetFilename);
target = Path.Combine(_appPaths.PluginsPath, Path.GetFileNameWithoutExtension(package.targetFilename));
}
// Download to temporary file so that, if interrupted, it won't destroy the existing installation
@ -547,36 +554,19 @@ namespace Emby.Server.Implementations.Updates
cancellationToken.ThrowIfCancellationRequested();
// Validate with a checksum
var packageChecksum = string.IsNullOrWhiteSpace(package.checksum) ? Guid.Empty : new Guid(package.checksum);
if (!packageChecksum.Equals(Guid.Empty)) // support for legacy uploads for now
{
using (var stream = File.OpenRead(tempFile))
{
var check = Guid.Parse(BitConverter.ToString(_cryptographyProvider.ComputeMD5(stream)).Replace("-", string.Empty));
if (check != packageChecksum)
{
throw new Exception(string.Format("Download validation failed for {0}. Probably corrupted during transfer.", package.name));
}
}
}
cancellationToken.ThrowIfCancellationRequested();
// TODO: Validate with a checksum, *properly*
// Success - move it to the real target
try
{
Directory.CreateDirectory(Path.GetDirectoryName(target));
File.Copy(tempFile, target, true);
//If it is an archive - write out a version file so we know what it is
if (isArchive)
using (var stream = File.OpenRead(tempFile))
{
File.WriteAllText(target + ".ver", package.versionStr);
_zipClient.ExtractAllFromZip(stream, target, true);
}
}
catch (IOException ex)
{
_logger.LogError(ex, "Error attempting to move file from {TempFile} to {TargetFile}", tempFile, target);
_logger.LogError(ex, "Error attempting to extract {TempFile} to {TargetFile}", tempFile, target);
throw;
}

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

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

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

@ -21,6 +21,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.AspNetCore;
@ -34,6 +35,7 @@ namespace Jellyfin.Server
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory();
private static ILogger _logger;
private static bool _restartOnShutdown;
private static IConfiguration appConfig;
public static async Task Main(string[] args)
{
@ -56,13 +58,32 @@ namespace Jellyfin.Server
errs => Task.FromResult(0)).ConfigureAwait(false);
}
public static void Shutdown()
{
if (!_tokenSource.IsCancellationRequested)
{
_tokenSource.Cancel();
}
}
public static void Restart()
{
_restartOnShutdown = true;
Shutdown();
}
private static async Task StartApp(StartupOptions options)
{
ServerApplicationPaths appPaths = CreateApplicationPaths(options);
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
await CreateLogger(appPaths);
appConfig = await CreateConfiguration(appPaths).ConfigureAwait(false);
CreateLogger(appConfig, appPaths);
_logger = _loggerFactory.CreateLogger("Main");
AppDomain.CurrentDomain.UnhandledException += (sender, e)
@ -75,6 +96,7 @@ namespace Jellyfin.Server
{
return; // Already shutting down
}
e.Cancel = true;
_logger.LogInformation("Ctrl+C, shutting down");
Environment.ExitCode = 128 + 2;
@ -88,6 +110,7 @@ namespace Jellyfin.Server
{
return; // Already shutting down
}
_logger.LogInformation("Received a SIGTERM signal, shutting down");
Environment.ExitCode = 128 + 15;
Shutdown();
@ -101,9 +124,9 @@ namespace Jellyfin.Server
SQLitePCL.Batteries_V2.Init();
// Allow all https requests
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; } );
var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, null, appPaths.TempDirectory, true);
var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, appPaths);
using (var appHost = new CoreAppHost(
appPaths,
@ -112,20 +135,21 @@ namespace Jellyfin.Server
fileSystem,
environmentInfo,
new NullImageEncoder(),
new NetworkManager(_loggerFactory, environmentInfo)))
new NetworkManager(_loggerFactory, environmentInfo),
appConfig))
{
await appHost.Init();
await appHost.Init(new ServiceCollection()).ConfigureAwait(false);
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
await appHost.RunStartupTasks();
await appHost.RunStartupTasks().ConfigureAwait(false);
// TODO: read input for a stop command
try
{
// Block main thread until shutdown
await Task.Delay(-1, _tokenSource.Token);
await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
@ -139,136 +163,185 @@ namespace Jellyfin.Server
}
}
/// <summary>
/// Create the data, config and log paths from the variety of inputs(command line args,
/// environment variables) or decide on what default to use. For Windows it's %AppPath%
/// for everything else the XDG approach is followed:
/// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
/// </summary>
/// <param name="options">StartupOptions</param>
/// <returns>ServerApplicationPaths</returns>
private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
{
string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
if (string.IsNullOrEmpty(programDataPath))
// dataDir
// IF --datadir
// ELSE IF $JELLYFIN_DATA_PATH
// ELSE IF windows, use <%APPDATA%>/jellyfin
// ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin
// ELSE use $HOME/.local/share/jellyfin
var dataDir = options.DataDir;
if (string.IsNullOrEmpty(dataDir))
{
if (options.DataDir != null)
{
programDataPath = options.DataDir;
}
else
dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
if (string.IsNullOrEmpty(dataDir))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
}
else
{
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
// If $XDG_DATA_HOME is either not set or empty, $HOME/.local/share should be used.
if (string.IsNullOrEmpty(programDataPath))
dataDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
if (string.IsNullOrEmpty(dataDir))
{
programDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
}
}
programDataPath = Path.Combine(programDataPath, "jellyfin");
dataDir = Path.Combine(dataDir, "jellyfin");
}
}
if (string.IsNullOrEmpty(programDataPath))
{
Console.WriteLine("Cannot continue without path to program data folder (try -programdata)");
Environment.Exit(1);
}
else
{
Directory.CreateDirectory(programDataPath);
}
// configDir
// IF --configdir
// ELSE IF $JELLYFIN_CONFIG_DIR
// ELSE IF --datadir, use <datadir>/config (assume portable run)
// ELSE IF <datadir>/config exists, use that
// ELSE IF windows, use <datadir>/config
// ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
// ELSE $HOME/.config/jellyfin
var configDir = options.ConfigDir;
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{
if (options.ConfigDir != null)
{
configDir = options.ConfigDir;
}
else
configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{
// Let BaseApplicationPaths set up the default value
configDir = null;
if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Hang config folder off already set dataDir
configDir = Path.Combine(dataDir, "config");
}
else
{
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
configDir = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME /.config should be used.
if (string.IsNullOrEmpty(configDir))
{
configDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
}
configDir = Path.Combine(configDir, "jellyfin");
}
}
}
if (configDir != null)
{
Directory.CreateDirectory(configDir);
}
// cacheDir
// IF --cachedir
// ELSE IF $JELLYFIN_CACHE_DIR
// ELSE IF windows, use <datadir>/cache
// ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin
// ELSE HOME/.cache/jellyfin
var cacheDir = options.CacheDir;
string cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
if (string.IsNullOrEmpty(cacheDir))
{
if (options.CacheDir != null)
{
cacheDir = options.CacheDir;
}
else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
if (string.IsNullOrEmpty(cacheDir))
{
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
// If $XDG_CACHE_HOME is either not set or empty, $HOME/.cache should be used.
if (string.IsNullOrEmpty(cacheDir))
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Hang cache folder off already set dataDir
cacheDir = Path.Combine(dataDir, "cache");
}
else
{
cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
if (string.IsNullOrEmpty(cacheDir))
{
cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
}
cacheDir = Path.Combine(cacheDir, "jellyfin");
}
cacheDir = Path.Combine(cacheDir, "jellyfin");
}
}
if (cacheDir != null)
{
Directory.CreateDirectory(cacheDir);
}
// logDir
// IF --logdir
// ELSE IF $JELLYFIN_LOG_DIR
// ELSE IF --datadir, use <datadir>/log (assume portable run)
// ELSE <datadir>/log
var logDir = options.LogDir;
string logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
if (string.IsNullOrEmpty(logDir))
{
if (options.LogDir != null)
{
logDir = options.LogDir;
}
else
logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
if (string.IsNullOrEmpty(logDir))
{
// Let BaseApplicationPaths set up the default value
logDir = null;
// Hang log folder off already set dataDir
logDir = Path.Combine(dataDir, "log");
}
}
if (logDir != null)
// Ensure the main folders exist before we continue
try
{
Directory.CreateDirectory(dataDir);
Directory.CreateDirectory(logDir);
Directory.CreateDirectory(configDir);
Directory.CreateDirectory(cacheDir);
}
catch (IOException ex)
{
Console.Error.WriteLine("Error whilst attempting to create folder");
Console.Error.WriteLine(ex.ToString());
Environment.Exit(1);
}
string appPath = AppContext.BaseDirectory;
return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir);
return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir);
}
private static async Task CreateLogger(IApplicationPaths appPaths)
private static async Task<IConfiguration> CreateConfiguration(IApplicationPaths appPaths)
{
try
{
string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json");
string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json");
if (!File.Exists(configPath))
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))
{
// 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);
}
await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
}
var configuration = new ConfigurationBuilder()
.SetBasePath(appPaths.ConfigurationDirectoryPath)
.AddJsonFile("logging.json")
.AddEnvironmentVariables("JELLYFIN_")
.Build();
}
return new ConfigurationBuilder()
.SetBasePath(appPaths.ConfigurationDirectoryPath)
.AddJsonFile("logging.json")
.AddEnvironmentVariables("JELLYFIN_")
.AddInMemoryCollection(ConfigurationOptions.Configuration)
.Build();
}
private static void CreateLogger(IConfiguration configuration, IApplicationPaths appPaths)
{
try
{
// Serilog.Log is used by SerilogLoggerFactory when no logger is specified
Serilog.Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
@ -290,7 +363,7 @@ namespace Jellyfin.Server
}
}
public static IImageEncoder GetImageEncoder(
private static IImageEncoder GetImageEncoder(
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILocalizationManager localizationManager)
@ -331,26 +404,12 @@ namespace Jellyfin.Server
{
return MediaBrowser.Model.System.OperatingSystem.BSD;
}
throw new Exception($"Can't resolve OS with description: '{osDescription}'");
}
}
}
public static void Shutdown()
{
if (!_tokenSource.IsCancellationRequested)
{
_tokenSource.Cancel();
}
}
public static void Restart()
{
_restartOnShutdown = true;
Shutdown();
}
private static void StartNewInstance(StartupOptions options)
{
_logger.LogInformation("Starting new instance");

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

@ -24,6 +24,7 @@ namespace Jellyfin.Server.SocketSharp
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed = false;
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
{
@ -40,9 +41,9 @@ namespace Jellyfin.Server.SocketSharp
_logger = logger;
WebSocket = socket;
socket.OnMessage += socket_OnMessage;
socket.OnClose += socket_OnClose;
socket.OnError += socket_OnError;
socket.OnMessage += OnSocketMessage;
socket.OnClose += OnSocketClose;
socket.OnError += OnSocketError;
WebSocket.ConnectAsServer();
}
@ -52,29 +53,22 @@ namespace Jellyfin.Server.SocketSharp
return _taskCompletionSource.Task;
}
void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e)
private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e)
{
_logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty);
//Closed?.Invoke(this, EventArgs.Empty);
// Closed?.Invoke(this, EventArgs.Empty);
}
void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e)
private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e)
{
_taskCompletionSource.TrySetResult(true);
Closed?.Invoke(this, EventArgs.Empty);
}
void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e)
private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e)
{
//if (!string.IsNullOrEmpty(e.Data))
//{
// if (OnReceive != null)
// {
// OnReceive(e.Data);
// }
// return;
//}
if (OnReceiveBytes != null)
{
OnReceiveBytes(e.RawData);
@ -117,6 +111,7 @@ namespace Jellyfin.Server.SocketSharp
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
@ -125,16 +120,23 @@ namespace Jellyfin.Server.SocketSharp
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (_disposed)
{
return;
}
if (dispose)
{
WebSocket.OnMessage -= socket_OnMessage;
WebSocket.OnClose -= socket_OnClose;
WebSocket.OnError -= socket_OnError;
WebSocket.OnMessage -= OnSocketMessage;
WebSocket.OnClose -= OnSocketClose;
WebSocket.OnError -= OnSocketError;
_cancellationTokenSource.Cancel();
WebSocket.Close();
}
_disposed = true;
}
/// <summary>
@ -142,11 +144,5 @@ namespace Jellyfin.Server.SocketSharp
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; }
/// <summary>
/// Gets or sets the on receive.
/// </summary>
/// <value>The on receive.</value>
public Action<string> OnReceive { get; set; }
}
}

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

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

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

@ -9,6 +9,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api
@ -118,8 +119,7 @@ namespace MediaBrowser.Api
{
var options = new DtoOptions();
var hasFields = request as IHasItemFields;
if (hasFields != null)
if (request is IHasItemFields hasFields)
{
options.Fields = hasFields.GetItemFields();
}
@ -133,9 +133,11 @@ namespace MediaBrowser.Api
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
{
var list = options.Fields.ToList();
list.Add(Model.Querying.ItemFields.RecursiveItemCount);
options.Fields = list.ToArray();
int oldLen = options.Fields.Length;
var arr = new ItemFields[oldLen + 1];
options.Fields.CopyTo(arr, 0);
arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
options.Fields = arr;
}
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@ -146,9 +148,12 @@ namespace MediaBrowser.Api
client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
{
var list = options.Fields.ToList();
list.Add(Model.Querying.ItemFields.ChildCount);
options.Fields = list.ToArray();
int oldLen = options.Fields.Length;
var arr = new ItemFields[oldLen + 1];
options.Fields.CopyTo(arr, 0);
arr[oldLen] = Model.Querying.ItemFields.ChildCount;
options.Fields = arr;
}
}
@ -167,7 +172,16 @@ namespace MediaBrowser.Api
if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
{
options.ImageTypes = (hasDtoOptions.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
if (string.IsNullOrEmpty(hasDtoOptions.EnableImageTypes))
{
options.ImageTypes = Array.Empty<ImageType>();
}
else
{
options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true))
.ToArray();
}
}
}

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

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

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

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

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

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

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

Loading…
Cancel
Save