Merge remote-tracking branch 'upstream/master' into random

This commit is contained in:
Bond-009 2019-12-06 12:06:13 +01:00
commit a2c35e6dba
339 changed files with 4276 additions and 4900 deletions

View File

@ -200,8 +200,8 @@ jobs:
persistCredentials: true
- task: CmdLine@2
displayName: "Check out web"
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
displayName: "Check out web (master, release or tag)"
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
@ -245,7 +245,7 @@ jobs:
inputs:
targetType: 'filePath' # Optional. Options: filePath, inline
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
#script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
#failOnStderr: false # Optional

View File

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

View File

@ -30,6 +30,7 @@ assignees: ''
- OS: [e.g. Docker, Debian, Windows]
- Browser: [e.g. Firefox, Chrome, Safari]
- Jellyfin Version: [e.g. 10.0.1]
- Installed Plugins: [e.g. none, Fanart, Anime, etc.]
- Reverse proxy: [e.g. no, nginx, apache, etc.]
**Additional context**

13
.github/stale.yml vendored
View File

@ -1,7 +1,7 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 90
daysUntilStale: 120
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
daysUntilClose: 21
# Issues with these labels will never be considered stale
exemptLabels:
- regression
@ -11,12 +11,15 @@ exemptLabels:
- future
- feature
- enhancement
- confirmed
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
If this issue is safe to close now please do so.
If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or nightlies, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

3
.gitignore vendored
View File

@ -268,3 +268,6 @@ doc/
# Deployment artifacts
dist
*.exe
# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts

4
.vscode/launch.json vendored
View File

@ -10,7 +10,7 @@
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin.dll",
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp3.0/jellyfin.dll",
"args": [],
"cwd": "${workspaceFolder}/Jellyfin.Server",
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
@ -25,4 +25,4 @@
"processId": "${command:pickProcess}"
}
,]
}
}

View File

@ -1,8 +1,8 @@
ARG DOTNET_VERSION=2.2
ARG DOTNET_VERSION=3.0
ARG FFMPEG_VERSION=latest
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=v10.5.0
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
@ -10,33 +10,39 @@ RUN apk add curl \
&& yarn build \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
FROM debian:buster-slim
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}
COPY --from=ffmpeg / /
COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
# Install dependencies:
# libfontconfig1: needed for Skia
# libgomp1: needed for ffmpeg
# libva-drm2: needed for ffmpeg
# mesa-va-drivers: needed for VAAPI
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
libfontconfig1 mesa-va-drivers \
libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl \
&& apt-get clean autoclean \
&& apt-get autoremove \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
&& chmod 777 /cache /config /media \
&& ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
&& ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/local/bin/ffmpeg
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
"--ffmpeg", "/usr/local/bin/ffmpeg"]

View File

@ -4,7 +4,7 @@ ARG DOTNET_VERSION=3.0
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=v10.5.0
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
@ -17,8 +17,6 @@ FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# TODO Remove or update the sed line when we update dotnet version.
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
@ -26,7 +24,7 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin"
FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
FROM debian:stretch-slim-arm32v7
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
@ -36,9 +34,11 @@ RUN apt-get update \
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/bin/ffmpeg
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
"--ffmpeg", "/usr/bin/ffmpeg"]

View File

@ -4,7 +4,7 @@ ARG DOTNET_VERSION=3.0
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=v10.5.0
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
@ -17,8 +17,6 @@ FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# TODO Remove or update the sed line when we update dotnet version.
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
@ -26,7 +24,7 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin"
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
FROM debian:stretch-slim-arm64v8
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
@ -36,9 +34,11 @@ RUN apt-get update \
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/bin/ffmpeg
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
"--ffmpeg", "/usr/bin/ffmpeg"]

View File

@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using Emby.Dlna.Main;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
@ -108,12 +109,13 @@ namespace Emby.Dlna.Api
public class DlnaServerService : IService, IRequiresRequest
{
private readonly IDlnaManager _dlnaManager;
private const string XMLContentType = "text/xml; charset=UTF-8";
private readonly IDlnaManager _dlnaManager;
private readonly IHttpResultFactory _resultFactory;
private readonly IServerConfigurationManager _configurationManager;
public IRequest Request { get; set; }
private IHttpResultFactory _resultFactory;
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
@ -121,10 +123,14 @@ namespace Emby.Dlna.Api
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory)
public DlnaServerService(
IDlnaManager dlnaManager,
IHttpResultFactory httpResultFactory,
IServerConfigurationManager configurationManager)
{
_dlnaManager = dlnaManager;
_resultFactory = httpResultFactory;
_configurationManager = configurationManager;
}
private string GetHeader(string name)
@ -205,19 +211,32 @@ namespace Emby.Dlna.Api
var pathInfo = Parse(Request.PathInfo);
var first = pathInfo[0];
string baseUrl = _configurationManager.Configuration.BaseUrl;
// backwards compatibility
// TODO: Work out what this is doing.
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) ||
string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase))
if (baseUrl.Length == 0)
{
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|| string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
{
index++;
}
}
else if (string.Equals(first, baseUrl.Remove(0, 1)))
{
index++;
var second = pathInfo[1];
if (string.Equals(second, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|| string.Equals(second, "emby", StringComparison.OrdinalIgnoreCase))
{
index++;
}
}
return pathInfo[index];
}
private List<string> Parse(string pathUri)
private static string[] Parse(string pathUri)
{
var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
@ -231,7 +250,7 @@ namespace Emby.Dlna.Api
var args = pathInfo.Split('/');
return args.Skip(1).ToList();
return args.Skip(1).ToArray();
}
public object Get(GetIcon request)

View File

@ -12,7 +12,7 @@
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

View File

@ -160,7 +160,7 @@ namespace Emby.Dlna.PlayTo
uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null);
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null);
var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();

View File

@ -4,8 +4,6 @@ using System.Linq;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
using Rssdp;
using Rssdp.Infrastructure;
@ -15,13 +13,14 @@ namespace Emby.Dlna.Ssdp
{
private bool _disposed;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
private int _listenerCount;
private object _syncLock = new object();
/// <inheritdoc />
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered
{
add
@ -31,6 +30,7 @@ namespace Emby.Dlna.Ssdp
_listenerCount++;
DeviceDiscoveredInternal += value;
}
StartInternal();
}
remove
@ -43,21 +43,16 @@ namespace Emby.Dlna.Ssdp
}
}
/// <inheritdoc />
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
private SsdpDeviceLocator _deviceLocator;
private readonly ISocketFactory _socketFactory;
private ISsdpCommunicationsServer _commsServer;
public DeviceDiscovery(
ILoggerFactory loggerFactory,
IServerConfigurationManager config,
ISocketFactory socketFactory)
public DeviceDiscovery(IServerConfigurationManager config)
{
_logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery));
_config = config;
_socketFactory = socketFactory;
}
// Call this method from somewhere in your code to start the search.
@ -82,8 +77,8 @@ namespace Emby.Dlna.Ssdp
//_DeviceLocator.NotificationFilter = "upnp:rootdevice";
// Connect our event handler so we process devices as they are found
_deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
_deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
_deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;
_deviceLocator.DeviceUnavailable += OnDeviceLocatorDeviceUnavailable;
var dueTime = TimeSpan.FromSeconds(5);
var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
@ -94,7 +89,7 @@ namespace Emby.Dlna.Ssdp
}
// Process each found device in the event handler
void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e)
private void OnDeviceLocatorDeviceAvailable(object sender, DeviceAvailableEventArgs e)
{
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
@ -115,7 +110,7 @@ namespace Emby.Dlna.Ssdp
DeviceDiscoveredInternal?.Invoke(this, args);
}
private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
private void OnDeviceLocatorDeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
{
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@ -17,9 +17,4 @@
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
<PropertyGroup>
<!-- We need at least C# 7.1 for the "default literal" feature-->
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>

View File

@ -3,7 +3,7 @@ using System;
namespace Emby.Naming.AudioBook
{
/// <summary>
/// Represents a single video file
/// Represents a single video file.
/// </summary>
public class AudioBookFileInfo : IComparable<AudioBookFileInfo>
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Emby.Naming.AudioBook
{
/// <summary>
/// Represents a complete video, including all parts and subtitles
/// Represents a complete video, including all parts and subtitles.
/// </summary>
public class AudioBookInfo
{

View File

@ -311,6 +311,14 @@ namespace Emby.Naming.Common
}
},
// This isn't a Kodi naming rule, but the expression below causes false positives,
// so we make sure this one gets tested first.
// "Foo Bar 889"
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>(\w+\s*?)*)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
{
IsNamed = true
},
new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")
{
SupportsAbsoluteEpisodeNumbers = true
@ -328,28 +336,33 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$")
                // [bar] Foo - 1 [baz]
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>(\w+\s*?)+?)[-\s_]+(?<epnumber>\d+).*$")
{
IsNamed = true
},
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d+)[xX](?<epnumber>\d+)[^\\\/]*$")
{
IsNamed = true
},
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d+)[x,X]?[eE](?<epnumber>\d+)[^\\\/]*$")
{
IsNamed = true
},
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$")
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d+))[^\\\/]*$")
{
IsNamed = true
},
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d+)[^\\\/]*$")
{
IsNamed = true
},
// "01.avi"
new EpisodeExpression(@".*[\\\/](?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.\w+$")
new EpisodeExpression(@".*[\\\/](?<epnumber>\d+)(-(?<endingepnumber>\d+))*\.\w+$")
{
IsOptimistic = true,
IsNamed = true
@ -654,9 +667,9 @@ namespace Emby.Naming.Common
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
}.Select(i => new EpisodeExpression(i)
{
IsNamed = true
}).ToArray();
{
IsNamed = true
}).ToArray();
VideoFileExtensions = extensions
.Distinct(StringComparer.OrdinalIgnoreCase)

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
@ -18,14 +19,14 @@
<PackageId>Jellyfin.Naming</PackageId>
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

View File

@ -25,7 +25,7 @@ namespace Emby.Naming.TV
}
/// <summary>
/// A season folder must contain one of these somewhere in the name
/// A season folder must contain one of these somewhere in the name.
/// </summary>
private static readonly string[] _seasonFolderNames =
{
@ -124,7 +124,7 @@ namespace Emby.Naming.TV
}
/// <summary>
/// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel")
/// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel").
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns>

View File

@ -8,7 +8,7 @@ using Emby.Naming.Common;
namespace Emby.Naming.Video
{
/// <summary>
/// http://kodi.wiki/view/Advancedsettings.xml#video
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
/// </summary>
public class CleanDateTimeParser
{

View File

@ -1,7 +1,7 @@
namespace Emby.Naming.Video
{
/// <summary>
/// Represents a single video file
/// Represents a single video file.
/// </summary>
public class VideoFileInfo
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Emby.Naming.Video
{
/// <summary>
/// Represents a complete video, including all parts and subtitles
/// Represents a complete video, including all parts and subtitles.
/// </summary>
public class VideoInfo
{

View File

@ -41,7 +41,7 @@ namespace Emby.Naming.Video
/// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
/// <param name="parseName">Whether or not the name should be parsed for info</param>
/// <returns>VideoFileInfo.</returns>
/// <exception cref="ArgumentNullException">path</exception>
/// <exception cref="ArgumentNullException"><c>path</c> is <c>null</c>.</exception>
public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
{
if (string.IsNullOrEmpty(path))

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

View File

@ -14,7 +14,7 @@
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@ -22,7 +22,7 @@
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
</ItemGroup>

View File

@ -616,8 +616,8 @@ namespace Emby.Server.Implementations.Activity
/// <summary>
/// Constructs a string description of a time-span value.
/// </summary>
/// <param name="value">The value of this item</param>
/// <param name="description">The name of this item (singular form)</param>
/// <param name="value">The value of this item.</param>
/// <param name="description">The name of this item (singular form).</param>
private static string CreateValueString(int value, string description)
{
return string.Format(

View File

@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
@ -16,7 +15,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.AppBase
{
/// <summary>
/// Class BaseConfigurationManager
/// Class BaseConfigurationManager.
/// </summary>
public abstract class BaseConfigurationManager : IConfigurationManager
{
@ -35,7 +34,7 @@ namespace Emby.Server.Implementations.AppBase
/// <summary>
/// The _configuration sync lock.
/// </summary>
private object _configurationSyncLock = new object();
private readonly object _configurationSyncLock = new object();
/// <summary>
/// The _configuration.
@ -48,7 +47,7 @@ namespace Emby.Server.Implementations.AppBase
/// <param name="applicationPaths">The application paths.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="xmlSerializer">The XML serializer.</param>
/// <param name="fileSystem">The file system</param>
/// <param name="fileSystem">The file system.</param>
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
{
CommonApplicationPaths = applicationPaths;
@ -85,6 +84,7 @@ namespace Emby.Server.Implementations.AppBase
/// </summary>
/// <value>The logger.</value>
protected ILogger Logger { get; private set; }
/// <summary>
/// Gets the XML serializer.
/// </summary>
@ -92,23 +92,39 @@ namespace Emby.Server.Implementations.AppBase
protected IXmlSerializer XmlSerializer { get; private set; }
/// <summary>
/// Gets or sets the application paths.
/// Gets the application paths.
/// </summary>
/// <value>The application paths.</value>
public IApplicationPaths CommonApplicationPaths { get; private set; }
/// <summary>
/// Gets the system configuration
/// Gets or sets the system configuration.
/// </summary>
/// <value>The configuration.</value>
public BaseApplicationConfiguration CommonConfiguration
{
get
{
// Lazy load
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
return _configuration;
if (_configurationLoaded)
{
return _configuration;
}
lock (_configurationSyncLock)
{
if (_configurationLoaded)
{
return _configuration;
}
_configuration = (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer);
_configurationLoaded = true;
return _configuration;
}
}
protected set
{
_configuration = value;
@ -158,7 +174,7 @@ namespace Emby.Server.Implementations.AppBase
/// Replaces the configuration.
/// </summary>
/// <param name="newConfiguration">The new configuration.</param>
/// <exception cref="ArgumentNullException">newConfiguration</exception>
/// <exception cref="ArgumentNullException"><c>newConfiguration</c> is <c>null</c>.</exception>
public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
{
if (newConfiguration == null)
@ -201,7 +217,7 @@ namespace Emby.Server.Implementations.AppBase
cachePath = CommonConfiguration.CachePath;
}
Logger.LogInformation("Setting cache path to " + cachePath);
Logger.LogInformation("Setting cache path: {Path}", cachePath);
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
}
@ -209,7 +225,7 @@ namespace Emby.Server.Implementations.AppBase
/// Replaces the cache path.
/// </summary>
/// <param name="newConfig">The new configuration.</param>
/// <exception cref="DirectoryNotFoundException"></exception>
/// <exception cref="DirectoryNotFoundException">The new cache path doesn't exist.</exception>
private void ValidateCachePath(BaseApplicationConfiguration newConfig)
{
var newPath = newConfig.CachePath;
@ -220,7 +236,7 @@ namespace Emby.Server.Implementations.AppBase
// Validate
if (!Directory.Exists(newPath))
{
throw new FileNotFoundException(
throw new DirectoryNotFoundException(
string.Format(
CultureInfo.InvariantCulture,
"{0} does not exist.",
@ -299,8 +315,7 @@ namespace Emby.Server.Implementations.AppBase
throw new ArgumentException("Expected configuration type is " + configurationType.Name);
}
var validatingStore = configurationStore as IValidatingConfiguration;
if (validatingStore != null)
if (configurationStore is IValidatingConfiguration validatingStore)
{
var currentConfiguration = GetConfiguration(key);

View File

@ -6,13 +6,13 @@ using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase
{
/// <summary>
/// Class ConfigurationHelper
/// Class ConfigurationHelper.
/// </summary>
public static class ConfigurationHelper
{
/// <summary>
/// Reads an xml configuration file from the file system
/// It will immediately re-serialize and save if new serialization data is available due to property changes
/// It will immediately re-serialize and save if new serialization data is available due to property changes.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="path">The path.</param>

View File

@ -88,7 +88,6 @@ using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
@ -110,9 +109,8 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using ServiceStack;
using Microsoft.OpenApi.Models;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Server.Implementations
@ -232,7 +230,25 @@ namespace Emby.Server.Implementations
}
}
protected IServiceProvider _serviceProvider;
/// <summary>
/// Gets or sets the service provider.
/// </summary>
public IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// Gets the http port for the webhost.
/// </summary>
public int HttpPort { get; private set; }
/// <summary>
/// Gets the https port for the webhost.
/// </summary>
public int HttpsPort { get; private set; }
/// <summary>
/// Gets the content root for the webhost.
/// </summary>
public string ContentRoot { get; private set; }
/// <summary>
/// Gets the server configuration manager.
@ -321,7 +337,7 @@ namespace Emby.Server.Implementations
private readonly IConfiguration _configuration;
/// <summary>
/// Gets or sets the installation manager.
/// Gets the installation manager.
/// </summary>
/// <value>The installation manager.</value>
protected IInstallationManager InstallationManager { get; private set; }
@ -362,7 +378,7 @@ namespace Emby.Server.Implementations
{
_configuration = configuration;
XmlSerializer = new MyXmlSerializer(fileSystem, loggerFactory);
XmlSerializer = new MyXmlSerializer();
NetworkManager = networkManager;
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
@ -410,13 +426,17 @@ namespace Emby.Server.Implementations
_validAddressResults.Clear();
}
public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
/// <inheritdoc />
public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version;
/// <inheritdoc />
public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
/// <summary>
/// Gets the current application user agent.
/// </summary>
/// <value>The application user agent.</value>
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion;
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString;
/// <summary>
/// Gets the email address for use within a comment section of a user agent field.
@ -452,20 +472,20 @@ namespace Emby.Server.Implementations
public string Name => ApplicationProductName;
/// <summary>
/// Creates an instance of type and resolves all constructor dependencies
/// 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)
=> ActivatorUtilities.CreateInstance(_serviceProvider, type);
=> ActivatorUtilities.CreateInstance(ServiceProvider, type);
/// <summary>
/// Creates an instance of type and resolves all constructor dependencies
/// Creates an instance of type and resolves all constructor dependencies.
/// </summary>
/// /// <typeparam name="T">The type.</typeparam>
/// <returns>T.</returns>
public T CreateInstance<T>()
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
=> ActivatorUtilities.CreateInstance<T>(ServiceProvider);
/// <summary>
/// Creates the instance safe.
@ -477,7 +497,7 @@ namespace Emby.Server.Implementations
try
{
Logger.LogDebug("Creating instance of {Type}", type);
return ActivatorUtilities.CreateInstance(_serviceProvider, type);
return ActivatorUtilities.CreateInstance(ServiceProvider, type);
}
catch (Exception ex)
{
@ -491,12 +511,12 @@ namespace Emby.Server.Implementations
/// </summary>
/// <typeparam name="T">The type</typeparam>
/// <returns>``0.</returns>
public T Resolve<T>() => _serviceProvider.GetService<T>();
public T Resolve<T>() => ServiceProvider.GetService<T>();
/// <summary>
/// Gets the export types.
/// </summary>
/// <typeparam name="T">The type</typeparam>
/// <typeparam name="T">The type.</typeparam>
/// <returns>IEnumerable{Type}.</returns>
public IEnumerable<Type> GetExportTypes<T>()
{
@ -508,11 +528,12 @@ namespace Emby.Server.Implementations
/// <inheritdoc />
public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
{
// Convert to list so this isn't executed for each iteration
var parts = GetExportTypes<T>()
.Select(CreateInstanceSafe)
.Where(i => i != null)
.Cast<T>()
.ToList(); // Convert to list so this isn't executed for each iteration
.ToList();
if (manageLifetime)
{
@ -607,77 +628,14 @@ namespace Emby.Server.Implementations
await RegisterResources(serviceCollection).ConfigureAwait(false);
FindParts();
string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
if (string.IsNullOrEmpty(contentRoot))
ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
if (string.IsNullOrEmpty(ContentRoot))
{
contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
}
var host = new WebHostBuilder()
.UseKestrel(options =>
{
var addresses = ServerConfigurationManager
.Configuration
.LocalNetworkAddresses
.Select(NormalizeConfiguredLocalAddress)
.Where(i => i != null)
.ToList();
if (addresses.Any())
{
foreach (var address in addresses)
{
Logger.LogInformation("Kestrel listening on {ipaddr}", address);
options.Listen(address, HttpPort);
if (EnableHttps && Certificate != null)
{
options.Listen(address, HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
}
}
}
else
{
Logger.LogInformation("Kestrel listening on all interfaces");
options.ListenAnyIP(HttpPort);
if (EnableHttps && Certificate != null)
{
options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
}
}
})
.UseContentRoot(contentRoot)
.ConfigureServices(services =>
{
services.AddResponseCompression();
services.AddHttpContextAccessor();
})
.Configure(app =>
{
app.UseWebSockets();
app.UseResponseCompression();
// TODO app.UseMiddleware<WebSocketMiddleware>();
app.Use(ExecuteWebsocketHandlerAsync);
app.Use(ExecuteHttpHandlerAsync);
})
.Build();
try
{
await host.StartAsync().ConfigureAwait(false);
}
catch
{
Logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again.");
throw;
ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
}
}
private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
{
if (!context.WebSockets.IsWebSocketRequest)
{
@ -688,7 +646,7 @@ namespace Emby.Server.Implementations
await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
}
private async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
{
if (context.WebSockets.IsWebSocketRequest)
{
@ -749,7 +707,8 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
var cryptoProvider = new CryptographyProvider();
serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider);
SocketFactory = new SocketFactory();
serviceCollection.AddSingleton(SocketFactory);
@ -788,7 +747,17 @@ namespace Emby.Server.Implementations
_userRepository = GetUserRepository();
UserManager = new UserManager(LoggerFactory.CreateLogger<UserManager>(), _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
UserManager = new UserManager(
LoggerFactory.CreateLogger<UserManager>(),
_userRepository,
XmlSerializer,
NetworkManager,
() => ImageProcessor,
() => DtoService,
this,
JsonSerializer,
FileSystemManager,
cryptoProvider);
serviceCollection.AddSingleton(UserManager);
@ -866,8 +835,7 @@ namespace Emby.Server.Implementations
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
serviceCollection.AddSingleton(NotificationManager);
serviceCollection.AddSingleton<IDeviceDiscovery>(
new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
serviceCollection.AddSingleton(ChapterManager);
@ -896,7 +864,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager);
AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
serviceCollection.AddSingleton(AuthService);
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
@ -906,7 +874,7 @@ namespace Emby.Server.Implementations
_displayPreferencesRepository.Initialize();
var userDataRepo = new SqliteUserDataRepository(LoggerFactory, ApplicationPaths);
var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger<SqliteUserDataRepository>(), ApplicationPaths);
SetStaticProperties();
@ -915,8 +883,6 @@ namespace Emby.Server.Implementations
((UserDataManager)UserDataManager).Repository = userDataRepo;
ItemRepository.Initialize(userDataRepo, UserManager);
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
_serviceProvider = serviceCollection.BuildServiceProvider();
}
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
@ -1007,7 +973,7 @@ namespace Emby.Server.Implementations
}
/// <summary>
/// Dirty hacks
/// Dirty hacks.
/// </summary>
private void SetStaticProperties()
{
@ -1073,9 +1039,9 @@ namespace Emby.Server.Implementations
/// <summary>
/// Finds the parts.
/// </summary>
protected void FindParts()
public void FindParts()
{
InstallationManager = _serviceProvider.GetService<IInstallationManager>();
InstallationManager = ServiceProvider.GetService<IInstallationManager>();
InstallationManager.PluginInstalled += PluginInstalled;
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
@ -1204,7 +1170,7 @@ namespace Emby.Server.Implementations
private CertificateInfo CertificateInfo { get; set; }
protected X509Certificate2 Certificate { get; private set; }
public X509Certificate2 Certificate { get; private set; }
private IEnumerable<string> GetUrlPrefixes()
{
@ -1415,17 +1381,18 @@ namespace Emby.Server.Implementations
/// <summary>
/// Gets the system status.
/// </summary>
/// <param name="cancellationToken">The cancellation token</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>SystemInfo.</returns>
public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
{
var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
var transcodingTempPath = ConfigurationManager.GetTranscodePath();
return new SystemInfo
{
HasPendingRestart = HasPendingRestart,
IsShuttingDown = IsShuttingDown,
Version = ApplicationVersion,
Version = ApplicationVersionString,
WebSocketPortNumber = HttpPort,
CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
Id = SystemId,
@ -1443,7 +1410,7 @@ namespace Emby.Server.Implementations
CanSelfRestart = CanSelfRestart,
CanLaunchWebBrowser = CanLaunchWebBrowser,
HasUpdateAvailable = HasUpdateAvailable,
TranscodingTempPath = ApplicationPaths.TranscodingTempPath,
TranscodingTempPath = transcodingTempPath,
ServerName = FriendlyName,
LocalAddress = localAddress,
SupportsLibraryMonitor = true,
@ -1465,7 +1432,7 @@ namespace Emby.Server.Implementations
return new PublicSystemInfo
{
Version = ApplicationVersion,
Version = ApplicationVersionString,
ProductName = ApplicationProductName,
Id = SystemId,
OperatingSystem = OperatingSystem.Id.ToString(),
@ -1588,7 +1555,7 @@ namespace Emby.Server.Implementations
return resultList;
}
private IPAddress NormalizeConfiguredLocalAddress(string address)
public IPAddress NormalizeConfiguredLocalAddress(string address)
{
var index = address.Trim('/').IndexOf('/');
@ -1664,10 +1631,6 @@ namespace Emby.Server.Implementations
? Environment.MachineName
: ServerConfigurationManager.Configuration.ServerName;
public int HttpPort { get; private set; }
public int HttpsPort { get; private set; }
/// <summary>
/// Shuts down.
/// </summary>
@ -1730,7 +1693,7 @@ namespace Emby.Server.Implementations
/// dns is prefixed with a valid Uri prefix.
/// </summary>
/// <param name="externalDns">The external dns prefix to get the hostname of.</param>
/// <returns>The hostname in <paramref name="externalDns"/></returns>
/// <returns>The hostname in <paramref name="externalDns"/>.</returns>
private static string GetHostnameFromExternalDns(string externalDns)
{
if (string.IsNullOrEmpty(externalDns))
@ -1844,6 +1807,7 @@ namespace Emby.Server.Implementations
internal class CertificateInfo
{
public string Path { get; set; }
public string Password { get; set; }
}
}

View File

@ -470,10 +470,10 @@ namespace Emby.Server.Implementations.Channels
_libraryManager.CreateItem(item, null);
}
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = !isNew && forceUpdate
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);
return item;
}
@ -636,7 +636,7 @@ namespace Emby.Server.Implementations.Channels
private async Task RefreshLatestChannelItems(IChannel channel, CancellationToken cancellationToken)
{
var internalChannel = await GetChannel(channel, cancellationToken);
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
var query = new InternalItemsQuery();
query.Parent = internalChannel;
@ -1156,7 +1156,7 @@ namespace Emby.Server.Implementations.Channels
if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
{
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal);
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}
return item;

View File

@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections
// This could cause it to get re-resolved as a plain folder
var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
var parentFolder = GetCollectionsFolder(true).Result;
var parentFolder = GetCollectionsFolder(true).GetAwaiter().GetResult();
if (parentFolder == null)
{
@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Collections
if (options.ItemIdList.Length > 0)
{
AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
// The initial adding of items is going to create a local metadata file
// This will cause internet metadata to be skipped as a result
@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Collections
}
else
{
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.High);
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
}
CollectionCreated?.Invoke(this, new CollectionCreatedEventArgs
@ -178,12 +178,12 @@ namespace Emby.Server.Implementations.Collections
public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
{
AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
}
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
{
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
}
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.Collections
}
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = true
}, RefreshPriority.High);

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using Emby.Server.Implementations.AppBase;
using MediaBrowser.Common.Configuration;
@ -14,7 +13,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Configuration
{
/// <summary>
/// Class ServerConfigurationManager
/// Class ServerConfigurationManager.
/// </summary>
public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager
{
@ -62,13 +61,6 @@ namespace Emby.Server.Implementations.Configuration
base.OnConfigurationUpdated();
}
public override void AddParts(IEnumerable<IConfigurationFactory> factories)
{
base.AddParts(factories);
UpdateTranscodePath();
}
/// <summary>
/// Updates the metadata path.
/// </summary>
@ -84,28 +76,6 @@ namespace Emby.Server.Implementations.Configuration
}
}
/// <summary>
/// Updates the transcoding temporary path.
/// </summary>
private void UpdateTranscodePath()
{
var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding");
((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ?
null :
Path.Combine(encodingConfig.TranscodingTempPath, "transcodes");
}
protected override void OnNamedConfigurationUpdated(string key, object configuration)
{
base.OnNamedConfigurationUpdated(key, configuration);
if (string.Equals(key, "encoding", StringComparison.OrdinalIgnoreCase))
{
UpdateTranscodePath();
}
}
/// <summary>
/// Replaces the configuration.
/// </summary>
@ -123,7 +93,6 @@ namespace Emby.Server.Implementations.Configuration
base.ReplaceConfiguration(newConfiguration);
}
/// <summary>
/// Validates the SSL certificate.
/// </summary>

View File

@ -30,6 +30,9 @@ namespace Emby.Server.Implementations.Cryptography
private bool _disposed = false;
/// <summary>
/// Initializes a new instance of the <see cref="CryptographyProvider"/> class.
/// </summary>
public CryptographyProvider()
{
// FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
@ -59,12 +62,6 @@ namespace Emby.Server.Implementations.Cryptography
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
}
public byte[] ComputeHash(string hashMethod, byte[] bytes)
=> ComputeHash(hashMethod, bytes, Array.Empty<byte>());
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
=> ComputeHash(DefaultHashMethod, bytes);
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
{
if (hashMethod == DefaultHashMethod)
@ -90,7 +87,6 @@ namespace Emby.Server.Implementations.Cryptography
}
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
}
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)

View File

@ -110,8 +110,8 @@ namespace Emby.Server.Implementations.Data
using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)"))
{
statement.TryBind("@id", displayPreferences.Id.ToGuidBlob());
statement.TryBind("@userId", userId.ToGuidBlob());
statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray());
statement.TryBind("@userId", userId.ToByteArray());
statement.TryBind("@client", client);
statement.TryBind("@data", serialized);
@ -170,8 +170,8 @@ namespace Emby.Server.Implementations.Data
{
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
{
statement.TryBind("@id", guidId.ToGuidBlob());
statement.TryBind("@userId", userId.ToGuidBlob());
statement.TryBind("@id", guidId.ToByteArray());
statement.TryBind("@userId", userId.ToByteArray());
statement.TryBind("@client", client);
foreach (var row in statement.ExecuteQuery())
@ -200,7 +200,7 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection(true))
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
{
statement.TryBind("@userId", userId.ToGuidBlob());
statement.TryBind("@userId", userId.ToByteArray());
foreach (var row in statement.ExecuteQuery())
{

View File

@ -9,6 +9,47 @@ namespace Emby.Server.Implementations.Data
{
public static class SqliteExtensions
{
private const string DatetimeFormatUtc = "yyyy-MM-dd HH:mm:ss.FFFFFFFK";
private const string DatetimeFormatLocal = "yyyy-MM-dd HH:mm:ss.FFFFFFF";
/// <summary>
/// An array of ISO-8601 DateTime formats that we support parsing.
/// </summary>
private static readonly string[] _datetimeFormats = new string[]
{
"THHmmssK",
"THHmmK",
"HH:mm:ss.FFFFFFFK",
"HH:mm:ssK",
"HH:mmK",
DatetimeFormatUtc,
"yyyy-MM-dd HH:mm:ssK",
"yyyy-MM-dd HH:mmK",
"yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
"yyyy-MM-ddTHH:mmK",
"yyyy-MM-ddTHH:mm:ssK",
"yyyyMMddHHmmssK",
"yyyyMMddHHmmK",
"yyyyMMddTHHmmssFFFFFFFK",
"THHmmss",
"THHmm",
"HH:mm:ss.FFFFFFF",
"HH:mm:ss",
"HH:mm",
DatetimeFormatLocal,
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm",
"yyyy-MM-ddTHH:mm:ss.FFFFFFF",
"yyyy-MM-ddTHH:mm",
"yyyy-MM-ddTHH:mm:ss",
"yyyyMMddHHmmss",
"yyyyMMddHHmm",
"yyyyMMddTHHmmssFFFFFFF",
"yyyy-MM-dd",
"yyyyMMdd",
"yy-MM-dd"
};
public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries)
{
if (queries == null)
@ -22,16 +63,6 @@ namespace Emby.Server.Implementations.Data
});
}
public static byte[] ToGuidBlob(this string str)
{
return ToGuidBlob(new Guid(str));
}
public static byte[] ToGuidBlob(this Guid guid)
{
return guid.ToByteArray();
}
public static Guid ReadGuidFromBlob(this IResultSetValue result)
{
return new Guid(result.ToBlob());
@ -50,58 +81,16 @@ namespace Emby.Server.Implementations.Data
CultureInfo.InvariantCulture);
}
private static string GetDateTimeKindFormat(
DateTimeKind kind)
{
return (kind == DateTimeKind.Utc) ? _datetimeFormatUtc : _datetimeFormatLocal;
}
/// <summary>
/// An array of ISO-8601 DateTime formats that we support parsing.
/// </summary>
private static string[] _datetimeFormats = new string[] {
"THHmmssK",
"THHmmK",
"HH:mm:ss.FFFFFFFK",
"HH:mm:ssK",
"HH:mmK",
"yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (5). */
"yyyy-MM-dd HH:mm:ssK",
"yyyy-MM-dd HH:mmK",
"yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
"yyyy-MM-ddTHH:mmK",
"yyyy-MM-ddTHH:mm:ssK",
"yyyyMMddHHmmssK",
"yyyyMMddHHmmK",
"yyyyMMddTHHmmssFFFFFFFK",
"THHmmss",
"THHmm",
"HH:mm:ss.FFFFFFF",
"HH:mm:ss",
"HH:mm",
"yyyy-MM-dd HH:mm:ss.FFFFFFF", /* NOTE: Non-UTC default (19). */
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm",
"yyyy-MM-ddTHH:mm:ss.FFFFFFF",
"yyyy-MM-ddTHH:mm",
"yyyy-MM-ddTHH:mm:ss",
"yyyyMMddHHmmss",
"yyyyMMddHHmm",
"yyyyMMddTHHmmssFFFFFFF",
"yyyy-MM-dd",
"yyyyMMdd",
"yy-MM-dd"
};
private static string _datetimeFormatUtc = _datetimeFormats[5];
private static string _datetimeFormatLocal = _datetimeFormats[19];
private static string GetDateTimeKindFormat(DateTimeKind kind)
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
public static DateTime ReadDateTime(this IResultSetValue result)
{
var dateText = result.ToString();
return DateTime.ParseExact(
dateText, _datetimeFormats,
dateText,
_datetimeFormats,
DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.None).ToUniversalTime();
}
@ -139,7 +128,10 @@ namespace Emby.Server.Implementations.Data
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
{
var commandText = string.Format("attach @path as {0};", alias);
var commandText = string.Format(
CultureInfo.InvariantCulture,
"attach @path as {0};",
alias);
using (var statement = db.PrepareStatement(commandText))
{
@ -186,10 +178,7 @@ namespace Emby.Server.Implementations.Data
private static void CheckName(string name)
{
#if DEBUG
//if (!name.IndexOf("@", StringComparison.OrdinalIgnoreCase) != 0)
{
throw new Exception("Invalid param name: " + name);
}
throw new ArgumentException("Invalid param name: " + name, nameof(name));
#endif
}
@ -264,7 +253,7 @@ namespace Emby.Server.Implementations.Data
{
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
{
bindParam.Bind(value.ToGuidBlob());
bindParam.Bind(value.ToByteArray());
}
else
{
@ -392,8 +381,7 @@ namespace Emby.Server.Implementations.Data
}
}
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(
this IStatement This)
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
{
while (This.MoveNext())
{

View File

@ -27,7 +27,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
@ -548,7 +547,7 @@ namespace Emby.Server.Implementations.Data
{
using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
{
saveImagesStatement.TryBind("@Id", item.Id.ToGuidBlob());
saveImagesStatement.TryBind("@Id", item.Id.ToByteArray());
saveImagesStatement.TryBind("@Images", SerializeImages(item));
saveImagesStatement.MoveNext();
@ -658,12 +657,14 @@ namespace Emby.Server.Implementations.Data
private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement)
{
saveItemStatement.TryBind("@guid", item.Id);
saveItemStatement.TryBind("@type", item.GetType().FullName);
Type type = item.GetType();
if (TypeRequiresDeserialization(item.GetType()))
saveItemStatement.TryBind("@guid", item.Id);
saveItemStatement.TryBind("@type", type.FullName);
if (TypeRequiresDeserialization(type))
{
saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, _jsonOptions));
saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, type, _jsonOptions));
}
else
{
@ -1177,7 +1178,7 @@ namespace Emby.Server.Implementations.Data
{
if (id == Guid.Empty)
{
throw new ArgumentException(nameof(id), "Guid can't be empty");
throw new ArgumentException("Guid can't be empty", nameof(id));
}
CheckDisposed();
@ -1988,7 +1989,7 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(chapters));
}
var idBlob = id.ToGuidBlob();
var idBlob = id.ToByteArray();
using (var connection = GetConnection())
{
@ -3760,7 +3761,7 @@ namespace Emby.Server.Implementations.Data
if (statement != null)
{
statement.TryBind(paramName, personId.ToGuidBlob());
statement.TryBind(paramName, personId.ToByteArray());
}
index++;
}
@ -3971,7 +3972,7 @@ namespace Emby.Server.Implementations.Data
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId.ToGuidBlob());
statement.TryBind(paramName, artistId.ToByteArray());
}
index++;
}
@ -3990,7 +3991,7 @@ namespace Emby.Server.Implementations.Data
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId.ToGuidBlob());
statement.TryBind(paramName, artistId.ToByteArray());
}
index++;
}
@ -4009,7 +4010,7 @@ namespace Emby.Server.Implementations.Data
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId.ToGuidBlob());
statement.TryBind(paramName, artistId.ToByteArray());
}
index++;
}
@ -4028,7 +4029,7 @@ namespace Emby.Server.Implementations.Data
clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
if (statement != null)
{
statement.TryBind(paramName, albumId.ToGuidBlob());
statement.TryBind(paramName, albumId.ToByteArray());
}
index++;
}
@ -4047,7 +4048,7 @@ namespace Emby.Server.Implementations.Data
clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId.ToGuidBlob());
statement.TryBind(paramName, artistId.ToByteArray());
}
index++;
}
@ -4066,7 +4067,7 @@ namespace Emby.Server.Implementations.Data
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
if (statement != null)
{
statement.TryBind(paramName, genreId.ToGuidBlob());
statement.TryBind(paramName, genreId.ToByteArray());
}
index++;
}
@ -4137,7 +4138,7 @@ namespace Emby.Server.Implementations.Data
if (statement != null)
{
statement.TryBind(paramName, studioId.ToGuidBlob());
statement.TryBind(paramName, studioId.ToByteArray());
}
index++;
}
@ -4913,7 +4914,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
connection.RunInTransaction(db =>
{
var idBlob = id.ToGuidBlob();
var idBlob = id.ToByteArray();
// Delete people
ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob);
@ -5032,7 +5033,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
whereClauses.Add("ItemId=@ItemId");
if (statement != null)
{
statement.TryBind("@ItemId", query.ItemId.ToGuidBlob());
statement.TryBind("@ItemId", query.ItemId.ToByteArray());
}
}
if (!query.AppearsInItemId.Equals(Guid.Empty))
@ -5040,7 +5041,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)");
if (statement != null)
{
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidBlob());
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray());
}
}
var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
@ -5109,7 +5110,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
CheckDisposed();
var itemIdBlob = itemId.ToGuidBlob();
var itemIdBlob = itemId.ToByteArray();
// First delete
deleteAncestorsStatement.Reset();
@ -5143,7 +5144,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
var ancestorId = ancestorIds[i];
statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob());
statement.TryBind("@AncestorId" + index, ancestorId.ToByteArray());
statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture));
}
@ -5608,7 +5609,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
CheckDisposed();
var guidBlob = itemId.ToGuidBlob();
var guidBlob = itemId.ToByteArray();
// First delete
db.Execute("delete from ItemValues where ItemId=@Id", guidBlob);
@ -5632,10 +5633,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
if (isSubsequentRow)
{
insertText.Append(",");
insertText.Append(',');
}
insertText.AppendFormat("(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})", i.ToString(CultureInfo.InvariantCulture));
insertText.AppendFormat(
CultureInfo.InvariantCulture,
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})",
i);
isSubsequentRow = true;
}
@ -5688,7 +5692,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
connection.RunInTransaction(db =>
{
var itemIdBlob = itemId.ToGuidBlob();
var itemIdBlob = itemId.ToByteArray();
// First delete chapters
db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
@ -5807,7 +5811,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
using (var statement = PrepareStatement(connection, cmdText))
{
statement.TryBind("@ItemId", query.ItemId.ToGuidBlob());
statement.TryBind("@ItemId", query.ItemId.ToByteArray());
if (query.Type.HasValue)
{
@ -5849,7 +5853,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
connection.RunInTransaction(db =>
{
var itemIdBlob = id.ToGuidBlob();
var itemIdBlob = id.ToByteArray();
// First delete chapters
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
@ -15,23 +14,19 @@ namespace Emby.Server.Implementations.Data
public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository
{
public SqliteUserDataRepository(
ILoggerFactory loggerFactory,
ILogger<SqliteUserDataRepository> logger,
IApplicationPaths appPaths)
: base(loggerFactory.CreateLogger(nameof(SqliteUserDataRepository)))
: base(logger)
{
DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
}
/// <summary>
/// Gets the name of the repository
/// </summary>
/// <value>The name.</value>
/// <inheritdoc />
public string Name => "SQLite";
/// <summary>
/// Opens the connection to the database
/// Opens the connection to the database.
/// </summary>
/// <returns>Task.</returns>
public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection)
{
WriteLock.Dispose();
@ -97,7 +92,7 @@ namespace Emby.Server.Implementations.Data
continue;
}
statement.TryBind("@UserId", user.Id.ToGuidBlob());
statement.TryBind("@UserId", user.Id.ToByteArray());
statement.TryBind("@InternalUserId", user.InternalId);
statement.MoveNext();

View File

@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Data
{
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
{
statement.TryBind("@guid", user.Id.ToGuidBlob());
statement.TryBind("@guid", user.Id.ToByteArray());
statement.TryBind("@data", serialized);
statement.MoveNext();

View File

@ -130,7 +130,6 @@ namespace Emby.Server.Implementations.Devices
var session = _authRepo.Get(new AuthenticationInfoQuery
{
DeviceId = id
}).Items.FirstOrDefault();
var device = session == null ? null : ToDeviceInfo(session);

View File

@ -3,6 +3,7 @@
<ItemGroup>
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
<ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@ -10,7 +11,6 @@
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
<ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
<ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
@ -29,12 +29,15 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<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.6.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />
<PackageReference Include="Mono.Nat" Version="2.0.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
<PackageReference Include="sharpcompress" Version="0.24.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
@ -47,16 +50,12 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<!-- We need at least C# 7.3 to compare tuples-->
<LangVersion>latest</LangVersion>
</PropertyGroup>
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

View File

@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins;
@ -15,209 +14,134 @@ using Mono.Nat;
namespace Emby.Server.Implementations.EntryPoints
{
/// <summary>
/// Server entrypoint handling external port forwarding.
/// </summary>
public class ExternalPortForwarding : IServerEntryPoint
{
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly object _createdRulesLock = new object();
private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
private Timer _timer;
private string _lastConfigIdentifier;
private NatManager _natManager;
private bool _disposed = false;
public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient)
/// <summary>
/// Initializes a new instance of the <see cref="ExternalPortForwarding"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="appHost">The application host.</param>
/// <param name="config">The configuration manager.</param>
/// <param name="deviceDiscovery">The device discovery.</param>
public ExternalPortForwarding(
ILogger<ExternalPortForwarding> logger,
IServerApplicationHost appHost,
IServerConfigurationManager config,
IDeviceDiscovery deviceDiscovery)
{
_logger = loggerFactory.CreateLogger("PortMapper");
_logger = logger;
_appHost = appHost;
_config = config;
_deviceDiscovery = deviceDiscovery;
_httpClient = httpClient;
_config.ConfigurationUpdated += _config_ConfigurationUpdated1;
}
private void _config_ConfigurationUpdated1(object sender, EventArgs e)
{
_config_ConfigurationUpdated(sender, e);
}
private string _lastConfigIdentifier;
private string GetConfigIdentifier()
{
var values = new List<string>();
const char Separator = '|';
var config = _config.Configuration;
values.Add(config.EnableUPnP.ToString());
values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture));
values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture));
values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture));
values.Add(_appHost.EnableHttps.ToString());
values.Add((config.EnableRemoteAccess).ToString());
return string.Join("|", values.ToArray());
return new StringBuilder(32)
.Append(config.EnableUPnP).Append(Separator)
.Append(config.PublicPort).Append(Separator)
.Append(_appHost.HttpPort).Append(Separator)
.Append(_appHost.HttpsPort).Append(Separator)
.Append(_appHost.EnableHttps).Append(Separator)
.Append(config.EnableRemoteAccess).Append(Separator)
.ToString();
}
private async void _config_ConfigurationUpdated(object sender, EventArgs e)
private void OnConfigurationUpdated(object sender, EventArgs e)
{
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
{
DisposeNat();
await RunAsync();
Stop();
Start();
}
}
/// <inheritdoc />
public Task RunAsync()
{
if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess)
{
Start();
}
Start();
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
_config.ConfigurationUpdated += OnConfigurationUpdated;
return Task.CompletedTask;
}
private void Start()
{
_logger.LogDebug("Starting NAT discovery");
if (_natManager == null)
if (!_config.Configuration.EnableUPnP || !_config.Configuration.EnableRemoteAccess)
{
_natManager = new NatManager(_logger, _httpClient);
_natManager.DeviceFound += NatUtility_DeviceFound;
_natManager.StartDiscovery();
return;
}
_logger.LogDebug("Starting NAT discovery");
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
NatUtility.StartDiscovery();
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
_lastConfigIdentifier = GetConfigIdentifier();
}
private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
private void Stop()
{
if (_disposed)
{
return;
}
_logger.LogDebug("Stopping NAT discovery");
var info = e.Argument;
NatUtility.StopDiscovery();
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
_timer?.Dispose();
if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
// Filter device type
if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1)
{
return;
}
var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
if (info.Location == null)
{
return;
}
lock (_usnsHandled)
{
if (_usnsHandled.Contains(identifier))
{
return;
}
_usnsHandled.Add(identifier);
}
_logger.LogDebug("Found NAT device: " + identifier);
if (IPAddress.TryParse(info.Location.Host, out var address))
{
// The Handle method doesn't need the port
var endpoint = new IPEndPoint(address, info.Location.Port);
IPAddress localAddress = null;
try
{
var localAddressString = await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false);
if (Uri.TryCreate(localAddressString, UriKind.Absolute, out var uri))
{
localAddressString = uri.Host;
if (!IPAddress.TryParse(localAddressString, out localAddress))
{
return;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error");
return;
}
if (_disposed)
{
return;
}
// This should never happen, but the Handle method will throw ArgumentNullException if it does
if (localAddress == null)
{
return;
}
var natManager = _natManager;
if (natManager != null)
{
await natManager.Handle(localAddress, info, endpoint, NatProtocol.Upnp).ConfigureAwait(false);
}
}
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
}
private void ClearCreatedRules(object state)
{
lock (_createdRules)
lock (_createdRulesLock)
{
_createdRules.Clear();
}
lock (_usnsHandled)
{
_usnsHandled.Clear();
}
}
void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
if (_disposed)
{
return;
}
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
}
private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
{
try
{
var device = e.Device;
CreateRules(device);
}
catch
catch (Exception ex)
{
// Commenting out because users are reporting problems out of our control
//_logger.LogError(ex, "Error creating port forwarding rules");
_logger.LogError(ex, "Error creating port forwarding rules");
}
}
private List<string> _createdRules = new List<string>();
private List<string> _usnsHandled = new List<string>();
private async void CreateRules(INatDevice device)
{
if (_disposed)
@ -227,15 +151,13 @@ namespace Emby.Server.Implementations.EntryPoints
// On some systems the device discovered event seems to fire repeatedly
// This check will help ensure we're not trying to port map the same device over and over
var address = device.LocalAddress;
var address = device.DeviceEndpoint;
var addressString = address.ToString();
lock (_createdRules)
lock (_createdRulesLock)
{
if (!_createdRules.Contains(addressString))
if (!_createdRules.Contains(address))
{
_createdRules.Add(addressString);
_createdRules.Add(address);
}
else
{
@ -263,54 +185,43 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
private Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
{
_logger.LogDebug("Creating port map on local port {0} to public port {1} with device {2}", privatePort, publicPort, device.LocalAddress.ToString());
_logger.LogDebug(
"Creating port map on local port {0} to public port {1} with device {2}",
privatePort,
publicPort,
device.DeviceEndpoint);
return device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
{
Description = _appHost.Name
});
return device.CreatePortMapAsync(
new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
}
private bool _disposed = false;
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
DisposeNat();
Dispose(true);
GC.SuppressFinalize(this);
}
private void DisposeNat()
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <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)
{
_logger.LogDebug("Stopping NAT discovery");
if (_timer != null)
if (_disposed)
{
_timer.Dispose();
_timer = null;
return;
}
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
_config.ConfigurationUpdated -= OnConfigurationUpdated;
var natManager = _natManager;
Stop();
if (natManager != null)
{
_natManager = null;
_timer = null;
using (natManager)
{
try
{
natManager.StopDiscovery();
natManager.DeviceFound -= NatUtility_DeviceFound;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error stopping NAT Discovery");
}
}
}
_disposed = true;
}
}
}

View File

@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints
public class LibraryChangedNotifier : IServerEntryPoint
{
/// <summary>
/// The _library manager
/// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly ILogger _logger;
/// <summary>
/// The _library changed sync lock
/// The library changed sync lock.
/// </summary>
private readonly object _libraryChangedSyncLock = new object();
@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.EntryPoints
private Timer LibraryUpdateTimer { get; set; }
/// <summary>
/// The library update duration
/// The library update duration.
/// </summary>
private const int LibraryUpdateDuration = 30000;
@ -188,8 +188,11 @@ namespace Emby.Server.Implementations.EntryPoints
{
if (LibraryUpdateTimer == null)
{
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
Timeout.Infinite);
LibraryUpdateTimer = new Timer(
LibraryUpdateTimerCallback,
null,
LibraryUpdateDuration,
Timeout.Infinite);
}
else
{
@ -452,7 +455,7 @@ namespace Emby.Server.Implementations.EntryPoints
return new[] { item };
}
return new T[] { };
return Array.Empty<T>();
}
/// <summary>

View File

@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.EntryPoints
{
cancellationToken.ThrowIfCancellationRequested();
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false);
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
}
}

View File

@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.EntryPoints
{
try
{
await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None);
await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception)
{

View File

@ -325,7 +325,7 @@ namespace Emby.Server.Implementations.HttpClientManager
if (options.LogErrorResponseBody)
{
var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
string msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
_logger.LogError("HTTP request failed with message: {Message}", msg);
}

View File

@ -7,7 +7,6 @@ using System.Net.Sockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Configuration;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Services;
using MediaBrowser.Common.Extensions;
@ -16,11 +15,9 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@ -166,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer
{
OnReceive = ProcessWebSocketMessageReceived,
Url = e.Url,
QueryString = e.QueryString ?? new QueryCollection()
QueryString = e.QueryString
};
connection.Closed += OnConnectionClosed;
@ -539,6 +536,11 @@ namespace Emby.Server.Implementations.HttpServer
}
finally
{
if (httpRes.StatusCode >= 500)
{
_logger.LogDebug("Sending HTTP Response 500 in response to {Url}", urlToLog);
}
stopWatch.Stop();
var elapsed = stopWatch.Elapsed;
if (elapsed.TotalMilliseconds > 500)

View File

@ -460,7 +460,7 @@ namespace Emby.Server.Implementations.HttpServer
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
throw new ArgumentException("Path can't be empty.", nameof(options));
}
if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)

View File

@ -48,12 +48,14 @@ namespace Emby.Server.Implementations.HttpServer
public IDictionary<string, string> Headers => _options;
/// <summary>
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
/// Initializes a new instance of the <see cref="RangeRequestWriter" /> class.
/// </summary>
/// <param name="rangeHeader">The range header.</param>
/// <param name="contentLength">The content length.</param>
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <param name="logger">The logger instance.</param>
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
{
if (string.IsNullOrEmpty(contentType))

View File

@ -1,5 +1,6 @@
using System;
using System.Linq;
using Emby.Server.Implementations.SocketSharp;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@ -7,22 +8,27 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.HttpServer.Security
{
public class AuthService : IAuthService
{
private readonly ILogger<AuthService> _logger;
private readonly IAuthorizationContext _authorizationContext;
private readonly ISessionManager _sessionManager;
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
public AuthService(
ILogger<AuthService> logger,
IAuthorizationContext authorizationContext,
IServerConfigurationManager config,
ISessionManager sessionManager,
INetworkManager networkManager)
{
_logger = logger;
_authorizationContext = authorizationContext;
_config = config;
_sessionManager = sessionManager;
@ -34,7 +40,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
ValidateUser(request, authAttribtues);
}
private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
{
var req = new WebSocketSharpRequest(request, null, request.Path, _logger);
var user = ValidateUser(req, authAttributes);
return user;
}
private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
{
// This code is executed before the service
var auth = _authorizationContext.GetAuthorizationInfo(request);
@ -81,6 +94,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
request.RemoteIp,
user);
}
return user;
}
private void ValidateUserAccess(

View File

@ -2,11 +2,11 @@ using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Cryptography;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Cryptography;
using static MediaBrowser.Common.HexHelper;
namespace Emby.Server.Implementations.Library
{
@ -59,7 +59,10 @@ namespace Emby.Server.Implementations.Library
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
|| _cryptographyProvider.DefaultHashMethod == readyHash.Id)
{
byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.Salt);
byte[] calculatedHash = _cryptographyProvider.ComputeHash(
readyHash.Id,
passwordbytes,
readyHash.Salt);
if (calculatedHash.SequenceEqual(readyHash.Hash))
{
@ -122,7 +125,7 @@ namespace Emby.Server.Implementations.Library
{
return string.IsNullOrEmpty(user.EasyPassword)
? null
: ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
: Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash);
}
/// <summary>

View File

@ -1,7 +1,6 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Net;
namespace Emby.Server.Implementations.Library
{

View File

@ -519,7 +519,7 @@ namespace Emby.Server.Implementations.Library
}
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
=> ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent);
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
private BaseItem ResolvePath(
FileSystemMetadata fileInfo,
@ -1045,7 +1045,7 @@ namespace Emby.Server.Implementations.Library
await RootFolder.ValidateChildren(
new SimpleProgress<double>(),
cancellationToken,
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
recursive: false).ConfigureAwait(false);
await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
@ -1053,7 +1053,7 @@ namespace Emby.Server.Implementations.Library
await GetUserRootFolder().ValidateChildren(
new SimpleProgress<double>(),
cancellationToken,
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
recursive: false).ConfigureAwait(false);
// Quickly scan CollectionFolders for changes
@ -1074,7 +1074,7 @@ namespace Emby.Server.Implementations.Library
innerProgress.RegisterAction(pct => progress.Report(pct * .96));
// Now validate the entire media library
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: true).ConfigureAwait(false);
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
progress.Report(96);
@ -1899,7 +1899,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="cancellationToken">The cancellation token.</param>
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
UpdateItems(new[] { item }, parent, updateReason, cancellationToken);
}
/// <summary>
@ -2135,7 +2135,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal);
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}
return item;
@ -2175,7 +2175,6 @@ namespace Emby.Server.Implementations.Library
DisplayParentId = parentId
};
CreateItem(item, null);
isNew = true;
@ -2193,11 +2192,10 @@ namespace Emby.Server.Implementations.Library
{
_providerManagerFactory().QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
// Need to force save to increment DateLastSaved
ForceSave = true
},
RefreshPriority.Normal);
}
@ -2261,7 +2259,7 @@ namespace Emby.Server.Implementations.Library
{
_providerManagerFactory().QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
// Need to force save to increment DateLastSaved
ForceSave = true
@ -2338,7 +2336,7 @@ namespace Emby.Server.Implementations.Library
{
_providerManagerFactory().QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
// Need to force save to increment DateLastSaved
ForceSave = true
@ -2487,6 +2485,15 @@ namespace Emby.Server.Implementations.Library
{
episode.ParentIndexNumber = season.IndexNumber;
}
else
{
/*
Anime series don't generally have a season in their file name, however,
tvdb needs a season to correctly get the metadata.
Hence, a null season needs to be filled with something. */
//FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
episode.ParentIndexNumber = 1;
}
if (episode.ParentIndexNumber.HasValue)
{

View File

@ -134,12 +134,13 @@ namespace Emby.Server.Implementations.Library
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
{
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
{
EnableRemoteContentProbe = true,
MetadataRefreshMode = MediaBrowser.Controller.Providers.MetadataRefreshMode.FullRefresh
}, cancellationToken).ConfigureAwait(false);
await item.RefreshMetadata(
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
EnableRemoteContentProbe = true,
MetadataRefreshMode = MetadataRefreshMode.FullRefresh
},
cancellationToken).ConfigureAwait(false);
mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
}

View File

@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Library
if (string.IsNullOrEmpty(searchTerm))
{
throw new ArgumentNullException(nameof(searchTerm));
throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm));
}
searchTerm = searchTerm.Trim().RemoveDiacritics();

View File

@ -24,6 +24,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
@ -31,7 +32,6 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users;
using Microsoft.Extensions.Logging;
using static MediaBrowser.Common.HexHelper;
namespace Emby.Server.Implementations.Library
{
@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.Library
private readonly Func<IDtoService> _dtoServiceFactory;
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
private readonly ICryptoProvider _cryptoProvider;
private ConcurrentDictionary<Guid, User> _users;
@ -80,7 +81,8 @@ namespace Emby.Server.Implementations.Library
Func<IDtoService> dtoServiceFactory,
IServerApplicationHost appHost,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem)
IFileSystem fileSystem,
ICryptoProvider cryptoProvider)
{
_logger = logger;
_userRepository = userRepository;
@ -91,6 +93,7 @@ namespace Emby.Server.Implementations.Library
_appHost = appHost;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_cryptoProvider = cryptoProvider;
_users = null;
}
@ -179,12 +182,7 @@ namespace Emby.Server.Implementations.Library
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
}
/// <summary>
/// Gets a User by Id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>User.</returns>
/// <exception cref="ArgumentException"></exception>
/// <inheritdoc />
public User GetUserById(Guid id)
{
if (id == Guid.Empty)
@ -196,11 +194,7 @@ namespace Emby.Server.Implementations.Library
return user;
}
/// <summary>
/// Gets the user by identifier.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>User.</returns>
/// <inheritdoc />
public User GetUserById(string id)
=> GetUserById(new Guid(id));
@ -428,7 +422,6 @@ namespace Emby.Server.Implementations.Library
{
try
{
var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
: await provider.Authenticate(username, password).ConfigureAwait(false);
@ -475,24 +468,21 @@ namespace Emby.Server.Implementations.Library
if (!success
&& _networkManager.IsInLocalNetwork(remoteEndPoint)
&& user.Configuration.EnableLocalPassword)
&& user.Configuration.EnableLocalPassword
&& !string.IsNullOrEmpty(user.EasyPassword))
{
success = string.Equals(
GetLocalPasswordHash(user),
_defaultAuthenticationProvider.GetHashedString(user, password),
StringComparison.OrdinalIgnoreCase);
// Check easy password
var passwordHash = PasswordHash.Parse(user.EasyPassword);
var hash = _cryptoProvider.ComputeHash(
passwordHash.Id,
Encoding.UTF8.GetBytes(password),
passwordHash.Salt);
success = passwordHash.Hash.SequenceEqual(hash);
}
return (authenticationProvider, username, success);
}
private string GetLocalPasswordHash(User user)
{
return string.IsNullOrEmpty(user.EasyPassword)
? null
: ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
}
private void ResetInvalidLoginAttemptCount(User user)
{
user.Policy.InvalidLoginAttemptCount = 0;
@ -538,6 +528,8 @@ namespace Emby.Server.Implementations.Library
defaultName = "MyJellyfinUser";
}
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
var name = MakeValidUsername(defaultName);
var user = InstantiateNewUser(name);
@ -601,7 +593,7 @@ namespace Emby.Server.Implementations.Library
catch (Exception ex)
{
// Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
_logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name);
_logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {User}", user.Name);
}
}
@ -625,7 +617,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path);
_logger.LogError(ex, "Error getting {ImageType} image info for {ImagePath}", image.Type, image.Path);
return null;
}
}
@ -639,7 +631,7 @@ namespace Emby.Server.Implementations.Library
{
foreach (var user in Users)
{
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false);
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
}
}

View File

@ -42,6 +42,11 @@ namespace Emby.Server.Implementations.Library
{
var user = _userManager.GetUserById(query.UserId);
if (user == null)
{
throw new ArgumentException("User Id specified in the query does not exist.", nameof(query));
}
var folders = _libraryManager.GetUserRootFolder()
.GetChildren(user, true)
.OfType<Folder>()
@ -54,7 +59,7 @@ namespace Emby.Server.Implementations.Library
foreach (var folder in folders)
{
var collectionFolder = folder as ICollectionFolder;
var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
var folderViewType = collectionFolder?.CollectionType;
if (UserView.IsUserSpecific(folder))
{
@ -130,16 +135,11 @@ namespace Emby.Server.Implementations.Library
{
var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture));
if (index == -1)
if (index == -1
&& i is UserView view
&& view.DisplayParentId != Guid.Empty)
{
var view = i as UserView;
if (view != null)
{
if (!view.DisplayParentId.Equals(Guid.Empty))
{
index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
}
}
index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
}
return index == -1 ? int.MaxValue : index;

View File

@ -28,10 +28,11 @@ namespace Emby.Server.Implementations.Library.Validators
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// Initializes a new instance of the <see cref="ArtistsValidator" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;

View File

@ -10,17 +10,18 @@ namespace Emby.Server.Implementations.Library.Validators
public class GenresPostScanTask : ILibraryPostScanTask
{
/// <summary>
/// The _library manager
/// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// Initializes a new instance of the <see cref="GenresPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;

View File

@ -20,10 +20,11 @@ namespace Emby.Server.Implementations.Library.Validators
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;

View File

@ -11,16 +11,17 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
/// <summary>
/// Class PeopleValidator
/// Class PeopleValidator.
/// </summary>
public class PeopleValidator
{
/// <summary>
/// The _library manager
/// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// The _logger
/// The _logger.
/// </summary>
private readonly ILogger _logger;
@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators
{
var item = _libraryManager.GetPerson(person);
var options = new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
@ -96,12 +97,19 @@ namespace Emby.Server.Implementations.Library.Validators
foreach (var item in deadEntities)
{
_logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
_logger.LogInformation(
"Deleting dead {2} {0} {1}.",
item.Id.ToString("N", CultureInfo.InvariantCulture),
item.Name,
item.GetType().Name);
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
}, false);
_libraryManager.DeleteItem(
item,
new DeleteOptions
{
DeleteFileLocation = false
},
false);
}
progress.Report(100);

View File

@ -21,9 +21,11 @@ namespace Emby.Server.Implementations.Library.Validators
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">Th item repository.</param>
public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;

View File

@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (requiresRefresh)
{
await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None);
await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None).ConfigureAwait(false);
}
}
@ -1489,16 +1489,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
_logger.LogInformation("Refreshing recording parent {path}", item.Path);
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
{
RefreshPaths = new string[]
_providerManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
path,
Path.GetDirectoryName(path),
Path.GetDirectoryName(Path.GetDirectoryName(path))
}
}, RefreshPriority.High);
RefreshPaths = new string[]
{
path,
Path.GetDirectoryName(path),
Path.GetDirectoryName(Path.GetDirectoryName(path))
}
},
RefreshPriority.High);
}
}

View File

@ -501,7 +501,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public async Task<List<NameIdPair>> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken)
{
var token = await GetToken(info, cancellationToken);
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
var lineups = new List<NameIdPair>();
@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
{
var token = await GetToken(info, cancellationToken);
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(token))
{
@ -738,7 +738,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
httpOptions.RequestHeaders["token"] = token;
using (await _httpClient.SendAsync(httpOptions, "PUT"))
using (await _httpClient.SendAsync(httpOptions, "PUT").ConfigureAwait(false))
{
}
}
@ -750,7 +750,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
throw new ArgumentException("Listings Id required");
}
var token = await GetToken(info, cancellationToken);
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(token))
{
@ -833,7 +833,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
throw new Exception("ListingsId required");
}
var token = await GetToken(info, cancellationToken);
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(token))
{

View File

@ -1226,12 +1226,13 @@ namespace Emby.Server.Implementations.LiveTv
currentChannel.AddTag("Kids");
}
//currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
await currentChannel.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
{
ForceSave = true
}, cancellationToken).ConfigureAwait(false);
currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
await currentChannel.RefreshMetadata(
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = true
},
cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@ -1245,7 +1246,7 @@ namespace Emby.Server.Implementations.LiveTv
numComplete++;
double percent = numComplete / (double)allChannelsList.Count;
progress.Report(85 * percent + 15);
progress.Report((85 * percent) + 15);
}
progress.Report(100);
@ -1278,12 +1279,14 @@ namespace Emby.Server.Implementations.LiveTv
if (item != null)
{
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false,
DeleteFromExternalProvider = false
}, false);
_libraryManager.DeleteItem(
item,
new DeleteOptions
{
DeleteFileLocation = false,
DeleteFromExternalProvider = false
},
false);
}
}
@ -2301,8 +2304,10 @@ namespace Emby.Server.Implementations.LiveTv
if (provider == null)
{
throw new ResourceNotFoundException(
string.Format("Couldn't find provider of type: '{0}'", info.Type)
);
string.Format(
CultureInfo.InvariantCulture,
"Couldn't find provider of type: '{0}'",
info.Type));
}
await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);

View File

@ -38,8 +38,8 @@ namespace Emby.Server.Implementations.LiveTv
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[] {
return new[]
{
// Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
};

View File

@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
CancellationToken = cancellationToken,
BufferContent = false
}, HttpMethod.Get))
}, HttpMethod.Get).ConfigureAwait(false))
using (var stream = response.Content)
using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
{
@ -259,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
for (int i = 0; i < model.TunerCount; ++i)
{
var name = string.Format("Tuner {0}", i + 1);
var currentChannel = "none"; /// @todo Get current channel and map back to Station Id
var currentChannel = "none"; // @todo Get current channel and map back to Station Id
var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
tuners.Add(new LiveTvTunerInfo
@ -298,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
{
// TODO Need faster way to determine UDP vs HTTP
var channels = await GetChannels(info, true, cancellationToken);
var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo;
@ -582,11 +582,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
modelInfo.TunerCount,
FileSystem,
Logger,
Config.ApplicationPaths,
Config,
_appHost,
_networkManager,
_streamHelper);
}
var enableHttpStream = true;
@ -611,7 +610,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
FileSystem,
_httpClient,
Logger,
Config.ApplicationPaths,
Config,
_appHost,
_streamHelper);
}
@ -624,7 +623,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
modelInfo.TunerCount,
FileSystem,
Logger,
Config.ApplicationPaths,
Config,
_appHost,
_networkManager,
_streamHelper);

View File

@ -6,6 +6,7 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
@ -33,11 +34,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
int numTuners,
IFileSystem fileSystem,
ILogger logger,
IServerApplicationPaths appPaths,
IConfigurationManager configurationManager,
IServerApplicationHost appHost,
INetworkManager networkManager,
IStreamHelper streamHelper)
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
{
_appHost = appHost;
_networkManager = networkManager;

View File

@ -5,8 +5,8 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
@ -16,8 +16,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class LiveStream : ILiveStream
{
private readonly IConfigurationManager _configurationManager;
protected readonly IFileSystem FileSystem;
protected readonly IServerApplicationPaths AppPaths;
protected readonly IStreamHelper StreamHelper;
protected string TempFilePath;
@ -29,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
TunerHostInfo tuner,
IFileSystem fileSystem,
ILogger logger,
IServerApplicationPaths appPaths,
IConfigurationManager configurationManager,
IStreamHelper streamHelper)
{
OriginalMediaSource = mediaSource;
@ -44,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
TunerHostId = tuner.Id;
}
AppPaths = appPaths;
_configurationManager = configurationManager;
StreamHelper = streamHelper;
ConsumerCount = 1;
@ -68,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected void SetTempFilePath(string extension)
{
TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
TempFilePath = Path.Combine(_configurationManager.GetTranscodePath(), UniqueId + "." + extension);
}
public virtual Task Open(CancellationToken openCancellationToken)

View File

@ -114,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config, _appHost, _streamHelper);
}
}
return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths, _streamHelper);
return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper);
}
public async Task Validate(TunerHostInfo info)

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
IFileSystem fileSystem,
IHttpClient httpClient,
ILogger logger,
IServerApplicationPaths appPaths,
IConfigurationManager configurationManager,
IServerApplicationHost appHost,
IStreamHelper streamHelper)
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
{
_httpClient = httpClient;
_appHost = appHost;

View File

@ -0,0 +1,96 @@
{
"Artists": "Kunstenare",
"Channels": "Kanale",
"Folders": "Fouers",
"Favorites": "Gunstelinge",
"HeaderFavoriteShows": "Gunsteling Vertonings",
"ValueSpecialEpisodeName": "Spesiaal - {0}",
"HeaderAlbumArtists": "Album Kunstenaars",
"Books": "Boeke",
"HeaderNextUp": "Volgende",
"Movies": "Rolprente",
"Shows": "Program",
"HeaderContinueWatching": "Hou Aan Kyk",
"HeaderFavoriteEpisodes": "Gunsteling Episodes",
"Photos": "Fotos",
"Playlists": "Speellysse",
"HeaderFavoriteArtists": "Gunsteling Kunstenaars",
"HeaderFavoriteAlbums": "Gunsteling Albums",
"Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies",
"DeviceOnlineWithName": "{0} is verbind",
"DeviceOfflineWithName": "{0} het afgesluit",
"Collections": "Versamelings",
"Inherit": "Ontvang",
"HeaderLiveTV": "Live TV",
"Application": "Program",
"AppDeviceValues": "App: {0}, Toestel: {1}",
"VersionNumber": "Weergawe {0}",
"ValueHasBeenAddedToLibrary": "{0} is by jou media biblioteek bygevoeg",
"UserStoppedPlayingItemWithValues": "{0} het klaar {1} op {2} gespeel",
"UserStartedPlayingItemWithValues": "{0} is besig om {1} op {2} te speel",
"UserPolicyUpdatedWithName": "Gebruiker beleid is verander vir {0}",
"UserPasswordChangedWithName": "Gebruiker {0} se wagwoord is verander",
"UserOnlineFromDevice": "{0} is aanlyn van {1}",
"UserOfflineFromDevice": "{0} is ontkoppel van {1}",
"UserLockedOutWithName": "Gebruiker {0} is uitgesluit",
"UserDownloadingItemWithValues": "{0} is besig om {1} af te laai",
"UserDeletedWithName": "Gebruiker {0} is verwyder",
"UserCreatedWithName": "Gebruiker {0} is geskep",
"User": "Gebruiker",
"TvShows": "TV Programme",
"System": "Stelsel",
"SubtitlesDownloadedForItem": "Ondertitels afgelaai vir {0}",
"SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}",
"StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.",
"ServerNameNeedsToBeRestarted": "{0} moet herbegin word",
"ScheduledTaskStartedWithName": "{0} het begin",
"ScheduledTaskFailedWithName": "{0} het misluk",
"ProviderValue": "Voorsiener: {0}",
"PluginUpdatedWithName": "{0} was opgedateer",
"PluginUninstalledWithName": "{0} was verwyder",
"PluginInstalledWithName": "{0} is geïnstalleer",
"Plugin": "Inprop module",
"NotificationOptionVideoPlaybackStopped": "Video terugspeel het gestop",
"NotificationOptionVideoPlayback": "Video terugspeel het begin",
"NotificationOptionUserLockedOut": "Gebruiker uitgeslyt",
"NotificationOptionTaskFailed": "Geskeduleerde taak het misluk",
"NotificationOptionServerRestartRequired": "Bediener herbegin nodig",
"NotificationOptionPluginUpdateInstalled": "Nuwe inprop module geïnstalleer",
"NotificationOptionPluginUninstalled": "Inprop module verwyder",
"NotificationOptionPluginInstalled": "Inprop module geïnstalleer",
"NotificationOptionPluginError": "Inprop module het misluk",
"NotificationOptionNewLibraryContent": "Nuwe inhoud bygevoeg",
"NotificationOptionInstallationFailed": "Installering het misluk",
"NotificationOptionCameraImageUploaded": "Kamera foto is opgelaai",
"NotificationOptionAudioPlaybackStopped": "Oudio terugspeel het gestop",
"NotificationOptionAudioPlayback": "Oudio terugspeel het begin",
"NotificationOptionApplicationUpdateInstalled": "Nuwe program weergawe geïnstalleer",
"NotificationOptionApplicationUpdateAvailable": "Nuwe program weergawe beskikbaar",
"NewVersionIsAvailable": "'n Nuwe Jellyfin Bedienaar weergawe kan afgelaai word.",
"NameSeasonUnknown": "Seisoen Onbekend",
"NameSeasonNumber": "Seisoen {0}",
"NameInstallFailed": "{0} installering het misluk",
"MusicVideos": "Musiek videos",
"Music": "Musiek",
"MixedContent": "Gemengde inhoud",
"MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
"MessageNamedServerConfigurationUpdatedWithValue": "Bediener konfigurasie seksie {0} is opgedateer",
"MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
"MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
"Latest": "Nuutste",
"LabelRunningTimeValue": "Lopende tyd: {0}",
"LabelIpAddressValue": "IP adres: {0}",
"ItemRemovedWithName": "{0} is uit versameling verwyder",
"ItemAddedWithName": "{0} is in die versameling",
"HomeVideos": "Tuis opnames",
"HeaderRecordingGroups": "Groep Opnames",
"HeaderCameraUploads": "Kamera Oplaai",
"Genres": "Genres",
"FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
"ChapterNameValue": "Hoofstuk",
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
"Albums": "Albums"
}

View File

@ -1,22 +1,22 @@
{
"Albums": "Албуми",
"AppDeviceValues": "Програма: {0}, Устройство: {1}",
"AppDeviceValues": "Програма: {0}, устройство: {1}",
"Application": "Програма",
"Artists": "Изпълнители",
"AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
"Books": "Книги",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
"CameraImageUploadedFrom": "",
"Channels": "Канали",
"ChapterNameValue": "Глава {0}",
"Collections": "Колекции",
"DeviceOfflineWithName": "{0} се разкачи",
"DeviceOnlineWithName": "{0} е свързан",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"FailedLoginAttemptWithUserName": "",
"Favorites": "Любими",
"Folders": "Папки",
"Genres": "Жанрове",
"HeaderAlbumArtists": "Изпълнители на албуми",
"HeaderCameraUploads": "Camera Uploads",
"HeaderCameraUploads": "",
"HeaderContinueWatching": "Продължаване на гледането",
"HeaderFavoriteAlbums": "Любими албуми",
"HeaderFavoriteArtists": "Любими изпълнители",
@ -25,26 +25,26 @@
"HeaderFavoriteSongs": "Любими песни",
"HeaderLiveTV": "Телевизия на живо",
"HeaderNextUp": "Следва",
"HeaderRecordingGroups": "Recording Groups",
"HeaderRecordingGroups": "",
"HomeVideos": "Домашни клипове",
"Inherit": "Наследяване",
"ItemAddedWithName": "{0} е добавено към библиотеката",
"ItemRemovedWithName": "{0} е премахнато от библиотеката",
"LabelIpAddressValue": "ИП адрес: {0}",
"LabelRunningTimeValue": "Running time: {0}",
"LabelRunningTimeValue": "",
"Latest": "Последни",
"MessageApplicationUpdated": "Сървърът е обновен",
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
"MessageServerConfigurationUpdated": "Server configuration has been updated",
"MessageApplicationUpdatedTo": "",
"MessageNamedServerConfigurationUpdatedWithValue": "",
"MessageServerConfigurationUpdated": "",
"MixedContent": "Смесено съдържание",
"Movies": "Филми",
"Music": "Музика",
"MusicVideos": "Музикални клипове",
"NameInstallFailed": "{0} installation failed",
"NameInstallFailed": "",
"NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Season Unknown",
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
"NameSeasonUnknown": "Неразпознат сезон",
"NewVersionIsAvailable": "",
"NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата",
"NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано",
"NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
@ -58,7 +58,7 @@
"NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
"NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
"NotificationOptionTaskFailed": "Грешка в планирана задача",
"NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionUserLockedOut": "",
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
"Photos": "Снимки",
@ -70,12 +70,12 @@
"ProviderValue": "Доставчик: {0}",
"ScheduledTaskFailedWithName": "{0} се провали",
"ScheduledTaskStartedWithName": "{0} започна",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
"ServerNameNeedsToBeRestarted": "",
"Shows": "Сериали",
"Songs": "Песни",
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
"SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
"SubtitleDownloadFailureFromForItem": "",
"SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}",
"Sync": "Синхронизиране",
"System": "Система",
@ -83,15 +83,15 @@
"User": "Потребител",
"UserCreatedWithName": "Потребителят {0} е създаден",
"UserDeletedWithName": "Потребителят {0} е изтрит",
"UserDownloadingItemWithValues": "{0} is downloading {1}",
"UserLockedOutWithName": "User {0} has been locked out",
"UserDownloadingItemWithValues": "",
"UserLockedOutWithName": "",
"UserOfflineFromDevice": "{0} се разкачи от {1}",
"UserOnlineFromDevice": "{0} е на линия от {1}",
"UserPasswordChangedWithName": "Паролата на потребителя {0} е променена",
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
"UserPolicyUpdatedWithName": "",
"UserStartedPlayingItemWithValues": "{0} пусна {1}",
"UserStoppedPlayingItemWithValues": "{0} спря {1}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueHasBeenAddedToLibrary": "",
"ValueSpecialEpisodeName": "Специални - {0}",
"VersionNumber": "Версия {0}"
}

View File

@ -23,7 +23,7 @@
"HeaderFavoriteEpisodes": "Oblíbené epizody",
"HeaderFavoriteShows": "Oblíbené seriály",
"HeaderFavoriteSongs": "Oblíbená hudba",
"HeaderLiveTV": "Live TV",
"HeaderLiveTV": "Živá TV",
"HeaderNextUp": "Nadcházející",
"HeaderRecordingGroups": "Skupiny nahrávek",
"HomeVideos": "Domáci videa",

View File

@ -3,14 +3,14 @@
"AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung",
"Artists": "Interpreten",
"AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
"Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
"Channels": "Kanäle",
"ChapterNameValue": "Kapitel {0}",
"Collections": "Sammlungen",
"DeviceOfflineWithName": "{0} wurde getrennt",
"DeviceOnlineWithName": "{0} hat sich verbunden",
"DeviceOnlineWithName": "{0} ist verbunden",
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
"Favorites": "Favoriten",
"Folders": "Verzeichnisse",
@ -23,7 +23,7 @@
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
"HeaderFavoriteShows": "Lieblingsserien",
"HeaderFavoriteSongs": "Lieblingslieder",
"HeaderLiveTV": "Live-TV",
"HeaderLiveTV": "Live TV",
"HeaderNextUp": "Als Nächstes",
"HeaderRecordingGroups": "Aufnahme-Gruppen",
"HomeVideos": "Heimvideos",
@ -35,7 +35,7 @@
"Latest": "Neueste",
"MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
"MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert",
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert",
"MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
"MixedContent": "Gemischte Inhalte",
"Movies": "Filme",

View File

@ -5,7 +5,7 @@
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès",
"Books": "Livres",
"CameraImageUploadedFrom": "Une image de caméra a été chargée depuis {0}",
"CameraImageUploadedFrom": "Une nouvelle image de caméra a été chargée depuis {0}",
"Channels": "Chaînes",
"ChapterNameValue": "Chapitre {0}",
"Collections": "Collections",
@ -17,7 +17,7 @@
"Genres": "Genres",
"HeaderAlbumArtists": "Artistes de l'album",
"HeaderCameraUploads": "Photos transférées",
"HeaderContinueWatching": "Reprendre",
"HeaderContinueWatching": "Continuer à regarder",
"HeaderFavoriteAlbums": "Albums favoris",
"HeaderFavoriteArtists": "Artistes favoris",
"HeaderFavoriteEpisodes": "Épisodes favoris",
@ -34,14 +34,14 @@
"LabelRunningTimeValue": "Durée : {0}",
"Latest": "Derniers",
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
"MessageApplicationUpdatedTo": "Jellyfin Serveur a été mis à jour en version {0}",
"MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
"MixedContent": "Contenu mixte",
"Movies": "Films",
"Music": "Musique",
"MusicVideos": "Vidéos musicales",
"NameInstallFailed": "{0} échec d'installation",
"NameInstallFailed": "{0} échec de l'installation",
"NameSeasonNumber": "Saison {0}",
"NameSeasonUnknown": "Saison Inconnue",
"NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.",
@ -50,7 +50,7 @@
"NotificationOptionAudioPlayback": "Lecture audio démarrée",
"NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée",
"NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée",
"NotificationOptionInstallationFailed": "Échec d'installation",
"NotificationOptionInstallationFailed": "Échec de l'installation",
"NotificationOptionNewLibraryContent": "Nouveau contenu ajouté",
"NotificationOptionPluginError": "Erreur d'extension",
"NotificationOptionPluginInstalled": "Extension installée",
@ -91,7 +91,7 @@
"UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
"UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}",
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre librairie",
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
"ValueSpecialEpisodeName": "Spécial - {0}",
"VersionNumber": "Version {0}"
}

View File

@ -1,41 +1,41 @@
{
"Albums": "אלבומים",
"AppDeviceValues": "App: {0}, Device: {1}",
"AppDeviceValues": "יישום: {0}, מכשיר: {1}",
"Application": "אפליקציה",
"Artists": "אמנים",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
"AuthenticationSucceededWithUserName": "{0} זוהה בהצלחה",
"Books": "ספרים",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
"Channels": "Channels",
"ChapterNameValue": "Chapter {0}",
"Collections": "Collections",
"DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"Favorites": "Favorites",
"Folders": "Folders",
"CameraImageUploadedFrom": "תמונה חדשה הועלתה מ{0}",
"Channels": "ערוצים",
"ChapterNameValue": "פרק {0}",
"Collections": "קולקציות",
"DeviceOfflineWithName": "{0} התנתק",
"DeviceOnlineWithName": "{0} מחובר",
"FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי מ{0}",
"Favorites": "אהובים",
"Folders": "תיקיות",
"Genres": "ז'אנרים",
"HeaderAlbumArtists": "Album Artists",
"HeaderCameraUploads": "Camera Uploads",
"HeaderContinueWatching": "המשך בצפייה",
"HeaderFavoriteAlbums": "Favorite Albums",
"HeaderFavoriteArtists": "Favorite Artists",
"HeaderFavoriteEpisodes": "Favorite Episodes",
"HeaderFavoriteShows": "Favorite Shows",
"HeaderFavoriteSongs": "Favorite Songs",
"HeaderLiveTV": "Live TV",
"HeaderNextUp": "Next Up",
"HeaderAlbumArtists": "אמני האלבום",
"HeaderCameraUploads": "העלאות ממצלמה",
"HeaderContinueWatching": "המשך לצפות",
"HeaderFavoriteAlbums": "אלבומים שאהבתי",
"HeaderFavoriteArtists": "אמנים שאהבתי",
"HeaderFavoriteEpisodes": "פרקים אהובים",
"HeaderFavoriteShows": "תוכניות אהובות",
"HeaderFavoriteSongs": "שירים שאהבתי",
"HeaderLiveTV": "טלוויזיה בשידור חי",
"HeaderNextUp": "הבא",
"HeaderRecordingGroups": "קבוצות הקלטה",
"HomeVideos": "Home videos",
"Inherit": "Inherit",
"HomeVideos": "סרטונים בייתים",
"Inherit": "הורש",
"ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library",
"LabelIpAddressValue": "Ip address: {0}",
"LabelRunningTimeValue": "Running time: {0}",
"ItemRemovedWithName": "{0} נמחק מהספרייה",
"LabelIpAddressValue": "Ip כתובת: {0}",
"LabelRunningTimeValue": "משך צפייה: {0}",
"Latest": "אחרון",
"MessageApplicationUpdated": "Jellyfin Server has been updated",
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
"MessageApplicationUpdated": "שרת הJellyfin עודכן",
"MessageApplicationUpdatedTo": "שרת הJellyfin עודכן לגרסא {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "הגדרת השרת {0} שונתה",
"MessageServerConfigurationUpdated": "Server configuration has been updated",
"MixedContent": "תוכן מעורב",
"Movies": "סרטים",
@ -50,7 +50,7 @@
"NotificationOptionAudioPlayback": "Audio playback started",
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionInstallationFailed": "התקנה נכשלה",
"NotificationOptionNewLibraryContent": "New content added",
"NotificationOptionPluginError": "Plugin failure",
"NotificationOptionPluginInstalled": "Plugin installed",

View File

@ -5,7 +5,7 @@
"Artists": "Előadók",
"AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
"Books": "Könyvek",
"CameraImageUploadedFrom": "Új kamerakép került feltöltésre {0}",
"CameraImageUploadedFrom": "Új kamerakép került feltöltésre innen: {0}",
"Channels": "Csatornák",
"ChapterNameValue": "Jelenet {0}",
"Collections": "Gyűjtemények",
@ -15,14 +15,14 @@
"Favorites": "Kedvencek",
"Folders": "Könyvtárak",
"Genres": "Műfajok",
"HeaderAlbumArtists": "Album Előadók",
"HeaderAlbumArtists": "Album előadók",
"HeaderCameraUploads": "Kamera feltöltések",
"HeaderContinueWatching": "Folyamatban lévő filmek",
"HeaderFavoriteAlbums": "Kedvenc Albumok",
"HeaderFavoriteArtists": "Kedvenc Előadók",
"HeaderFavoriteEpisodes": "Kedvenc Epizódok",
"HeaderFavoriteShows": "Kedvenc Sorozatok",
"HeaderFavoriteSongs": "Kedvenc Dalok",
"HeaderFavoriteAlbums": "Kedvenc albumok",
"HeaderFavoriteArtists": "Kedvenc előadók",
"HeaderFavoriteEpisodes": "Kedvenc epizódok",
"HeaderFavoriteShows": "Kedvenc sorozatok",
"HeaderFavoriteSongs": "Kedvenc dalok",
"HeaderLiveTV": "Élő TV",
"HeaderNextUp": "Következik",
"HeaderRecordingGroups": "Felvételi csoportok",
@ -34,21 +34,21 @@
"LabelRunningTimeValue": "Futási idő: {0}",
"Latest": "Legújabb",
"MessageApplicationUpdated": "Jellyfin Szerver frissítve",
"MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész {0} frissítve",
"MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre: {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész frissítve: {0}",
"MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve",
"MixedContent": "Vegyes tartalom",
"Movies": "Filmek",
"Music": "Zene",
"MusicVideos": "Zenei Videók",
"MusicVideos": "Zenei videók",
"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",
"NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz",
"NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve",
"NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve",
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás leállítva",
"NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
"NotificationOptionInstallationFailed": "Telepítési hiba",
"NotificationOptionNewLibraryContent": "Új tartalom hozzáadva",
@ -60,15 +60,15 @@
"NotificationOptionTaskFailed": "Ütemezett feladat hiba",
"NotificationOptionUserLockedOut": "Felhasználó tiltva",
"NotificationOptionVideoPlayback": "Videó lejátszás elkezdve",
"NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve",
"NotificationOptionVideoPlaybackStopped": "Videó lejátszás leállítva",
"Photos": "Fényképek",
"Playlists": "Lejátszási listák",
"Plugin": "Bővítmény",
"PluginInstalledWithName": "{0} telepítve",
"PluginUninstalledWithName": "{0} eltávolítva",
"PluginUpdatedWithName": "{0} frissítve",
"ProviderValue": "Provider: {0}",
"ScheduledTaskFailedWithName": "{0} hiba",
"ProviderValue": "Szolgáltató: {0}",
"ScheduledTaskFailedWithName": "{0} sikertelen",
"ScheduledTaskStartedWithName": "{0} elkezdve",
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
"Shows": "Műsorok",
@ -76,10 +76,10 @@
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download 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}",
"SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz: {0}",
"Sync": "Szinkronizál",
"System": "Rendszer",
"TvShows": "TV Műsorok",
"TvShows": "TV műsorok",
"User": "Felhasználó",
"UserCreatedWithName": "{0} felhasználó létrehozva",
"UserDeletedWithName": "{0} felhasználó törölve",
@ -88,7 +88,7 @@
"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}",
"UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {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",

View File

@ -0,0 +1 @@
{}

View File

@ -9,13 +9,13 @@
"Channels": "Canali",
"ChapterNameValue": "Capitolo {0}",
"Collections": "Collezioni",
"DeviceOfflineWithName": "{0} è stato disconnesso",
"DeviceOfflineWithName": "{0} ha disconnesso",
"DeviceOnlineWithName": "{0} è connesso",
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
"Favorites": "Preferiti",
"Folders": "Cartelle",
"Genres": "Generi",
"HeaderAlbumArtists": "Artisti Album",
"HeaderAlbumArtists": "Artisti dell' Album",
"HeaderCameraUploads": "Caricamenti Fotocamera",
"HeaderContinueWatching": "Continua a guardare",
"HeaderFavoriteAlbums": "Album preferiti",
@ -32,7 +32,7 @@
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
"LabelIpAddressValue": "Indirizzo IP: {0}",
"LabelRunningTimeValue": "Durata: {0}",
"Latest": "Più recenti",
"Latest": "Novità",
"MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato",
"MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata",
@ -43,7 +43,7 @@
"MusicVideos": "Video musicali",
"NameInstallFailed": "{0} installazione fallita",
"NameSeasonNumber": "Stagione {0}",
"NameSeasonUnknown": "Stagione sconosciuto",
"NameSeasonUnknown": "Stagione sconosciuta",
"NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.",
"NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile",
"NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato",
@ -88,9 +88,9 @@
"UserOfflineFromDevice": "{0} è stato disconnesso da {1}",
"UserOnlineFromDevice": "{0} è online da {1}",
"UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}",
"UserPolicyUpdatedWithName": "La politica dell'utente è stata aggiornata per {0}",
"UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1}",
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1}",
"UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}",
"UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}",
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}",
"ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
"ValueSpecialEpisodeName": "Speciale - {0}",
"VersionNumber": "Versione {0}"

View File

@ -1,7 +1,7 @@
{
"Albums": "Albums",
"AppDeviceValues": "App: {0}, Apparaat: {1}",
"Application": "Toepassing",
"Application": "Applicatie",
"Artists": "Artiesten",
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
"Books": "Boeken",
@ -30,7 +30,7 @@
"Inherit": "Overerven",
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
"LabelIpAddressValue": "IP adres: {0}",
"LabelIpAddressValue": "IP-adres: {0}",
"LabelRunningTimeValue": "Looptijd: {0}",
"Latest": "Nieuwste",
"MessageApplicationUpdated": "Jellyfin Server is bijgewerkt",
@ -50,7 +50,7 @@
"NotificationOptionAudioPlayback": "Muziek gestart",
"NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
"NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
"NotificationOptionInstallationFailed": "Installatie mislukt",
"NotificationOptionInstallationFailed": "Installatie mislukking",
"NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
"NotificationOptionPluginError": "Plug-in fout",
"NotificationOptionPluginInstalled": "Plug-in geïnstalleerd",

View File

@ -5,7 +5,7 @@
"Artists": "Umelci",
"AuthenticationSucceededWithUserName": "{0} úspešne overený",
"Books": "Knihy",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
"CameraImageUploadedFrom": "Z {0} bola nahraná nová fotografia",
"Channels": "Kanály",
"ChapterNameValue": "Kapitola {0}",
"Collections": "Zbierky",
@ -15,9 +15,9 @@
"Favorites": "Obľúbené",
"Folders": "Priečinky",
"Genres": "Žánre",
"HeaderAlbumArtists": "Album Artists",
"HeaderAlbumArtists": "Albumy umelcov",
"HeaderCameraUploads": "Nahrané fotografie",
"HeaderContinueWatching": "Pokračujte v pozeraní",
"HeaderContinueWatching": "Pokračovať v pozeraní",
"HeaderFavoriteAlbums": "Obľúbené albumy",
"HeaderFavoriteArtists": "Obľúbení umelci",
"HeaderFavoriteEpisodes": "Obľúbené epizódy",

View File

@ -5,93 +5,93 @@
"Artists": "Sanatçılar",
"AuthenticationSucceededWithUserName": "{0} kimlik başarıyla doğrulandı",
"Books": "Kitaplar",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
"CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi",
"Channels": "Kanallar",
"ChapterNameValue": "Chapter {0}",
"Collections": "Collections",
"DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"Favorites": "Favorites",
"Folders": "Folders",
"Genres": "Genres",
"HeaderAlbumArtists": "Album Artists",
"HeaderCameraUploads": "Camera Uploads",
"ChapterNameValue": "Bölüm {0}",
"Collections": "Koleksiyonlar",
"DeviceOfflineWithName": "{0} bağlantısı kesildi",
"DeviceOnlineWithName": "{0} bağlı",
"FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
"Favorites": "Favoriler",
"Folders": "Klasörler",
"Genres": "Türler",
"HeaderAlbumArtists": "Albüm Sanatçıları",
"HeaderCameraUploads": "Kamera Yüklemeleri",
"HeaderContinueWatching": "İzlemeye Devam Et",
"HeaderFavoriteAlbums": "Favori Albümler",
"HeaderFavoriteArtists": "Favorite Artists",
"HeaderFavoriteEpisodes": "Favorite Episodes",
"HeaderFavoriteShows": "Favori Showlar",
"HeaderFavoriteSongs": "Favorite Songs",
"HeaderLiveTV": "Live TV",
"HeaderNextUp": "Next Up",
"HeaderRecordingGroups": "Recording Groups",
"HomeVideos": "Home videos",
"Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library",
"LabelIpAddressValue": "Ip adresi: {0}",
"HeaderFavoriteArtists": "Favori Sanatçılar",
"HeaderFavoriteEpisodes": "Favori Bölümler",
"HeaderFavoriteShows": "Favori Diziler",
"HeaderFavoriteSongs": "Favori Şarkılar",
"HeaderLiveTV": "Canlı TV",
"HeaderNextUp": "Sonraki hafta",
"HeaderRecordingGroups": "Kayıt Grupları",
"HomeVideos": "Ev videoları",
"Inherit": "Devral",
"ItemAddedWithName": "{0} kütüphaneye eklendi",
"ItemRemovedWithName": "{0} kütüphaneden silindi",
"LabelIpAddressValue": "IP adresi: {0}",
"LabelRunningTimeValue": "Çalışma süresi: {0}",
"Latest": "Latest",
"Latest": "En son",
"MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
"MessageServerConfigurationUpdated": "Server configuration has been updated",
"MixedContent": "Mixed content",
"Movies": "Movies",
"MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} olarak güncellendi",
"MessageNamedServerConfigurationUpdatedWithValue": "Sunucu ayarları kısım {0} güncellendi",
"MessageServerConfigurationUpdated": "Sunucu ayarları güncellendi",
"MixedContent": "Karışık içerik",
"Movies": "Filmler",
"Music": "Müzik",
"MusicVideos": "Müzik videoları",
"NameInstallFailed": "{0} kurulum başarısız",
"NameInstallFailed": "{0} kurulumu başarısız",
"NameSeasonNumber": "Sezon {0}",
"NameSeasonUnknown": "Bilinmeyen Sezon",
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionAudioPlayback": "Audio playback started",
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
"NotificationOptionInstallationFailed": "Yükleme hatası",
"NotificationOptionNewLibraryContent": "New content added",
"NotificationOptionPluginError": "Plugin failure",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionServerRestartRequired": "Server restart required",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionVideoPlayback": "Video playback started",
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
"Photos": "Photos",
"Playlists": "Playlists",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} was installed",
"PluginUninstalledWithName": "{0} was uninstalled",
"PluginUpdatedWithName": "{0} was updated",
"ProviderValue": "Provider: {0}",
"ScheduledTaskFailedWithName": "{0} failed",
"ScheduledTaskStartedWithName": "{0} started",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
"Shows": "Shows",
"Songs": "Songs",
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
"NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
"NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi",
"NotificationOptionAudioPlayback": "Ses çalma başladı",
"NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu",
"NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi",
"NotificationOptionInstallationFailed": "Yükleme başarısız oldu",
"NotificationOptionNewLibraryContent": "Yeni içerik eklendi",
"NotificationOptionPluginError": "Eklenti hatası",
"NotificationOptionPluginInstalled": "Eklenti yüklendi",
"NotificationOptionPluginUninstalled": "Eklenti kaldırıldı",
"NotificationOptionPluginUpdateInstalled": "Eklenti güncellemesi yüklendi",
"NotificationOptionServerRestartRequired": "Sunucu yeniden başlatma gerekli",
"NotificationOptionTaskFailed": "Zamanlanmış görev hatası",
"NotificationOptionUserLockedOut": "Kullanıcı kitlendi",
"NotificationOptionVideoPlayback": "Video oynatma başladı",
"NotificationOptionVideoPlaybackStopped": "Video oynatma durduruldu",
"Photos": "Fotoğraflar",
"Playlists": "Çalma listeleri",
"Plugin": "Eklenti",
"PluginInstalledWithName": "{0} yüklendi",
"PluginUninstalledWithName": "{0} kaldırıldı",
"PluginUpdatedWithName": "{0} güncellendi",
"ProviderValue": "Sağlayıcı: {0}",
"ScheduledTaskFailedWithName": "{0} başarısız oldu",
"ScheduledTaskStartedWithName": "{0} başladı",
"ServerNameNeedsToBeRestarted": "{0} yeniden başlatılması gerekiyor",
"Shows": "Diziler",
"Songs": "Şarkılar",
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
"Sync": "Sync",
"System": "System",
"TvShows": "TV Shows",
"User": "User",
"UserCreatedWithName": "User {0} has been created",
"UserDeletedWithName": "User {0} has been deleted",
"UserDownloadingItemWithValues": "{0} is downloading {1}",
"UserLockedOutWithName": "User {0} has been locked out",
"UserOfflineFromDevice": "{0} has disconnected from {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} is playing {1} on {2}",
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}"
"SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
"SubtitlesDownloadedForItem": "{0} için altyazılar indirildi",
"Sync": "Eşitle",
"System": "Sistem",
"TvShows": "Diziler",
"User": "Kullanıcı",
"UserCreatedWithName": "{0} kullanıcısı oluşturuldu",
"UserDeletedWithName": "Kullanıcı {0} silindi",
"UserDownloadingItemWithValues": "{0} indiriliyor {1}",
"UserLockedOutWithName": "Kullanıcı {0} kitlendi",
"UserOfflineFromDevice": "{0}, {1} ile bağlantısı kesildi",
"UserOnlineFromDevice": "{0}, {1} çevrimiçi",
"UserPasswordChangedWithName": "{0} kullanıcısı için şifre değiştirildi",
"UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi",
"UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
"ValueSpecialEpisodeName": "Özel - {0}",
"VersionNumber": "Versiyon {0}"
}

View File

@ -5,7 +5,7 @@
"Artists": "艺术家",
"AuthenticationSucceededWithUserName": "{0} 认证成功",
"Books": "书籍",
"CameraImageUploadedFrom": "已从 {0} 上传了一张新的相机图像",
"CameraImageUploadedFrom": "已从 {0} 上传了一张新的相",
"Channels": "频道",
"ChapterNameValue": "章节 {0}",
"Collections": "合集",

View File

@ -2,7 +2,7 @@
"Albums": "Albums",
"AppDeviceValues": "App: {0}, Device: {1}",
"Application": "Application",
"Artists": "Artists",
"Artists": "藝人",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
"Books": "Books",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",

View File

@ -1,6 +1,6 @@
{
"Albums": "專輯",
"AppDeviceValues": "應用: {0}, 裝置: {1}",
"AppDeviceValues": "軟體: {0}, 裝置: {1}",
"Application": "應用程式",
"Artists": "演出者",
"AuthenticationSucceededWithUserName": "{0} 成功授權",

View File

@ -5,11 +5,9 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;

View File

@ -27,12 +27,12 @@ namespace Emby.Server.Implementations.Middleware
var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
if (webSocketContext != null)
{
await _webSocketManager.OnWebSocketConnected(webSocketContext);
await _webSocketManager.OnWebSocketConnected(webSocketContext).ConfigureAwait(false);
}
}
else
{
await _next.Invoke(httpContext);
await _next.Invoke(httpContext).ConfigureAwait(false);
}
}
}

View File

@ -7,8 +7,6 @@ using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Networking
@ -20,6 +18,9 @@ namespace Emby.Server.Implementations.Networking
private IPAddress[] _localIpAddresses;
private readonly object _localIpAddressSyncLock = new object();
private readonly object _subnetLookupLock = new object();
private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
public NetworkManager(ILogger<NetworkManager> logger)
{
_logger = logger;
@ -28,10 +29,10 @@ namespace Emby.Server.Implementations.Networking
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
}
public Func<string[]> LocalSubnetsFn { get; set; }
public event EventHandler NetworkChanged;
public Func<string[]> LocalSubnetsFn { get; set; }
private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
_logger.LogDebug("NetworkAvailabilityChanged");
@ -52,10 +53,7 @@ namespace Emby.Server.Implementations.Networking
_macAddresses = null;
}
if (NetworkChanged != null)
{
NetworkChanged(this, EventArgs.Empty);
}
NetworkChanged?.Invoke(this, EventArgs.Empty);
}
public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
@ -179,10 +177,9 @@ namespace Emby.Server.Implementations.Networking
return false;
}
private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
private List<string> GetSubnets(string endpointFirstPart)
{
lock (_subnetLookup)
lock (_subnetLookupLock)
{
if (_subnetLookup.TryGetValue(endpointFirstPart, out var subnets))
{
@ -200,7 +197,11 @@ namespace Emby.Server.Implementations.Networking
int subnet_Test = 0;
foreach (string part in unicastIPAddressInformation.IPv4Mask.ToString().Split('.'))
{
if (part.Equals("0")) break;
if (part.Equals("0", StringComparison.Ordinal))
{
break;
}
subnet_Test++;
}
@ -255,10 +256,10 @@ namespace Emby.Server.Implementations.Networking
return true;
}
if (normalizedSubnet.IndexOf('/') != -1)
if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
{
var ipnetwork = IPNetwork.Parse(normalizedSubnet);
if (ipnetwork.Contains(address))
var ipNetwork = IPNetwork.Parse(normalizedSubnet);
if (ipNetwork.Contains(address))
{
return true;
}
@ -447,119 +448,11 @@ namespace Emby.Server.Implementations.Networking
.Select(x => x.GetPhysicalAddress())
.Where(x => x != null && x != PhysicalAddress.None);
/// <summary>
/// Parses the specified endpointstring.
/// </summary>
/// <param name="endpointstring">The endpointstring.</param>
/// <returns>IPEndPoint.</returns>
public IPEndPoint Parse(string endpointstring)
{
return Parse(endpointstring, -1).Result;
}
/// <summary>
/// Parses the specified endpointstring.
/// </summary>
/// <param name="endpointstring">The endpointstring.</param>
/// <param name="defaultport">The defaultport.</param>
/// <returns>IPEndPoint.</returns>
/// <exception cref="ArgumentException">Endpoint descriptor may not be empty.</exception>
/// <exception cref="FormatException"></exception>
private static async Task<IPEndPoint> Parse(string endpointstring, int defaultport)
{
if (string.IsNullOrEmpty(endpointstring)
|| endpointstring.Trim().Length == 0)
{
throw new ArgumentException("Endpoint descriptor may not be empty.");
}
if (defaultport != -1 &&
(defaultport < IPEndPoint.MinPort
|| defaultport > IPEndPoint.MaxPort))
{
throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport));
}
string[] values = endpointstring.Split(new char[] { ':' });
IPAddress ipaddy;
int port = -1;
//check if we have an IPv6 or ports
if (values.Length <= 2) // ipv4 or hostname
{
port = values.Length == 1 ? defaultport : GetPort(values[1]);
//try to use the address as IPv4, otherwise get hostname
if (!IPAddress.TryParse(values[0], out ipaddy))
ipaddy = await GetIPfromHost(values[0]).ConfigureAwait(false);
}
else if (values.Length > 2) //ipv6
{
//could [a:b:c]:d
if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
{
string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
ipaddy = IPAddress.Parse(ipaddressstring);
port = GetPort(values[values.Length - 1]);
}
else //[a:b:c] or a:b:c
{
ipaddy = IPAddress.Parse(endpointstring);
port = defaultport;
}
}
else
{
throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
}
if (port == -1)
throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring));
return new IPEndPoint(ipaddy, port);
}
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary>
/// Gets the port.
/// </summary>
/// <param name="p">The p.</param>
/// <returns>System.Int32.</returns>
/// <exception cref="FormatException"></exception>
private static int GetPort(string p)
{
if (!int.TryParse(p, out var port)
|| port < IPEndPoint.MinPort
|| port > IPEndPoint.MaxPort)
{
throw new FormatException(string.Format("Invalid end point port '{0}'", p));
}
return port;
}
/// <summary>
/// Gets the I pfrom host.
/// </summary>
/// <param name="p">The p.</param>
/// <returns>IPAddress.</returns>
/// <exception cref="ArgumentException"></exception>
private static async Task<IPAddress> GetIPfromHost(string p)
{
var hosts = await Dns.GetHostAddressesAsync(p).ConfigureAwait(false);
if (hosts == null || hosts.Length == 0)
throw new ArgumentException(string.Format("Host not found: {0}", p));
return hosts[0];
}
public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
{
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
IPAddress network2 = GetNetworkAddress(address2, subnetMask);
return network1.Equals(network2);
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
IPAddress network2 = GetNetworkAddress(address2, subnetMask);
return network1.Equals(network2);
}
private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
@ -575,7 +468,7 @@ namespace Emby.Server.Implementations.Networking
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
for (int i = 0; i < broadcastAddress.Length; i++)
{
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
broadcastAddress[i] = (byte)(ipAdressBytes[i] & subnetMaskBytes[i]);
}
return new IPAddress(broadcastAddress);
@ -615,24 +508,5 @@ namespace Emby.Server.Implementations.Networking
return null;
}
/// <summary>
/// Gets the network shares.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>IEnumerable{NetworkShare}.</returns>
public virtual IEnumerable<NetworkShare> GetNetworkShares(string path)
{
return new List<NetworkShare>();
}
/// <summary>
/// Gets available devices within the domain
/// </summary>
/// <returns>PC's in the Domain</returns>
public virtual IEnumerable<FileSystemEntryInfo> GetNetworkDevices()
{
return new List<FileSystemEntryInfo>();
}
}
}

View File

@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.Playlists
{
@ -24,13 +24,13 @@ namespace Emby.Server.Implementations.Playlists
return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>();
}
[IgnoreDataMember]
[JsonIgnore]
public override bool IsHidden => true;
[IgnoreDataMember]
[JsonIgnore]
public override bool SupportsInheritedParentImages => false;
[IgnoreDataMember]
[JsonIgnore]
public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists;
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
@ -48,4 +48,3 @@ namespace Emby.Server.Implementations.Playlists
}
}
}

View File

@ -90,8 +90,7 @@ namespace Emby.Server.Implementations.Playlists
}
else
{
var folder = item as Folder;
if (folder != null)
if (item is Folder folder)
{
options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist)
.Select(i => i.MediaType)
@ -140,7 +139,7 @@ namespace Emby.Server.Implementations.Playlists
parentFolder.AddChild(playlist, CancellationToken.None);
await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) { ForceSave = true }, CancellationToken.None)
await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None)
.ConfigureAwait(false);
if (options.ItemIdList.Length > 0)
@ -201,7 +200,7 @@ namespace Emby.Server.Implementations.Playlists
var list = new List<LinkedChild>();
var items = (GetPlaylistItems(itemIds, playlist.MediaType, user, options))
var items = GetPlaylistItems(itemIds, playlist.MediaType, user, options)
.Where(i => i.SupportsAddingToPlaylist)
.ToList();
@ -221,18 +220,18 @@ namespace Emby.Server.Implementations.Playlists
SavePlaylistFile(playlist);
}
_providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
{
ForceSave = true
}, RefreshPriority.High);
_providerManager.QueueRefresh(
playlist.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = true
},
RefreshPriority.High);
}
public void RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
{
var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
if (playlist == null)
if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
{
throw new ArgumentException("No Playlist exists with the supplied Id");
}
@ -254,7 +253,7 @@ namespace Emby.Server.Implementations.Playlists
SavePlaylistFile(playlist);
}
_providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
_providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = true
@ -263,9 +262,7 @@ namespace Emby.Server.Implementations.Playlists
public void MoveItem(string playlistId, string entryId, int newIndex)
{
var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
if (playlist == null)
if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
{
throw new ArgumentException("No Playlist exists with the supplied Id");
}

View File

@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
public event EventHandler<GenericEventArgs<double>> TaskProgress;
/// <summary>
/// Gets or sets the scheduled task.
/// Gets the scheduled task.
/// </summary>
/// <value>The scheduled task.</value>
public IScheduledTask ScheduledTask { get; private set; }
@ -215,11 +215,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
public double? CurrentProgress { get; private set; }
/// <summary>
/// The _triggers
/// The _triggers.
/// </summary>
private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
/// <summary>
/// Gets the triggers that define when the task will run
/// Gets the triggers that define when the task will run.
/// </summary>
/// <value>The triggers.</value>
private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
/// Gets the triggers that define when the task will run
/// Gets the triggers that define when the task will run.
/// </summary>
/// <value>The triggers.</value>
/// <exception cref="ArgumentNullException">value</exception>

View File

@ -36,19 +36,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// Gets or sets the json serializer.
/// </summary>
/// <value>The json serializer.</value>
private IJsonSerializer JsonSerializer { get; set; }
private readonly IJsonSerializer _jsonSerializer;
/// <summary>
/// Gets or sets the application paths.
/// </summary>
/// <value>The application paths.</value>
private IApplicationPaths ApplicationPaths { get; set; }
private readonly IApplicationPaths _applicationPaths;
/// <summary>
/// Gets the logger.
/// </summary>
/// <value>The logger.</value>
private ILogger Logger { get; set; }
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
/// <summary>
@ -57,19 +57,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="applicationPaths">The application paths.</param>
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <exception cref="System.ArgumentException">kernel</exception>
/// <param name="fileSystem">The filesystem manager.</param>
public TaskManager(
IApplicationPaths applicationPaths,
IJsonSerializer jsonSerializer,
ILoggerFactory loggerFactory,
IFileSystem fileSystem)
{
ApplicationPaths = applicationPaths;
JsonSerializer = jsonSerializer;
Logger = loggerFactory.CreateLogger(nameof(TaskManager));
_applicationPaths = applicationPaths;
_jsonSerializer = jsonSerializer;
_logger = loggerFactory.CreateLogger(nameof(TaskManager));
_fileSystem = fileSystem;
ScheduledTasks = new IScheduledTaskWorker[] { };
ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
}
/// <summary>
@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <typeparam name="T"></typeparam>
/// <param name="options">Task options.</param>
public void CancelIfRunningAndQueue<T>(TaskOptions options)
where T : IScheduledTask
where T : IScheduledTask
{
var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
((ScheduledTaskWorker)task).CancelIfRunning();
@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
if (scheduledTask == null)
{
Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
_logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
}
else
{
@ -147,13 +147,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
if (scheduledTask == null)
{
Logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name);
_logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name);
}
else
{
var type = scheduledTask.ScheduledTask.GetType();
Logger.LogInformation("Queueing task {0}", type.Name);
_logger.LogInformation("Queueing task {0}", type.Name);
lock (_taskQueue)
{
@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
if (scheduledTask == null)
{
Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
_logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
}
else
{
@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
{
var type = task.ScheduledTask.GetType();
Logger.LogInformation("Queueing task {0}", type.Name);
_logger.LogInformation("Queueing task {0}", type.Name);
lock (_taskQueue)
{
@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="tasks">The tasks.</param>
public void AddTasks(IEnumerable<IScheduledTask> tasks)
{
var list = tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem));
var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger, _fileSystem));
ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
}
@ -281,7 +281,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary>
private void ExecuteQueuedTasks()
{
Logger.LogInformation("ExecuteQueuedTasks");
_logger.LogInformation("ExecuteQueuedTasks");
// Execute queued tasks
lock (_taskQueue)

View File

@ -19,16 +19,17 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.ScheduledTasks
{
/// <summary>
/// Class ChapterImagesTask
/// Class ChapterImagesTask.
/// </summary>
public class ChapterImagesTask : IScheduledTask
{
/// <summary>
/// The _logger
/// The _logger.
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// The _library manager
/// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
@ -53,12 +54,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
/// Creates the triggers that define when the task will run
/// Creates the triggers that define when the task will run.
/// </summary>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[] {
return new[]
{
new TaskTriggerInfo
{
Type = TaskTriggerInfo.TriggerDaily,
@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
previouslyFailedImages = new List<string>();
}
var directoryService = new DirectoryService(_logger, _fileSystem);
var directoryService = new DirectoryService(_fileSystem);
foreach (var video in videos)
{

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
@ -15,24 +16,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
/// </summary>
public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
/// Gets or sets the application paths.
/// </summary>
/// <value>The application paths.</value>
private ServerApplicationPaths ApplicationPaths { get; set; }
private readonly ILogger _logger;
private readonly IConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class.
/// </summary>
public DeleteTranscodeFileTask(ServerApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
public DeleteTranscodeFileTask(ILogger logger, IFileSystem fileSystem, IConfigurationManager configurationManager)
{
ApplicationPaths = appPaths;
_logger = logger;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
}
/// <summary>
@ -52,14 +47,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
var minDateModified = DateTime.UtcNow.AddDays(-1);
progress.Report(50);
try
{
DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress);
}
catch (DirectoryNotFoundException)
{
// No biggie here. Nothing to delete
}
DeleteTempFilesFromDirectory(cancellationToken, _configurationManager.GetTranscodePath(), minDateModified, progress);
return Task.CompletedTask;
}
@ -138,13 +126,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
}
}
public string Name => "Transcoding temp cleanup";
public string Name => "Transcode file cleanup";
public string Description => "Deletes transcoding temp files older than 24 hours.";
public string Description => "Deletes transcode files more than 24 hours old.";
public string Category => "Maintenance";
public string Key => "DeleteTranscodingTempFiles";
public string Key => "DeleteTranscodeFiles";
public bool IsHidden => false;

View File

@ -1,24 +1,23 @@
using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Net;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Progress;
using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.ScheduledTasks
{
/// <summary>
/// Plugin Update Task
/// Plugin Update Task.
/// </summary>
public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
/// The _logger
/// The _logger.
/// </summary>
private readonly ILogger _logger;
@ -31,7 +30,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
/// Creates the triggers that define when the task will run
/// Creates the triggers that define when the task will run.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
@ -44,16 +43,18 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
/// Update installed plugins
/// Update installed plugins.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
/// <returns>Task.</returns>
/// <returns><see cref="Task" />.</returns>
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
progress.Report(0);
var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(typeof(PluginUpdateTask).Assembly.GetName().Version, true, cancellationToken).ConfigureAwait(false)).ToList();
var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
progress.Report(10);
@ -94,18 +95,25 @@ namespace Emby.Server.Implementations.ScheduledTasks
progress.Report(100);
}
/// <inheritdoc />
public string Name => "Check for plugin updates";
/// <inheritdoc />
public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
/// <inheritdoc />
public string Category => "Application";
/// <inheritdoc />
public string Key => "PluginUpdates";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Globalization;
using System.Threading;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
@ -7,12 +6,12 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.ScheduledTasks
{
/// <summary>
/// Represents a task trigger that fires everyday
/// Represents a task trigger that fires everyday.
/// </summary>
public class DailyTrigger : ITaskTrigger
{
/// <summary>
/// Get the time of day to trigger the task to run
/// Get the time of day to trigger the task to run.
/// </summary>
/// <value>The time of day.</value>
public TimeSpan TimeOfDay { get; set; }

View File

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Serialization
{
@ -14,35 +12,13 @@ namespace Emby.Server.Implementations.Serialization
/// </summary>
public class MyXmlSerializer : IXmlSerializer
{
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
public MyXmlSerializer(
IFileSystem fileSystem,
ILoggerFactory loggerFactory)
{
_fileSystem = fileSystem;
_logger = loggerFactory.CreateLogger("XmlSerializer");
}
// Need to cache these
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
private readonly Dictionary<string, XmlSerializer> _serializers =
new Dictionary<string, XmlSerializer>();
private static readonly ConcurrentDictionary<string, XmlSerializer> _serializers =
new ConcurrentDictionary<string, XmlSerializer>();
private XmlSerializer GetSerializer(Type type)
{
var key = type.FullName;
lock (_serializers)
{
if (!_serializers.TryGetValue(key, out var serializer))
{
serializer = new XmlSerializer(type);
_serializers[key] = serializer;
}
return serializer;
}
}
private static XmlSerializer GetSerializer(Type type)
=> _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type));
/// <summary>
/// Serializes to writer.
@ -91,7 +67,6 @@ namespace Emby.Server.Implementations.Serialization
/// <param name="file">The file.</param>
public void SerializeToFile(object obj, string file)
{
_logger.LogDebug("Serializing to file {0}", file);
using (var stream = new FileStream(file, FileMode.Create))
{
SerializeToStream(obj, stream);
@ -106,7 +81,6 @@ namespace Emby.Server.Implementations.Serialization
/// <returns>System.Object.</returns>
public object DeserializeFromFile(Type type, string file)
{
_logger.LogDebug("Deserializing file {0}", file);
using (var stream = File.OpenRead(file))
{
return DeserializeFromStream(type, stream);

View File

@ -1,4 +1,3 @@
using System;
using System.IO;
using Emby.Server.Implementations.AppBase;
using MediaBrowser.Controller;
@ -6,12 +5,10 @@ using MediaBrowser.Controller;
namespace Emby.Server.Implementations
{
/// <summary>
/// Extends BaseApplicationPaths to add paths that are only applicable on the server
/// Extends BaseApplicationPaths to add paths that are only applicable on the server.
/// </summary>
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
{
private string _defaultTranscodingTempPath;
private string _transcodingTempPath;
private string _internalMetadataPath;
/// <summary>
@ -23,7 +20,8 @@ namespace Emby.Server.Implementations
string configurationDirectoryPath,
string cacheDirectoryPath,
string webDirectoryPath)
: base(programDataPath,
: base(
programDataPath,
logDirectoryPath,
configurationDirectoryPath,
cacheDirectoryPath,
@ -31,8 +29,6 @@ namespace Emby.Server.Implementations
{
}
public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
/// <summary>
/// Gets the path to the base root media directory.
/// </summary>
@ -45,18 +41,13 @@ namespace Emby.Server.Implementations
/// <value>The default user views path.</value>
public string DefaultUserViewsPath => Path.Combine(RootFolderPath, "default");
/// <summary>
/// Gets the path to localization data.
/// </summary>
/// <value>The localization path.</value>
public string LocalizationPath => Path.Combine(ProgramDataPath, "localization");
/// <summary>
/// Gets the path to the People directory.
/// </summary>
/// <value>The people path.</value>
public string PeoplePath => Path.Combine(InternalMetadataPath, "People");
/// <inheritdoc />
public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists");
/// <summary>
@ -107,46 +98,14 @@ namespace Emby.Server.Implementations
/// <value>The user configuration directory path.</value>
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp"));
public string TranscodingTempPath
{
get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath);
set => _transcodingTempPath = value;
}
public string GetTranscodingTempPath()
{
var path = TranscodingTempPath;
if (!string.Equals(path, DefaultTranscodingTempPath, StringComparison.OrdinalIgnoreCase))
{
try
{
Directory.CreateDirectory(path);
var testPath = Path.Combine(path, Guid.NewGuid().ToString());
Directory.CreateDirectory(testPath);
Directory.Delete(testPath);
return path;
}
catch
{
}
}
path = DefaultTranscodingTempPath;
Directory.CreateDirectory(path);
return path;
}
/// <inheritdoc />
public string InternalMetadataPath
{
get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
set => _internalMetadataPath = value;
}
/// <inheritdoc />
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
}
}

View File

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.Services
{
@ -28,6 +30,13 @@ namespace Emby.Server.Implementations.Services
private readonly bool[] isWildcard;
private readonly int wildcardCount = 0;
internal static string[] IgnoreAttributesNamed = new[]
{
nameof(JsonIgnoreAttribute)
};
private static Type _excludeType = typeof(Stream);
public int VariableArgsCount { get; set; }
/// <summary>
@ -37,8 +46,8 @@ namespace Emby.Server.Implementations.Services
public int PathComponentsCount { get; set; }
/// <summary>
/// The total number of segments after subparts have been exploded ('.')
/// e.g. /path/to/here.ext == 4
/// Gets or sets the total number of segments after subparts have been exploded ('.')
/// e.g. /path/to/here.ext == 4.
/// </summary>
public int TotalComponentsCount { get; set; }
@ -190,21 +199,12 @@ namespace Emby.Server.Implementations.Services
StringComparer.OrdinalIgnoreCase);
}
internal static string[] IgnoreAttributesNamed = new[]
{
"IgnoreDataMemberAttribute",
"JsonIgnoreAttribute"
};
private static Type excludeType = typeof(Stream);
internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type)
{
foreach (var prop in GetPublicProperties(type))
{
if (prop.GetMethod == null
|| excludeType == prop.PropertyType)
|| _excludeType == prop.PropertyType)
{
continue;
}
@ -280,7 +280,7 @@ namespace Emby.Server.Implementations.Services
}
/// <summary>
/// Provide for quick lookups based on hashes that can be determined from a request url
/// Provide for quick lookups based on hashes that can be determined from a request url.
/// </summary>
public string FirstMatchHashKey { get; private set; }
@ -437,9 +437,12 @@ namespace Emby.Server.Implementations.Services
&& requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
if (!isValidWildCardPath)
throw new ArgumentException(string.Format(
"Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
pathInfo, this.restPath));
throw new ArgumentException(
string.Format(
CultureInfo.InvariantCulture,
"Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
pathInfo,
this.restPath));
}
var requestKeyValuesMap = new Dictionary<string, string>();

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.Net;
@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.Services
private SwaggerTag[] GetTags()
{
return new SwaggerTag[] { };
return Array.Empty<SwaggerTag>();
}
private Dictionary<string, SwaggerDefinition> GetDefinitions()

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